@rudderhq/db 0.4.6-canary.9 → 0.5.1-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.test.js +633 -0
- package/dist/client.test.js.map +1 -1
- package/dist/embedded-postgres-recovery.d.ts +1 -0
- package/dist/embedded-postgres-recovery.d.ts.map +1 -1
- package/dist/embedded-postgres-recovery.js +33 -0
- package/dist/embedded-postgres-recovery.js.map +1 -1
- package/dist/embedded-postgres-recovery.test.js +19 -1
- package/dist/embedded-postgres-recovery.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/migration-runtime.d.ts.map +1 -1
- package/dist/migration-runtime.js +3 -20
- package/dist/migration-runtime.js.map +1 -1
- package/dist/migrations/0102_complex_retro_girl.sql +38 -0
- package/dist/migrations/0103_cute_colonel_america.sql +4 -0
- package/dist/migrations/0104_unusual_mister_fear.sql +173 -0
- package/dist/migrations/0105_chat_queue_actor_reconciliation.sql +54 -0
- package/dist/migrations/0106_damp_amphibian.sql +9 -0
- package/dist/migrations/0107_daffy_luke_cage.sql +20 -0
- package/dist/migrations/0108_atomic_chat_first_turn_cleanup.sql +74 -0
- package/dist/migrations/atomic-chat-first-turn-cleanup.test.ts +18 -0
- package/dist/migrations/meta/0102_snapshot.json +19090 -0
- package/dist/migrations/meta/0103_snapshot.json +19114 -0
- package/dist/migrations/meta/0104_snapshot.json +20338 -0
- package/dist/migrations/meta/0105_snapshot.json +20338 -0
- package/dist/migrations/meta/0106_snapshot.json +20442 -0
- package/dist/migrations/meta/0107_snapshot.json +20646 -0
- package/dist/migrations/meta/_journal.json +50 -1
- package/dist/schema/activity_log.d.ts +17 -0
- package/dist/schema/activity_log.d.ts.map +1 -1
- package/dist/schema/activity_log.js +6 -1
- package/dist/schema/activity_log.js.map +1 -1
- package/dist/schema/agent_wakeup_requests.d.ts.map +1 -1
- package/dist/schema/agent_wakeup_requests.js +5 -1
- package/dist/schema/agent_wakeup_requests.js.map +1 -1
- package/dist/schema/chat_control_actions.d.ts +442 -0
- package/dist/schema/chat_control_actions.d.ts.map +1 -0
- package/dist/schema/chat_control_actions.js +35 -0
- package/dist/schema/chat_control_actions.js.map +1 -0
- package/dist/schema/chat_conversations.d.ts +119 -0
- package/dist/schema/chat_conversations.d.ts.map +1 -1
- package/dist/schema/chat_conversations.js +10 -1
- package/dist/schema/chat_conversations.js.map +1 -1
- package/dist/schema/chat_generation_events.d.ts +268 -0
- package/dist/schema/chat_generation_events.d.ts.map +1 -0
- package/dist/schema/chat_generation_events.js +32 -0
- package/dist/schema/chat_generation_events.js.map +1 -0
- package/dist/schema/chat_generation_terminal_outbox.d.ts +370 -0
- package/dist/schema/chat_generation_terminal_outbox.d.ts.map +1 -0
- package/dist/schema/chat_generation_terminal_outbox.js +33 -0
- package/dist/schema/chat_generation_terminal_outbox.js.map +1 -0
- package/dist/schema/chat_generations.d.ts +279 -2
- package/dist/schema/chat_generations.d.ts.map +1 -1
- package/dist/schema/chat_generations.js +23 -2
- package/dist/schema/chat_generations.js.map +1 -1
- package/dist/schema/chat_queued_messages.d.ts +285 -2
- package/dist/schema/chat_queued_messages.d.ts.map +1 -1
- package/dist/schema/chat_queued_messages.js +23 -1
- package/dist/schema/chat_queued_messages.js.map +1 -1
- package/dist/schema/cost_events.d.ts +34 -0
- package/dist/schema/cost_events.d.ts.map +1 -1
- package/dist/schema/cost_events.js +5 -1
- package/dist/schema/cost_events.js.map +1 -1
- package/dist/schema/heartbeat_run_events.d.ts +17 -0
- package/dist/schema/heartbeat_run_events.d.ts.map +1 -1
- package/dist/schema/heartbeat_run_events.js +6 -1
- package/dist/schema/heartbeat_run_events.js.map +1 -1
- package/dist/schema/heartbeat_runs.d.ts +303 -0
- package/dist/schema/heartbeat_runs.d.ts.map +1 -1
- package/dist/schema/heartbeat_runs.js +24 -2
- package/dist/schema/heartbeat_runs.js.map +1 -1
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/messenger_saved_views.d.ts +234 -0
- package/dist/schema/messenger_saved_views.d.ts.map +1 -0
- package/dist/schema/messenger_saved_views.js +27 -0
- package/dist/schema/messenger_saved_views.js.map +1 -0
- package/dist/seed.js +2 -2
- package/dist/seed.js.map +1 -1
- package/package.json +2 -2
package/dist/client.test.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { drizzle as drizzlePg } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { migrate as migratePg } from "drizzle-orm/postgres-js/migrator";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import fs from "node:fs";
|
|
3
5
|
import net from "node:net";
|
|
@@ -62,6 +64,93 @@ async function migrationHash(migrationFile) {
|
|
|
62
64
|
const content = await fs.promises.readFile(new URL(`./migrations/${migrationFile}`, import.meta.url), "utf8");
|
|
63
65
|
return createHash("sha256").update(content).digest("hex");
|
|
64
66
|
}
|
|
67
|
+
function createLegacyTerminalMigrationsFolder() {
|
|
68
|
+
const migrationsFolder = fs.mkdtempSync(path.join(os.tmpdir(), "rudder-legacy-terminal-migrations-"));
|
|
69
|
+
tempPaths.push(migrationsFolder);
|
|
70
|
+
fs.mkdirSync(path.join(migrationsFolder, "meta"));
|
|
71
|
+
const currentMigrationsUrl = new URL("./migrations/", import.meta.url);
|
|
72
|
+
const currentJournal = JSON.parse(fs.readFileSync(new URL("meta/_journal.json", currentMigrationsUrl), "utf8"));
|
|
73
|
+
const baseEntries = currentJournal.entries.filter((entry) => entry.idx < 102);
|
|
74
|
+
if (baseEntries.at(-1)?.tag !== "0101_bizarre_morlun") {
|
|
75
|
+
throw new Error("Legacy terminal migration fixture requires the 0101 base schema");
|
|
76
|
+
}
|
|
77
|
+
for (const entry of baseEntries) {
|
|
78
|
+
fs.copyFileSync(new URL(`${entry.tag}.sql`, currentMigrationsUrl), path.join(migrationsFolder, `${entry.tag}.sql`));
|
|
79
|
+
}
|
|
80
|
+
const fixtureUrl = new URL("./test-fixtures/legacy-terminal-state-migrations/", import.meta.url);
|
|
81
|
+
const manifest = JSON.parse(fs.readFileSync(new URL("manifest.json", fixtureUrl), "utf8"));
|
|
82
|
+
for (const entry of manifest.entries) {
|
|
83
|
+
const fixtureContent = fs.readFileSync(new URL(`${entry.tag}.sql`, fixtureUrl), "utf8");
|
|
84
|
+
const normalizedFixtureContent = fixtureContent.replaceAll("\r\n", "\n");
|
|
85
|
+
const historicalContent = normalizedFixtureContent.endsWith("\n")
|
|
86
|
+
? normalizedFixtureContent.slice(0, -1)
|
|
87
|
+
: normalizedFixtureContent;
|
|
88
|
+
const actualHash = createHash("sha256").update(historicalContent).digest("hex");
|
|
89
|
+
if (actualHash !== entry.sha256) {
|
|
90
|
+
throw new Error(`Legacy migration fixture hash mismatch for ${entry.tag}: expected ${entry.sha256}, received ${actualHash}`);
|
|
91
|
+
}
|
|
92
|
+
fs.writeFileSync(path.join(migrationsFolder, `${entry.tag}.sql`), historicalContent);
|
|
93
|
+
}
|
|
94
|
+
fs.writeFileSync(path.join(migrationsFolder, "meta", "_journal.json"), JSON.stringify({
|
|
95
|
+
version: currentJournal.version,
|
|
96
|
+
dialect: currentJournal.dialect,
|
|
97
|
+
entries: [
|
|
98
|
+
...baseEntries,
|
|
99
|
+
...manifest.entries.map(({ sha256: _sha256, ...entry }) => entry),
|
|
100
|
+
],
|
|
101
|
+
}));
|
|
102
|
+
return { manifest, migrationsFolder };
|
|
103
|
+
}
|
|
104
|
+
function createLegacyChatRuntimeControlsMigrationsFolder() {
|
|
105
|
+
const migrationsFolder = fs.mkdtempSync(path.join(os.tmpdir(), "rudder-legacy-chat-controls-migrations-"));
|
|
106
|
+
tempPaths.push(migrationsFolder);
|
|
107
|
+
fs.mkdirSync(path.join(migrationsFolder, "meta"));
|
|
108
|
+
const currentMigrationsUrl = new URL("./migrations/", import.meta.url);
|
|
109
|
+
const currentJournal = JSON.parse(fs.readFileSync(new URL("meta/_journal.json", currentMigrationsUrl), "utf8"));
|
|
110
|
+
const baseEntries = currentJournal.entries.filter((entry) => entry.idx < 102);
|
|
111
|
+
if (baseEntries.at(-1)?.tag !== "0101_bizarre_morlun") {
|
|
112
|
+
throw new Error("Legacy chat control migration fixture requires the 0101 base schema");
|
|
113
|
+
}
|
|
114
|
+
for (const entry of baseEntries) {
|
|
115
|
+
fs.copyFileSync(new URL(`${entry.tag}.sql`, currentMigrationsUrl), path.join(migrationsFolder, `${entry.tag}.sql`));
|
|
116
|
+
}
|
|
117
|
+
const historicalContent = fs.readFileSync(new URL("0104_unusual_mister_fear.sql", currentMigrationsUrl), "utf8");
|
|
118
|
+
const historicalHash = createHash("sha256").update(historicalContent).digest("hex");
|
|
119
|
+
if (historicalHash !== "a1fc0446af5ec1640890bb9cf36208eab8dce6687c233029bd54e179613e1af7") {
|
|
120
|
+
throw new Error(`Legacy chat runtime controls fixture hash changed: ${historicalHash}`);
|
|
121
|
+
}
|
|
122
|
+
const legacyEntry = {
|
|
123
|
+
idx: 102,
|
|
124
|
+
version: "7",
|
|
125
|
+
when: 1784159513933,
|
|
126
|
+
tag: "0102_chat_runtime_controls",
|
|
127
|
+
breakpoints: true,
|
|
128
|
+
};
|
|
129
|
+
fs.writeFileSync(path.join(migrationsFolder, `${legacyEntry.tag}.sql`), historicalContent);
|
|
130
|
+
fs.writeFileSync(path.join(migrationsFolder, "meta", "_journal.json"), JSON.stringify({
|
|
131
|
+
version: currentJournal.version,
|
|
132
|
+
dialect: currentJournal.dialect,
|
|
133
|
+
entries: [...baseEntries, legacyEntry],
|
|
134
|
+
}));
|
|
135
|
+
return { historicalHash, migrationsFolder };
|
|
136
|
+
}
|
|
137
|
+
function createCurrentMigrationsFolderThrough(maxIdx) {
|
|
138
|
+
const migrationsFolder = fs.mkdtempSync(path.join(os.tmpdir(), `rudder-migrations-through-${maxIdx}-`));
|
|
139
|
+
tempPaths.push(migrationsFolder);
|
|
140
|
+
fs.mkdirSync(path.join(migrationsFolder, "meta"));
|
|
141
|
+
const currentMigrationsUrl = new URL("./migrations/", import.meta.url);
|
|
142
|
+
const currentJournal = JSON.parse(fs.readFileSync(new URL("meta/_journal.json", currentMigrationsUrl), "utf8"));
|
|
143
|
+
const entries = currentJournal.entries.filter((entry) => entry.idx <= maxIdx);
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
fs.copyFileSync(new URL(`${entry.tag}.sql`, currentMigrationsUrl), path.join(migrationsFolder, `${entry.tag}.sql`));
|
|
146
|
+
}
|
|
147
|
+
fs.writeFileSync(path.join(migrationsFolder, "meta", "_journal.json"), JSON.stringify({
|
|
148
|
+
version: currentJournal.version,
|
|
149
|
+
dialect: currentJournal.dialect,
|
|
150
|
+
entries,
|
|
151
|
+
}));
|
|
152
|
+
return migrationsFolder;
|
|
153
|
+
}
|
|
65
154
|
afterEach(async () => {
|
|
66
155
|
while (runningInstances.length > 0) {
|
|
67
156
|
const instance = runningInstances.pop();
|
|
@@ -245,6 +334,550 @@ describe("applyPendingMigrations", () => {
|
|
|
245
334
|
await verifySql.end();
|
|
246
335
|
}
|
|
247
336
|
}, 20_000);
|
|
337
|
+
it("reapplies consolidated migration 0102 over the legacy 0102-0104 feature shape", async () => {
|
|
338
|
+
const connectionString = await createTempDatabase();
|
|
339
|
+
const { manifest, migrationsFolder } = createLegacyTerminalMigrationsFolder();
|
|
340
|
+
expect(manifest.sourceCommit).toBe("0491a75db25a24e644e67a502239fd1209840f40");
|
|
341
|
+
const sql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
342
|
+
try {
|
|
343
|
+
await migratePg(drizzlePg(sql), { migrationsFolder });
|
|
344
|
+
const legacyHistory = await sql.unsafe(`
|
|
345
|
+
SELECT hash, created_at
|
|
346
|
+
FROM "drizzle"."__drizzle_migrations"
|
|
347
|
+
ORDER BY created_at DESC
|
|
348
|
+
LIMIT 3
|
|
349
|
+
`);
|
|
350
|
+
expect(legacyHistory.map((row) => ({
|
|
351
|
+
hash: row.hash,
|
|
352
|
+
createdAt: Number(row.created_at),
|
|
353
|
+
}))).toEqual([...manifest.entries].reverse().map((entry) => ({
|
|
354
|
+
hash: entry.sha256,
|
|
355
|
+
createdAt: entry.when,
|
|
356
|
+
})));
|
|
357
|
+
const legacyColumns = await sql.unsafe(`
|
|
358
|
+
SELECT table_name, column_name
|
|
359
|
+
FROM information_schema.columns
|
|
360
|
+
WHERE table_schema = 'public'
|
|
361
|
+
AND table_name IN ('heartbeat_runs', 'cost_events')
|
|
362
|
+
AND column_name IN (
|
|
363
|
+
'terminal_effects_pending',
|
|
364
|
+
'process_exited_at',
|
|
365
|
+
'terminal_effects_json',
|
|
366
|
+
'terminal_effects_last_error',
|
|
367
|
+
'budget_evaluated_at',
|
|
368
|
+
'execution_owner_token',
|
|
369
|
+
'execution_lease_expires_at',
|
|
370
|
+
'terminal_effects_completed_json'
|
|
371
|
+
)
|
|
372
|
+
ORDER BY table_name, column_name
|
|
373
|
+
`);
|
|
374
|
+
expect(legacyColumns).toEqual([
|
|
375
|
+
{ table_name: "cost_events", column_name: "budget_evaluated_at" },
|
|
376
|
+
{ table_name: "heartbeat_runs", column_name: "process_exited_at" },
|
|
377
|
+
{ table_name: "heartbeat_runs", column_name: "terminal_effects_json" },
|
|
378
|
+
{ table_name: "heartbeat_runs", column_name: "terminal_effects_last_error" },
|
|
379
|
+
{ table_name: "heartbeat_runs", column_name: "terminal_effects_pending" },
|
|
380
|
+
]);
|
|
381
|
+
}
|
|
382
|
+
finally {
|
|
383
|
+
await sql.end();
|
|
384
|
+
}
|
|
385
|
+
const pendingState = await inspectMigrations(connectionString);
|
|
386
|
+
expect(pendingState).toMatchObject({
|
|
387
|
+
status: "needsMigrations",
|
|
388
|
+
pendingMigrations: [
|
|
389
|
+
"0055_illegal_sheva_callister.sql",
|
|
390
|
+
"0102_complex_retro_girl.sql",
|
|
391
|
+
"0103_cute_colonel_america.sql",
|
|
392
|
+
"0104_unusual_mister_fear.sql",
|
|
393
|
+
"0105_chat_queue_actor_reconciliation.sql",
|
|
394
|
+
"0106_damp_amphibian.sql",
|
|
395
|
+
"0107_daffy_luke_cage.sql",
|
|
396
|
+
"0108_atomic_chat_first_turn_cleanup.sql",
|
|
397
|
+
],
|
|
398
|
+
reason: "pending-migrations",
|
|
399
|
+
});
|
|
400
|
+
await expect(applyPendingMigrations(connectionString)).resolves.toBeUndefined();
|
|
401
|
+
expect((await inspectMigrations(connectionString)).status).toBe("upToDate");
|
|
402
|
+
const verifySql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
403
|
+
try {
|
|
404
|
+
const consolidatedHash = await migrationHash("0102_complex_retro_girl.sql");
|
|
405
|
+
const consolidatedHistory = await verifySql.unsafe(`
|
|
406
|
+
SELECT hash
|
|
407
|
+
FROM "drizzle"."__drizzle_migrations"
|
|
408
|
+
WHERE hash = '${consolidatedHash}'
|
|
409
|
+
`);
|
|
410
|
+
expect(consolidatedHistory).toEqual([{ hash: consolidatedHash }]);
|
|
411
|
+
const columns = await verifySql.unsafe(`
|
|
412
|
+
SELECT column_name
|
|
413
|
+
FROM information_schema.columns
|
|
414
|
+
WHERE table_schema = 'public'
|
|
415
|
+
AND table_name = 'heartbeat_runs'
|
|
416
|
+
AND column_name IN (
|
|
417
|
+
'execution_owner_token',
|
|
418
|
+
'execution_lease_expires_at',
|
|
419
|
+
'session_params_after_json',
|
|
420
|
+
'session_params_before_json',
|
|
421
|
+
'session_reuse_scope',
|
|
422
|
+
'terminal_effects_completed_json',
|
|
423
|
+
'terminal_effects_last_error'
|
|
424
|
+
)
|
|
425
|
+
ORDER BY column_name
|
|
426
|
+
`);
|
|
427
|
+
expect(columns.map((row) => row.column_name)).toEqual([
|
|
428
|
+
"execution_lease_expires_at",
|
|
429
|
+
"execution_owner_token",
|
|
430
|
+
"session_params_after_json",
|
|
431
|
+
"session_params_before_json",
|
|
432
|
+
"session_reuse_scope",
|
|
433
|
+
"terminal_effects_completed_json",
|
|
434
|
+
"terminal_effects_last_error",
|
|
435
|
+
]);
|
|
436
|
+
const indexes = await verifySql.unsafe(`
|
|
437
|
+
SELECT indexname
|
|
438
|
+
FROM pg_indexes
|
|
439
|
+
WHERE schemaname = 'public'
|
|
440
|
+
AND indexname IN (
|
|
441
|
+
'heartbeat_runs_active_chat_conversation_uq',
|
|
442
|
+
'heartbeat_runs_status_execution_lease_created_idx',
|
|
443
|
+
'heartbeat_run_events_run_idempotency_key_uq'
|
|
444
|
+
)
|
|
445
|
+
ORDER BY indexname
|
|
446
|
+
`);
|
|
447
|
+
expect(indexes.map((row) => row.indexname)).toEqual([
|
|
448
|
+
"heartbeat_run_events_run_idempotency_key_uq",
|
|
449
|
+
"heartbeat_runs_active_chat_conversation_uq",
|
|
450
|
+
"heartbeat_runs_status_execution_lease_created_idx",
|
|
451
|
+
]);
|
|
452
|
+
}
|
|
453
|
+
finally {
|
|
454
|
+
await verifySql.end();
|
|
455
|
+
}
|
|
456
|
+
}, 20_000);
|
|
457
|
+
it("upgrades databases that applied the legacy 0102 chat runtime controls migration", async () => {
|
|
458
|
+
const connectionString = await createTempDatabase();
|
|
459
|
+
const { historicalHash, migrationsFolder } = createLegacyChatRuntimeControlsMigrationsFolder();
|
|
460
|
+
const sql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
461
|
+
try {
|
|
462
|
+
await migratePg(drizzlePg(sql), { migrationsFolder });
|
|
463
|
+
await sql.unsafe(`
|
|
464
|
+
INSERT INTO "organizations" ("id", "url_key", "name", "issue_prefix")
|
|
465
|
+
VALUES (
|
|
466
|
+
'00000000-0000-0000-0000-000000000122',
|
|
467
|
+
'legacy-chat-controls',
|
|
468
|
+
'Legacy Chat Controls',
|
|
469
|
+
'LCC'
|
|
470
|
+
)
|
|
471
|
+
`);
|
|
472
|
+
await sql.unsafe(`
|
|
473
|
+
INSERT INTO "chat_conversations" ("id", "org_id", "title")
|
|
474
|
+
VALUES (
|
|
475
|
+
'00000000-0000-0000-0000-000000000123',
|
|
476
|
+
'00000000-0000-0000-0000-000000000122',
|
|
477
|
+
'Legacy chat runtime controls'
|
|
478
|
+
)
|
|
479
|
+
`);
|
|
480
|
+
await sql.unsafe(`
|
|
481
|
+
INSERT INTO "chat_queued_messages" (
|
|
482
|
+
"id",
|
|
483
|
+
"org_id",
|
|
484
|
+
"conversation_id",
|
|
485
|
+
"position",
|
|
486
|
+
"status",
|
|
487
|
+
"client_mutation_id",
|
|
488
|
+
"payload",
|
|
489
|
+
"delivery_intent",
|
|
490
|
+
"delivery_disposition"
|
|
491
|
+
)
|
|
492
|
+
VALUES (
|
|
493
|
+
'00000000-0000-0000-0000-000000000124',
|
|
494
|
+
'00000000-0000-0000-0000-000000000122',
|
|
495
|
+
'00000000-0000-0000-0000-000000000123',
|
|
496
|
+
1,
|
|
497
|
+
'continuation_pending',
|
|
498
|
+
'legacy-wip-steer',
|
|
499
|
+
'{"body":"legacy WIP steer"}'::jsonb,
|
|
500
|
+
'steer',
|
|
501
|
+
'continuation_pending'
|
|
502
|
+
)
|
|
503
|
+
`);
|
|
504
|
+
}
|
|
505
|
+
finally {
|
|
506
|
+
await sql.end();
|
|
507
|
+
}
|
|
508
|
+
const pendingState = await inspectMigrations(connectionString);
|
|
509
|
+
expect(pendingState).toMatchObject({
|
|
510
|
+
status: "needsMigrations",
|
|
511
|
+
pendingMigrations: [
|
|
512
|
+
"0055_illegal_sheva_callister.sql",
|
|
513
|
+
"0102_complex_retro_girl.sql",
|
|
514
|
+
"0103_cute_colonel_america.sql",
|
|
515
|
+
"0105_chat_queue_actor_reconciliation.sql",
|
|
516
|
+
"0106_damp_amphibian.sql",
|
|
517
|
+
"0107_daffy_luke_cage.sql",
|
|
518
|
+
"0108_atomic_chat_first_turn_cleanup.sql",
|
|
519
|
+
],
|
|
520
|
+
reason: "pending-migrations",
|
|
521
|
+
});
|
|
522
|
+
await expect(applyPendingMigrations(connectionString)).resolves.toBeUndefined();
|
|
523
|
+
expect((await inspectMigrations(connectionString)).status).toBe("upToDate");
|
|
524
|
+
const verifySql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
525
|
+
try {
|
|
526
|
+
const legacyHistory = await verifySql.unsafe(`
|
|
527
|
+
SELECT hash
|
|
528
|
+
FROM "drizzle"."__drizzle_migrations"
|
|
529
|
+
WHERE hash = '${historicalHash}'
|
|
530
|
+
`);
|
|
531
|
+
expect(legacyHistory).toEqual([{ hash: historicalHash }]);
|
|
532
|
+
const [queueItem] = await verifySql.unsafe(`
|
|
533
|
+
SELECT
|
|
534
|
+
status,
|
|
535
|
+
delivery_intent,
|
|
536
|
+
delivery_disposition,
|
|
537
|
+
request_actor,
|
|
538
|
+
reconciliation_reason
|
|
539
|
+
FROM "chat_queued_messages"
|
|
540
|
+
WHERE "id" = '00000000-0000-0000-0000-000000000124'
|
|
541
|
+
`);
|
|
542
|
+
expect(queueItem).toEqual({
|
|
543
|
+
status: "queued",
|
|
544
|
+
delivery_intent: "queue",
|
|
545
|
+
delivery_disposition: null,
|
|
546
|
+
request_actor: null,
|
|
547
|
+
reconciliation_reason: "legacy_request_actor_unavailable",
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
finally {
|
|
551
|
+
await verifySql.end();
|
|
552
|
+
}
|
|
553
|
+
}, 20_000);
|
|
554
|
+
it("upgrades 0102 heartbeat runs with durable session lineage columns", async () => {
|
|
555
|
+
const connectionString = await createTempDatabase();
|
|
556
|
+
const migrationsThrough0102 = createCurrentMigrationsFolderThrough(102);
|
|
557
|
+
const sql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
558
|
+
try {
|
|
559
|
+
await migratePg(drizzlePg(sql), { migrationsFolder: migrationsThrough0102 });
|
|
560
|
+
await sql.unsafe(`
|
|
561
|
+
INSERT INTO "organizations" ("id", "url_key", "name", "issue_prefix")
|
|
562
|
+
VALUES ('00000000-0000-0000-0000-000000000103', 'session-lineage', 'Session Lineage', 'SLG')
|
|
563
|
+
`);
|
|
564
|
+
await sql.unsafe(`
|
|
565
|
+
INSERT INTO "agents" ("id", "org_id", "name")
|
|
566
|
+
VALUES (
|
|
567
|
+
'00000000-0000-0000-0000-000000000104',
|
|
568
|
+
'00000000-0000-0000-0000-000000000103',
|
|
569
|
+
'Migration Agent'
|
|
570
|
+
)
|
|
571
|
+
`);
|
|
572
|
+
await sql.unsafe(`
|
|
573
|
+
INSERT INTO "heartbeat_runs" ("id", "org_id", "agent_id")
|
|
574
|
+
VALUES (
|
|
575
|
+
'00000000-0000-0000-0000-000000000105',
|
|
576
|
+
'00000000-0000-0000-0000-000000000103',
|
|
577
|
+
'00000000-0000-0000-0000-000000000104'
|
|
578
|
+
)
|
|
579
|
+
`);
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
await sql.end();
|
|
583
|
+
}
|
|
584
|
+
await expect(applyPendingMigrations(connectionString)).resolves.toBeUndefined();
|
|
585
|
+
expect((await inspectMigrations(connectionString)).status).toBe("upToDate");
|
|
586
|
+
const verifySql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
587
|
+
try {
|
|
588
|
+
const [legacyRun] = await verifySql.unsafe(`
|
|
589
|
+
SELECT session_params_before_json, session_params_after_json, session_reuse_scope
|
|
590
|
+
FROM "heartbeat_runs"
|
|
591
|
+
WHERE "id" = '00000000-0000-0000-0000-000000000105'
|
|
592
|
+
`);
|
|
593
|
+
expect(legacyRun).toEqual({
|
|
594
|
+
session_params_before_json: null,
|
|
595
|
+
session_params_after_json: null,
|
|
596
|
+
session_reuse_scope: "unknown",
|
|
597
|
+
});
|
|
598
|
+
for (const scope of ["explicit", "task", "none", "unknown"]) {
|
|
599
|
+
await expect(verifySql.unsafe(`
|
|
600
|
+
UPDATE "heartbeat_runs"
|
|
601
|
+
SET "session_reuse_scope" = '${scope}',
|
|
602
|
+
"session_params_before_json" = '{"sessionId":"before"}'::jsonb,
|
|
603
|
+
"session_params_after_json" = '{"sessionId":"after"}'::jsonb
|
|
604
|
+
WHERE "id" = '00000000-0000-0000-0000-000000000105'
|
|
605
|
+
`)).resolves.toBeDefined();
|
|
606
|
+
}
|
|
607
|
+
await expect(verifySql.unsafe(`
|
|
608
|
+
UPDATE "heartbeat_runs"
|
|
609
|
+
SET "session_reuse_scope" = 'global'
|
|
610
|
+
WHERE "id" = '00000000-0000-0000-0000-000000000105'
|
|
611
|
+
`)).rejects.toMatchObject({ code: "23514" });
|
|
612
|
+
}
|
|
613
|
+
finally {
|
|
614
|
+
await verifySql.end();
|
|
615
|
+
}
|
|
616
|
+
}, 20_000);
|
|
617
|
+
it("reconciles legacy chat control states before creating active-generation constraints", async () => {
|
|
618
|
+
const connectionString = await createTempDatabase();
|
|
619
|
+
const migrationsThrough0103 = createCurrentMigrationsFolderThrough(103);
|
|
620
|
+
const sql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
621
|
+
try {
|
|
622
|
+
await migratePg(drizzlePg(sql), { migrationsFolder: migrationsThrough0103 });
|
|
623
|
+
await sql.unsafe(`
|
|
624
|
+
INSERT INTO "organizations" ("id", "url_key", "name", "issue_prefix")
|
|
625
|
+
VALUES (
|
|
626
|
+
'00000000-0000-0000-0000-000000000110',
|
|
627
|
+
'chat-control-migration',
|
|
628
|
+
'Chat Control Migration',
|
|
629
|
+
'CCM'
|
|
630
|
+
)
|
|
631
|
+
`);
|
|
632
|
+
await sql.unsafe(`
|
|
633
|
+
INSERT INTO "agents" ("id", "org_id", "name")
|
|
634
|
+
VALUES (
|
|
635
|
+
'00000000-0000-0000-0000-000000000119',
|
|
636
|
+
'00000000-0000-0000-0000-000000000110',
|
|
637
|
+
'Legacy queue agent'
|
|
638
|
+
)
|
|
639
|
+
`);
|
|
640
|
+
await sql.unsafe(`
|
|
641
|
+
INSERT INTO "chat_conversations" ("id", "org_id", "title")
|
|
642
|
+
VALUES (
|
|
643
|
+
'00000000-0000-0000-0000-000000000111',
|
|
644
|
+
'00000000-0000-0000-0000-000000000110',
|
|
645
|
+
'Legacy chat control state'
|
|
646
|
+
)
|
|
647
|
+
`);
|
|
648
|
+
await sql.unsafe(`
|
|
649
|
+
INSERT INTO "chat_generations" ("id", "org_id", "conversation_id", "status")
|
|
650
|
+
VALUES
|
|
651
|
+
(
|
|
652
|
+
'00000000-0000-0000-0000-000000000112',
|
|
653
|
+
'00000000-0000-0000-0000-000000000110',
|
|
654
|
+
'00000000-0000-0000-0000-000000000111',
|
|
655
|
+
'active'
|
|
656
|
+
),
|
|
657
|
+
(
|
|
658
|
+
'00000000-0000-0000-0000-000000000113',
|
|
659
|
+
'00000000-0000-0000-0000-000000000110',
|
|
660
|
+
'00000000-0000-0000-0000-000000000111',
|
|
661
|
+
'running'
|
|
662
|
+
)
|
|
663
|
+
`);
|
|
664
|
+
await sql.unsafe(`
|
|
665
|
+
INSERT INTO "chat_queued_messages" (
|
|
666
|
+
"id",
|
|
667
|
+
"org_id",
|
|
668
|
+
"conversation_id",
|
|
669
|
+
"position",
|
|
670
|
+
"status",
|
|
671
|
+
"client_mutation_id",
|
|
672
|
+
"payload",
|
|
673
|
+
"last_delivery_reason"
|
|
674
|
+
)
|
|
675
|
+
VALUES
|
|
676
|
+
(
|
|
677
|
+
'00000000-0000-0000-0000-000000000114',
|
|
678
|
+
'00000000-0000-0000-0000-000000000110',
|
|
679
|
+
'00000000-0000-0000-0000-000000000111',
|
|
680
|
+
1,
|
|
681
|
+
'steer_pending',
|
|
682
|
+
'legacy-steer',
|
|
683
|
+
'{"body":"steer"}'::jsonb,
|
|
684
|
+
'unsupported'
|
|
685
|
+
),
|
|
686
|
+
(
|
|
687
|
+
'00000000-0000-0000-0000-000000000115',
|
|
688
|
+
'00000000-0000-0000-0000-000000000110',
|
|
689
|
+
'00000000-0000-0000-0000-000000000111',
|
|
690
|
+
2,
|
|
691
|
+
'dequeue_claimed',
|
|
692
|
+
'legacy-claim',
|
|
693
|
+
'{"body":"claim"}'::jsonb,
|
|
694
|
+
NULL
|
|
695
|
+
),
|
|
696
|
+
(
|
|
697
|
+
'00000000-0000-0000-0000-000000000116',
|
|
698
|
+
'00000000-0000-0000-0000-000000000110',
|
|
699
|
+
'00000000-0000-0000-0000-000000000111',
|
|
700
|
+
3,
|
|
701
|
+
'running',
|
|
702
|
+
'legacy-running',
|
|
703
|
+
'{"body":"running"}'::jsonb,
|
|
704
|
+
NULL
|
|
705
|
+
),
|
|
706
|
+
(
|
|
707
|
+
'00000000-0000-0000-0000-000000000117',
|
|
708
|
+
'00000000-0000-0000-0000-000000000110',
|
|
709
|
+
'00000000-0000-0000-0000-000000000111',
|
|
710
|
+
4,
|
|
711
|
+
'queued',
|
|
712
|
+
'legacy-actorless-queue',
|
|
713
|
+
'{"body":"actorless queue"}'::jsonb,
|
|
714
|
+
NULL
|
|
715
|
+
),
|
|
716
|
+
(
|
|
717
|
+
'00000000-0000-0000-0000-000000000118',
|
|
718
|
+
'00000000-0000-0000-0000-000000000110',
|
|
719
|
+
'00000000-0000-0000-0000-000000000111',
|
|
720
|
+
5,
|
|
721
|
+
'steer_pending',
|
|
722
|
+
'legacy-actorless-steer',
|
|
723
|
+
'{"body":"actorless steer"}'::jsonb,
|
|
724
|
+
'unsupported'
|
|
725
|
+
)
|
|
726
|
+
`);
|
|
727
|
+
await sql.unsafe(`
|
|
728
|
+
INSERT INTO "activity_log" (
|
|
729
|
+
"id",
|
|
730
|
+
"org_id",
|
|
731
|
+
"actor_type",
|
|
732
|
+
"actor_id",
|
|
733
|
+
"action",
|
|
734
|
+
"entity_type",
|
|
735
|
+
"entity_id",
|
|
736
|
+
"agent_id",
|
|
737
|
+
"details"
|
|
738
|
+
)
|
|
739
|
+
VALUES
|
|
740
|
+
(
|
|
741
|
+
'00000000-0000-0000-0000-000000000120',
|
|
742
|
+
'00000000-0000-0000-0000-000000000110',
|
|
743
|
+
'user',
|
|
744
|
+
'legacy-board-user',
|
|
745
|
+
'chat.queue.created',
|
|
746
|
+
'chat',
|
|
747
|
+
'00000000-0000-0000-0000-000000000111',
|
|
748
|
+
NULL,
|
|
749
|
+
'{"queuedMessageId":"00000000-0000-0000-0000-000000000114"}'::jsonb
|
|
750
|
+
),
|
|
751
|
+
(
|
|
752
|
+
'00000000-0000-0000-0000-000000000121',
|
|
753
|
+
'00000000-0000-0000-0000-000000000110',
|
|
754
|
+
'agent',
|
|
755
|
+
'00000000-0000-0000-0000-000000000119',
|
|
756
|
+
'chat.queue.created',
|
|
757
|
+
'chat',
|
|
758
|
+
'00000000-0000-0000-0000-000000000111',
|
|
759
|
+
'00000000-0000-0000-0000-000000000119',
|
|
760
|
+
'{"queuedMessageId":"00000000-0000-0000-0000-000000000115"}'::jsonb
|
|
761
|
+
)
|
|
762
|
+
`);
|
|
763
|
+
}
|
|
764
|
+
finally {
|
|
765
|
+
await sql.end();
|
|
766
|
+
}
|
|
767
|
+
await expect(applyPendingMigrations(connectionString)).resolves.toBeUndefined();
|
|
768
|
+
expect((await inspectMigrations(connectionString)).status).toBe("upToDate");
|
|
769
|
+
const verifySql = postgres(connectionString, { max: 1, onnotice: () => { } });
|
|
770
|
+
try {
|
|
771
|
+
const generations = await verifySql.unsafe(`
|
|
772
|
+
SELECT
|
|
773
|
+
status,
|
|
774
|
+
terminal_reason,
|
|
775
|
+
control_state,
|
|
776
|
+
runtime_terminal_at IS NOT NULL AS runtime_terminal_recorded,
|
|
777
|
+
completed_at IS NOT NULL AS completed_recorded
|
|
778
|
+
FROM "chat_generations"
|
|
779
|
+
WHERE "conversation_id" = '00000000-0000-0000-0000-000000000111'
|
|
780
|
+
ORDER BY "id"
|
|
781
|
+
`);
|
|
782
|
+
expect(generations).toEqual([
|
|
783
|
+
{
|
|
784
|
+
status: "aborted",
|
|
785
|
+
terminal_reason: "ownerless_during_durable_control_migration",
|
|
786
|
+
control_state: "terminal",
|
|
787
|
+
runtime_terminal_recorded: true,
|
|
788
|
+
completed_recorded: true,
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
status: "aborted",
|
|
792
|
+
terminal_reason: "ownerless_during_durable_control_migration",
|
|
793
|
+
control_state: "terminal",
|
|
794
|
+
runtime_terminal_recorded: true,
|
|
795
|
+
completed_recorded: true,
|
|
796
|
+
},
|
|
797
|
+
]);
|
|
798
|
+
const queueItems = await verifySql.unsafe(`
|
|
799
|
+
SELECT
|
|
800
|
+
position,
|
|
801
|
+
status,
|
|
802
|
+
delivery_intent,
|
|
803
|
+
delivery_disposition,
|
|
804
|
+
request_actor,
|
|
805
|
+
reconciliation_reason,
|
|
806
|
+
last_delivery_reason
|
|
807
|
+
FROM "chat_queued_messages"
|
|
808
|
+
WHERE "conversation_id" = '00000000-0000-0000-0000-000000000111'
|
|
809
|
+
ORDER BY "position"
|
|
810
|
+
`);
|
|
811
|
+
expect(queueItems).toEqual([
|
|
812
|
+
{
|
|
813
|
+
position: 1,
|
|
814
|
+
status: "continuation_pending",
|
|
815
|
+
delivery_intent: "steer",
|
|
816
|
+
delivery_disposition: "continuation_pending",
|
|
817
|
+
request_actor: {
|
|
818
|
+
type: "board",
|
|
819
|
+
source: "session",
|
|
820
|
+
userId: "legacy-board-user",
|
|
821
|
+
orgIds: ["00000000-0000-0000-0000-000000000110"],
|
|
822
|
+
isInstanceAdmin: false,
|
|
823
|
+
},
|
|
824
|
+
reconciliation_reason: "legacy_steer_recovered_on_upgrade",
|
|
825
|
+
last_delivery_reason: null,
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
position: 2,
|
|
829
|
+
status: "queued",
|
|
830
|
+
delivery_intent: "queue",
|
|
831
|
+
delivery_disposition: null,
|
|
832
|
+
request_actor: {
|
|
833
|
+
type: "agent",
|
|
834
|
+
source: "agent_key",
|
|
835
|
+
orgId: "00000000-0000-0000-0000-000000000110",
|
|
836
|
+
agentId: "00000000-0000-0000-0000-000000000119",
|
|
837
|
+
},
|
|
838
|
+
reconciliation_reason: "legacy_claim_released_on_upgrade",
|
|
839
|
+
last_delivery_reason: "legacy_claim_released_on_upgrade",
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
position: 3,
|
|
843
|
+
status: "failed_actionable",
|
|
844
|
+
delivery_intent: "queue",
|
|
845
|
+
delivery_disposition: "failed_actionable",
|
|
846
|
+
request_actor: null,
|
|
847
|
+
reconciliation_reason: "legacy_running_delivery_unconfirmed_on_upgrade",
|
|
848
|
+
last_delivery_reason: "legacy_running_delivery_unconfirmed_on_upgrade",
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
position: 4,
|
|
852
|
+
status: "queued",
|
|
853
|
+
delivery_intent: "queue",
|
|
854
|
+
delivery_disposition: null,
|
|
855
|
+
request_actor: null,
|
|
856
|
+
reconciliation_reason: "legacy_request_actor_unavailable",
|
|
857
|
+
last_delivery_reason: "legacy_request_actor_unavailable",
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
position: 5,
|
|
861
|
+
status: "queued",
|
|
862
|
+
delivery_intent: "queue",
|
|
863
|
+
delivery_disposition: null,
|
|
864
|
+
request_actor: null,
|
|
865
|
+
reconciliation_reason: "legacy_request_actor_unavailable",
|
|
866
|
+
last_delivery_reason: "legacy_request_actor_unavailable",
|
|
867
|
+
},
|
|
868
|
+
]);
|
|
869
|
+
const activeIndex = await verifySql.unsafe(`
|
|
870
|
+
SELECT indexname
|
|
871
|
+
FROM pg_indexes
|
|
872
|
+
WHERE schemaname = 'public'
|
|
873
|
+
AND indexname = 'chat_generations_active_conversation_uq'
|
|
874
|
+
`);
|
|
875
|
+
expect(activeIndex).toEqual([{ indexname: "chat_generations_active_conversation_uq" }]);
|
|
876
|
+
}
|
|
877
|
+
finally {
|
|
878
|
+
await verifySql.end();
|
|
879
|
+
}
|
|
880
|
+
}, 20_000);
|
|
248
881
|
it("rolls back migration 0100 when a legacy alias conflicts with another organization route", async () => {
|
|
249
882
|
const connectionString = await createTempDatabase();
|
|
250
883
|
await applyPendingMigrations(connectionString);
|