1import { assert, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as SqlClient from "effect/sql/SqlClient";
5
6import { migrationEntries, runMigrations } from "../Migrations.ts";
7import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient";
8
9const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layer({ filename: ":memory:" })));
10
11layer("055_OrchestrationV2", (it) => {
12 it.effect("keeps released migrations contiguous", () =>
13 Effect.sync(() => {
14 assert.deepStrictEqual(
15 migrationEntries.map(([id]) => id),
16 Array.from({ length: 62 }, (_, index) => index + 1),
17 );
18 }),
19 );
20
21 it.effect("upgrades released schema 53 through the latest migrations", () =>
22 Effect.gen(function* () {
23 const sql = yield* SqlClient.SqlClient;
24 yield* runMigrations({ toMigrationInclusive: 53 });
25
26 const executed = yield* runMigrations();
27 assert.deepStrictEqual(executed, [
28 [54, "ProjectionThreadsAutoSettleDisabledAt"],
29 [55, "OrchestrationV2"],
30 [56, "RemoveRedundantProjectionIndexes"],
31 [57, "ScheduledTaskWebhooks"],
32 [58, "WebhookRelayDeliveries"],
33 [59, "McpAppModelContext"],
34 [60, "ThreadSnapshotWindowIndexes"],
35 [61, "PullRequestReviewGuides"],
36 [62, "ThreadFailureLookupIndex"],
37 ]);
38 assert.deepStrictEqual(yield* runMigrations(), []);
39
40 const migrations = yield* sql<{
41 readonly migration_id: number;
42 readonly name: string;
43 }>`
44 SELECT migration_id, name
45 FROM effect_sql_migrations
46 WHERE migration_id >= 48
47 ORDER BY migration_id
48 `;
49 assert.deepStrictEqual(migrations, [
50 { migration_id: 48, name: "ProjectionThreadBranchPullRequest" },
51 { migration_id: 49, name: "ProjectionThreadsActiveOrderKey" },
52 { migration_id: 50, name: "ProjectionThreadPullRequests" },
53 { migration_id: 51, name: "ProjectionThreadMessageContext" },
54 { migration_id: 52, name: "ProjectionThreadTitleState" },
55 { migration_id: 53, name: "PullRequestFilesViewed" },
56 { migration_id: 54, name: "ProjectionThreadsAutoSettleDisabledAt" },
57 { migration_id: 55, name: "OrchestrationV2" },
58 { migration_id: 56, name: "RemoveRedundantProjectionIndexes" },
59 { migration_id: 57, name: "ScheduledTaskWebhooks" },
60 { migration_id: 58, name: "WebhookRelayDeliveries" },
61 { migration_id: 59, name: "McpAppModelContext" },
62 { migration_id: 60, name: "ThreadSnapshotWindowIndexes" },
63 { migration_id: 61, name: "PullRequestReviewGuides" },
64 { migration_id: 62, name: "ThreadFailureLookupIndex" },
65 ]);
66
67 const tables = yield* sql<{ readonly name: string }>`
68 SELECT name
69 FROM sqlite_master
70 WHERE type = 'table'
71 AND name IN (
72 'orchestration_v2_projection_threads',
73 'orchestration_v2_projection_subagents',
74 'orchestration_v2_effect_outbox',
75 'orchestration_v2_turn_item_positions',
76 'orchestration_v2_projection_metadata',
77 'orchestration_v2_projection_provider_session_bindings',
78 'orchestration_v2_thread_launch_workflows',
79 'orchestration_v2_legacy_imports',
80 'scheduled_tasks'
81 )
82 ORDER BY name
83 `;
84 assert.deepStrictEqual(
85 tables.map(({ name }) => name),
86 [
87 "orchestration_v2_effect_outbox",
88 "orchestration_v2_legacy_imports",
89 "orchestration_v2_projection_metadata",
90 "orchestration_v2_projection_provider_session_bindings",
91 "orchestration_v2_projection_subagents",
92 "orchestration_v2_projection_threads",
93 "orchestration_v2_thread_launch_workflows",
94 "orchestration_v2_turn_item_positions",
95 "scheduled_tasks",
96 ],
97 );
98
99 const eventColumns = yield* sql<{ readonly name: string }>`
100 PRAGMA table_info(orchestration_events)
101 `;
102 const receiptColumns = yield* sql<{ readonly name: string }>`
103 PRAGMA table_info(orchestration_command_receipts)
104 `;
105 const threadColumns = yield* sql<{ readonly name: string }>`
106 PRAGMA table_info(orchestration_v2_projection_threads)
107 `;
108 const subagentColumns = yield* sql<{ readonly name: string }>`
109 PRAGMA table_info(orchestration_v2_projection_subagents)
110 `;
111 assert.ok(eventColumns.some(({ name }) => name === "application_event_version"));
112 assert.ok(receiptColumns.some(({ name }) => name === "command_type"));
113 assert.ok(threadColumns.some(({ name }) => name === "provider_instance_id"));
114 assert.ok(subagentColumns.some(({ name }) => name === "driver"));
115 assert.ok(subagentColumns.some(({ name }) => name === "provider_instance_id"));
116
117 const indexes = yield* sql<{ readonly name: string }>`
118 SELECT name
119 FROM sqlite_master
120 WHERE type = 'index'
121 AND name IN (
122 'idx_orchestration_events_application_high_water',
123 'orchestration_events_v2_created_threads_idx',
124 'orchestration_v2_projection_turn_items_shell_pending_idx'
125 )
126 ORDER BY name
127 `;
128 assert.deepStrictEqual(
129 indexes.map(({ name }) => name),
130 [
131 "idx_orchestration_events_application_high_water",
132 "orchestration_events_v2_created_threads_idx",
133 "orchestration_v2_projection_turn_items_shell_pending_idx",
134 ],
135 );
136 }),
137 );
138});