@vellumai/assistant 0.10.1-dev.202606240723.ea25efe → 0.10.1-dev.202606241108.5d35d48

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.10.1-dev.202606240723.ea25efe",
3
+ "version": "0.10.1-dev.202606241108.5d35d48",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { createHash } from "node:crypto";
14
14
  import { Database } from "bun:sqlite";
15
- import { afterEach, describe, expect, test } from "bun:test";
15
+ import { describe, expect, test } from "bun:test";
16
16
 
17
17
  import { drizzle } from "drizzle-orm/bun-sqlite";
18
18
 
@@ -69,16 +69,16 @@ import { migrateRenameThreadStartersCheckpointsDown } from "../memory/migrations
69
69
  import { migrateBackfillAudioAttachmentMimeTypesDown } from "../memory/migrations/191-backfill-audio-attachment-mime-types.js";
70
70
  import { migrateLlmUsageAttribution } from "../memory/migrations/235-llm-usage-attribution.js";
71
71
  import {
72
- MIGRATION_REGISTRY,
73
- type MigrationRegistryEntry,
74
- type MigrationValidationResult,
75
- } from "../memory/migrations/registry.js";
76
- import { runMigrationSteps } from "../memory/migrations/run-migrations.js";
72
+ type MigrationStep,
73
+ runMigrationSteps,
74
+ } from "../memory/migrations/run-migrations.js";
77
75
  import {
76
+ type MigrationValidationResult,
78
77
  rollbackMemoryMigration,
79
78
  validateMigrationState,
80
79
  } from "../memory/migrations/validate-migration-state.js";
81
80
  import * as schema from "../memory/schema.js";
81
+ import { migrationSteps } from "../memory/steps.js";
82
82
 
83
83
  // ---------------------------------------------------------------------------
84
84
  // Helpers
@@ -547,7 +547,7 @@ describe("schema-drift recovery: migration handles unexpected schema state", ()
547
547
  // structured diagnostic data. Assert directly on the returned result rather
548
548
  // than re-deriving the crashed list from the raw DB — this verifies the
549
549
  // function itself detects the crash, not just that the data is present.
550
- const result: MigrationValidationResult = validateMigrationState(db);
550
+ const result: MigrationValidationResult = validateMigrationState(db, migrationSteps);
551
551
  expect(result.crashed).toContain("step:migrateJobDeferrals");
552
552
  expect(result.crashed).not.toContain(
553
553
  "step:migrateMemoryEntityRelationDedup",
@@ -575,22 +575,24 @@ describe("schema-drift recovery: migration handles unexpected schema state", ()
575
575
 
576
576
  // validateMigrationState throws an IntegrityError on dependency violations
577
577
  // to block daemon startup with an inconsistent schema.
578
- expect(() => validateMigrationState(db)).toThrow(
578
+ expect(() => validateMigrationState(db, migrationSteps)).toThrow(
579
579
  "Migration dependency violations detected",
580
580
  );
581
- expect(() => validateMigrationState(db)).toThrow(
582
- "migration_memory_items_fingerprint_scope_unique_v1",
581
+ expect(() => validateMigrationState(db, migrationSteps)).toThrow(
582
+ "migrateMemoryItemsFingerprintScopeUnique",
583
583
  );
584
584
 
585
- // Sanity-check: confirm the registry also declares this dependency, so the
586
- // violation detection is grounded in real schema intent.
587
- const saltedEntry = MIGRATION_REGISTRY.find(
588
- (e) => e.key === "migration_memory_items_scope_salted_fingerprints_v1",
589
- );
590
- expect(saltedEntry).toBeTruthy();
591
- expect(saltedEntry!.dependsOn).toContain(
592
- "migration_memory_items_fingerprint_scope_unique_v1",
585
+ // Sanity-check: confirm the steps list also declares this dependency, so
586
+ // the violation detection is grounded in real schema intent.
587
+ const saltedStep = migrationSteps.find(
588
+ (s) => typeof s !== "function" && s.name === "migrateMemoryItemsScopeSaltedFingerprints",
593
589
  );
590
+ expect(saltedStep).toBeTruthy();
591
+ if (saltedStep && typeof saltedStep !== "function") {
592
+ expect(saltedStep.dependsOn).toContain(
593
+ "migrateMemoryItemsFingerprintScopeUnique",
594
+ );
595
+ }
594
596
  });
595
597
 
596
598
  test("validateMigrationState: no checkpoints table is handled gracefully", () => {
@@ -599,7 +601,7 @@ describe("schema-drift recovery: migration handles unexpected schema state", ()
599
601
  const db = createTestDb();
600
602
  // Deliberately do NOT create memory_checkpoints.
601
603
 
602
- expect(() => validateMigrationState(db)).not.toThrow();
604
+ expect(() => validateMigrationState(db, migrationSteps)).not.toThrow();
603
605
  });
604
606
 
605
607
  test("migrateMemoryItemsFingerprintScopeUnique: old schema with UNIQUE on fingerprint is migrated", () => {
@@ -859,26 +861,40 @@ describe("schema-drift recovery: migration handles unexpected schema state", ()
859
861
  }
860
862
  });
861
863
 
862
- test("MIGRATION_REGISTRY: version numbers are strictly monotonically increasing", () => {
863
- // Registry ordering invariant: each entry's version must be strictly greater
864
- // than the previous one. A violation here would mean the ordering guarantees
865
- // documented in the migration comments cannot be relied upon.
866
- for (let i = 1; i < MIGRATION_REGISTRY.length; i++) {
867
- const prev = MIGRATION_REGISTRY[i - 1];
868
- const curr = MIGRATION_REGISTRY[i];
869
- expect(curr.version).toBeGreaterThan(prev.version);
864
+ test("migrationSteps: rollback versions are strictly monotonically increasing", () => {
865
+ // Steps ordering invariant: every rollback entry's version must be strictly
866
+ // greater than the previous rollback entry across the full steps list. A
867
+ // violation here would mean the rollback ordering guarantees documented in
868
+ // the migration comments cannot be relied upon.
869
+ const rollbackVersions: number[] = [];
870
+ for (const step of migrationSteps) {
871
+ if (typeof step === "function") continue;
872
+ if (!step.rollback) continue;
873
+ for (const entry of step.rollback) {
874
+ rollbackVersions.push(entry.version);
875
+ }
876
+ }
877
+ rollbackVersions.sort((a, b) => a - b);
878
+ for (let i = 1; i < rollbackVersions.length; i++) {
879
+ const prev = rollbackVersions[i - 1];
880
+ const curr = rollbackVersions[i];
881
+ expect(curr).toBeGreaterThan(prev);
870
882
  }
871
883
  });
872
884
 
873
- test("MIGRATION_REGISTRY: all dependsOn references point to existing registry keys", () => {
874
- // Schema drift guard: if a migration declares a dependency on a key that
875
- // doesn't exist in the registry, the dependency check in validateMigrationState
876
- // can never be satisfied. This test ensures all declared dependencies are valid.
877
- const allKeys = new Set(MIGRATION_REGISTRY.map((e) => e.key));
878
- for (const entry of MIGRATION_REGISTRY) {
879
- if (!entry.dependsOn) continue;
880
- for (const dep of entry.dependsOn) {
881
- expect(allKeys.has(dep)).toBe(true);
885
+ test("migrationSteps: all dependsOn references point to existing step names", () => {
886
+ // Schema drift guard: if a migration declares a dependency on a step name
887
+ // that doesn't exist in the steps list, the dependency check in
888
+ // validateMigrationState can never be satisfied. This test ensures all
889
+ // declared dependencies are valid.
890
+ const allNames = new Set(
891
+ migrationSteps.map((s) => (typeof s === "function" ? s.name : s.name)),
892
+ );
893
+ for (const step of migrationSteps) {
894
+ if (typeof step === "function") continue;
895
+ if (!step.dependsOn) continue;
896
+ for (const dep of step.dependsOn) {
897
+ expect(allNames.has(dep)).toBe(true);
882
898
  }
883
899
  }
884
900
  });
@@ -938,26 +954,7 @@ describe("schema-drift recovery: migration handles unexpected schema state", ()
938
954
  // ---------------------------------------------------------------------------
939
955
 
940
956
  describe("rollbackMemoryMigration", () => {
941
- // Track test entries pushed onto MIGRATION_REGISTRY so we can restore after
942
- // each test. This avoids polluting the real registry across test runs.
943
- let registrySnapshot: MigrationRegistryEntry[];
944
-
945
- function saveRegistry() {
946
- registrySnapshot = [...MIGRATION_REGISTRY];
947
- }
948
-
949
- function restoreRegistry() {
950
- MIGRATION_REGISTRY.length = 0;
951
- MIGRATION_REGISTRY.push(...registrySnapshot);
952
- }
953
-
954
- afterEach(() => {
955
- restoreRegistry();
956
- });
957
-
958
957
  test("rolls back checkpoint-tracked migrations in reverse version order", () => {
959
- saveRegistry();
960
-
961
958
  const db = createTestDb();
962
959
  const raw = getRaw(db);
963
960
  bootstrapCheckpointsTable(raw);
@@ -968,47 +965,58 @@ describe("rollbackMemoryMigration", () => {
968
965
  const now = Date.now();
969
966
 
970
967
  // Use very high version numbers to avoid colliding with real registry entries.
971
- const testEntries: MigrationRegistryEntry[] = [
968
+ const testSteps: MigrationStep[] = [
972
969
  {
973
- key: "test_rollback_v1000",
974
- stepName: "test_rollback_v1000",
975
- version: 1000,
976
- description: "test migration v1000",
977
- down: () => {
978
- downCalls.push("test_rollback_v1000");
979
- },
970
+ name: "test_rollback_v1000",
971
+ run: () => {},
972
+ rollback: [
973
+ {
974
+ version: 1000,
975
+ description: "test migration v1000",
976
+ down: () => {
977
+ downCalls.push("test_rollback_v1000");
978
+ },
979
+ },
980
+ ],
980
981
  },
981
982
  {
982
- key: "test_rollback_v1001",
983
- stepName: "test_rollback_v1001",
984
- version: 1001,
985
- description: "test migration v1001",
986
- down: () => {
987
- downCalls.push("test_rollback_v1001");
988
- },
983
+ name: "test_rollback_v1001",
984
+ run: () => {},
985
+ rollback: [
986
+ {
987
+ version: 1001,
988
+ description: "test migration v1001",
989
+ down: () => {
990
+ downCalls.push("test_rollback_v1001");
991
+ },
992
+ },
993
+ ],
989
994
  },
990
995
  {
991
- key: "test_rollback_v1002",
992
- stepName: "test_rollback_v1002",
993
- version: 1002,
994
- description: "test migration v1002",
995
- down: () => {
996
- downCalls.push("test_rollback_v1002");
997
- },
996
+ name: "test_rollback_v1002",
997
+ run: () => {},
998
+ rollback: [
999
+ {
1000
+ version: 1002,
1001
+ description: "test migration v1002",
1002
+ down: () => {
1003
+ downCalls.push("test_rollback_v1002");
1004
+ },
1005
+ },
1006
+ ],
998
1007
  },
999
1008
  ];
1000
1009
 
1001
- MIGRATION_REGISTRY.push(...testEntries);
1002
-
1003
1010
  // Simulate all three migrations as completed via their step checkpoints.
1004
- for (const entry of testEntries) {
1011
+ for (const entry of testSteps) {
1012
+ if (typeof entry === "function") continue;
1005
1013
  raw.exec(
1006
- `INSERT INTO memory_checkpoints (key, value, updated_at) VALUES ('step:${entry.stepName}', '1', ${now})`,
1014
+ `INSERT INTO memory_checkpoints (key, value, updated_at) VALUES ('step:${entry.name}', '1', ${now})`,
1007
1015
  );
1008
1016
  }
1009
1017
 
1010
1018
  // Roll back to version 1000 — should roll back v1002 and v1001 (version > 1000).
1011
- const rolledBack = rollbackMemoryMigration(db, 1000);
1019
+ const rolledBack = rollbackMemoryMigration(db, 1000, testSteps);
1012
1020
 
1013
1021
  // Verify returned keys.
1014
1022
  expect(rolledBack).toEqual(["test_rollback_v1002", "test_rollback_v1001"]);
@@ -1048,8 +1056,6 @@ describe("rollbackMemoryMigration", () => {
1048
1056
  * must clear the step checkpoint too, otherwise the next upgrade skips the
1049
1057
  * step and never restores the rolled-back schema.
1050
1058
  */
1051
- saveRegistry();
1052
-
1053
1059
  const db = createTestDb();
1054
1060
  const raw = getRaw(db);
1055
1061
  bootstrapCheckpointsTable(raw);
@@ -1063,7 +1069,7 @@ describe("rollbackMemoryMigration", () => {
1063
1069
 
1064
1070
  // GIVEN a registry-backed migration whose forward body is a named step that
1065
1071
  // creates a table. The step runner records `step:<functionName>` after a
1066
- // successful run; the registry entry's stepName maps to that key so rollback
1072
+ // successful run; the step's rollback entry maps to that key so rollback
1067
1073
  // can find and clear it.
1068
1074
  function migrateTestStepRollback(database: DrizzleDb): void {
1069
1075
  getSqliteFrom(database).exec(
@@ -1071,18 +1077,25 @@ describe("rollbackMemoryMigration", () => {
1071
1077
  );
1072
1078
  }
1073
1079
 
1074
- // AND the migration is registered with a down() that drops that table.
1075
- MIGRATION_REGISTRY.push({
1076
- key: "test_step_rollback_v4000",
1077
- stepName: "migrateTestStepRollback",
1078
- version: 4000,
1079
- description: "test step rollback",
1080
- down: (database) => {
1081
- getSqliteFrom(database).exec(
1082
- `DROP TABLE IF EXISTS test_step_rollback_data`,
1083
- );
1080
+ // The test step carries rollback metadata so rollbackMemoryMigration can
1081
+ // find it in the steps list.
1082
+ const testSteps: MigrationStep[] = [
1083
+ {
1084
+ name: "migrateTestStepRollback",
1085
+ run: migrateTestStepRollback,
1086
+ rollback: [
1087
+ {
1088
+ version: 4000,
1089
+ description: "test step rollback",
1090
+ down: (database) => {
1091
+ getSqliteFrom(database).exec(
1092
+ `DROP TABLE IF EXISTS test_step_rollback_data`,
1093
+ );
1094
+ },
1095
+ },
1096
+ ],
1084
1097
  },
1085
- });
1098
+ ];
1086
1099
 
1087
1100
  // AND the step has run once through the runner, recording the step checkpoint.
1088
1101
  await runMigrationSteps(db, [migrateTestStepRollback]);
@@ -1096,7 +1109,7 @@ describe("rollbackMemoryMigration", () => {
1096
1109
  ).toBeTruthy();
1097
1110
 
1098
1111
  // AND the migration has since been rolled back below its version.
1099
- rollbackMemoryMigration(db, 3999);
1112
+ rollbackMemoryMigration(db, 3999, testSteps);
1100
1113
  expect(hasTable()).toBe(false);
1101
1114
 
1102
1115
  // WHEN the runner executes the forward steps again on a later upgrade.
@@ -1107,8 +1120,6 @@ describe("rollbackMemoryMigration", () => {
1107
1120
  });
1108
1121
 
1109
1122
  test("handles transaction failure in down() — rolls back and preserves checkpoint", () => {
1110
- saveRegistry();
1111
-
1112
1123
  const db = createTestDb();
1113
1124
  const raw = getRaw(db);
1114
1125
  bootstrapCheckpointsTable(raw);
@@ -1126,21 +1137,27 @@ describe("rollbackMemoryMigration", () => {
1126
1137
  `INSERT INTO test_rollback_data (id, value) VALUES ('row-1', 'original')`,
1127
1138
  );
1128
1139
 
1129
- // Register a migration whose down() modifies test_rollback_data,
1130
- // but a trigger will force the modification to fail.
1131
- MIGRATION_REGISTRY.push({
1132
- key: "test_fail_down_v3000",
1133
- stepName: "test_fail_down_v3000",
1134
- version: 3000,
1135
- description: "test migration with failing down()",
1136
- down: (database) => {
1137
- const sqlite = getSqliteFrom(database);
1138
- // This UPDATE will trigger our failure trigger.
1139
- sqlite.exec(
1140
- `UPDATE test_rollback_data SET value = 'rolled-back' WHERE id = 'row-1'`,
1141
- );
1140
+ // The test step carries rollback metadata so rollbackMemoryMigration can
1141
+ // find it in the steps list.
1142
+ const testSteps: MigrationStep[] = [
1143
+ {
1144
+ name: "test_fail_down_v3000",
1145
+ run: () => {},
1146
+ rollback: [
1147
+ {
1148
+ version: 3000,
1149
+ description: "test migration with failing down()",
1150
+ down: (database) => {
1151
+ const sqlite = getSqliteFrom(database);
1152
+ // This UPDATE will trigger our failure trigger.
1153
+ sqlite.exec(
1154
+ `UPDATE test_rollback_data SET value = 'rolled-back' WHERE id = 'row-1'`,
1155
+ );
1156
+ },
1157
+ },
1158
+ ],
1142
1159
  },
1143
- });
1160
+ ];
1144
1161
 
1145
1162
  // Mark as completed (via step checkpoint).
1146
1163
  raw.exec(
@@ -1158,7 +1175,7 @@ describe("rollbackMemoryMigration", () => {
1158
1175
  // Rollback should throw because down() fails.
1159
1176
  let threw = false;
1160
1177
  try {
1161
- rollbackMemoryMigration(db, 2999);
1178
+ rollbackMemoryMigration(db, 2999, testSteps);
1162
1179
  } catch {
1163
1180
  threw = true;
1164
1181
  }
@@ -1187,8 +1204,6 @@ describe("rollbackMemoryMigration", () => {
1187
1204
  });
1188
1205
 
1189
1206
  test("down() with its own BEGIN/COMMIT succeeds without nested-transaction errors", () => {
1190
- saveRegistry();
1191
-
1192
1207
  const db = createTestDb();
1193
1208
  const raw = getRaw(db);
1194
1209
  bootstrapCheckpointsTable(raw);
@@ -1206,23 +1221,30 @@ describe("rollbackMemoryMigration", () => {
1206
1221
  `INSERT INTO test_self_txn_data (id, value) VALUES ('row-1', 'migrated')`,
1207
1222
  );
1208
1223
 
1209
- // Register a migration whose down() manages its own transaction —
1224
+ // The test step carries rollback metadata so rollbackMemoryMigration can
1225
+ // find it in the steps list. The down() manages its own transaction —
1210
1226
  // this previously caused nested-transaction errors when rollbackMemoryMigration
1211
1227
  // wrapped every down() call in BEGIN/COMMIT.
1212
- MIGRATION_REGISTRY.push({
1213
- key: "test_self_txn_down_v3500",
1214
- stepName: "test_self_txn_down_v3500",
1215
- version: 3500,
1216
- description: "test migration with self-transactional down()",
1217
- down: (database) => {
1218
- const sqlite = getSqliteFrom(database);
1219
- sqlite.exec("BEGIN");
1220
- sqlite.exec(
1221
- `UPDATE test_self_txn_data SET value = 'original' WHERE id = 'row-1'`,
1222
- );
1223
- sqlite.exec("COMMIT");
1228
+ const testSteps: MigrationStep[] = [
1229
+ {
1230
+ name: "test_self_txn_down_v3500",
1231
+ run: () => {},
1232
+ rollback: [
1233
+ {
1234
+ version: 3500,
1235
+ description: "test migration with self-transactional down()",
1236
+ down: (database) => {
1237
+ const sqlite = getSqliteFrom(database);
1238
+ sqlite.exec("BEGIN");
1239
+ sqlite.exec(
1240
+ `UPDATE test_self_txn_data SET value = 'original' WHERE id = 'row-1'`,
1241
+ );
1242
+ sqlite.exec("COMMIT");
1243
+ },
1244
+ },
1245
+ ],
1224
1246
  },
1225
- });
1247
+ ];
1226
1248
 
1227
1249
  // Mark as completed (via step checkpoint).
1228
1250
  raw.exec(
@@ -1230,7 +1252,7 @@ describe("rollbackMemoryMigration", () => {
1230
1252
  );
1231
1253
 
1232
1254
  // This should succeed — no nested transaction error.
1233
- const rolledBack = rollbackMemoryMigration(db, 3499);
1255
+ const rolledBack = rollbackMemoryMigration(db, 3499, testSteps);
1234
1256
 
1235
1257
  expect(rolledBack).toEqual(["test_self_txn_down_v3500"]);
1236
1258
 
@@ -1251,37 +1273,43 @@ describe("rollbackMemoryMigration", () => {
1251
1273
  });
1252
1274
 
1253
1275
  test("no-op when already at target version", () => {
1254
- saveRegistry();
1255
-
1256
1276
  const db = createTestDb();
1257
1277
  const raw = getRaw(db);
1258
1278
  bootstrapCheckpointsTable(raw);
1259
1279
 
1260
1280
  const now = Date.now();
1261
1281
 
1262
- // Register entries with down functions — they should NOT be called.
1282
+ // Test steps with rollback metadata — they should NOT have down() called.
1263
1283
  const downCalls: string[] = [];
1264
1284
 
1265
- MIGRATION_REGISTRY.push(
1285
+ const testSteps: MigrationStep[] = [
1266
1286
  {
1267
- key: "test_noop_v4000",
1268
- stepName: "test_noop_v4000",
1269
- version: 4000,
1270
- description: "test noop v4000",
1271
- down: () => {
1272
- downCalls.push("test_noop_v4000");
1273
- },
1287
+ name: "test_noop_v4000",
1288
+ run: () => {},
1289
+ rollback: [
1290
+ {
1291
+ version: 4000,
1292
+ description: "test noop v4000",
1293
+ down: () => {
1294
+ downCalls.push("test_noop_v4000");
1295
+ },
1296
+ },
1297
+ ],
1274
1298
  },
1275
1299
  {
1276
- key: "test_noop_v4001",
1277
- stepName: "test_noop_v4001",
1278
- version: 4001,
1279
- description: "test noop v4001",
1280
- down: () => {
1281
- downCalls.push("test_noop_v4001");
1282
- },
1300
+ name: "test_noop_v4001",
1301
+ run: () => {},
1302
+ rollback: [
1303
+ {
1304
+ version: 4001,
1305
+ description: "test noop v4001",
1306
+ down: () => {
1307
+ downCalls.push("test_noop_v4001");
1308
+ },
1309
+ },
1310
+ ],
1283
1311
  },
1284
- );
1312
+ ];
1285
1313
 
1286
1314
  // Mark both as completed (via step checkpoints).
1287
1315
  raw.exec(
@@ -1292,7 +1320,7 @@ describe("rollbackMemoryMigration", () => {
1292
1320
  );
1293
1321
 
1294
1322
  // Roll back to version >= latest applied migration — should be a no-op.
1295
- const rolledBack = rollbackMemoryMigration(db, 4001);
1323
+ const rolledBack = rollbackMemoryMigration(db, 4001, testSteps);
1296
1324
 
1297
1325
  expect(rolledBack).toEqual([]);
1298
1326
  expect(downCalls).toEqual([]);
@@ -1312,14 +1340,12 @@ describe("rollbackMemoryMigration", () => {
1312
1340
  expect(cp4001!.value).toBe("1");
1313
1341
 
1314
1342
  // Also verify with a target version greater than the latest.
1315
- const rolledBack2 = rollbackMemoryMigration(db, 9999);
1343
+ const rolledBack2 = rollbackMemoryMigration(db, 9999, testSteps);
1316
1344
  expect(rolledBack2).toEqual([]);
1317
1345
  expect(downCalls).toEqual([]);
1318
1346
  });
1319
1347
 
1320
1348
  test("respects dependency ordering on rollback (children rolled back before parents)", () => {
1321
- saveRegistry();
1322
-
1323
1349
  const db = createTestDb();
1324
1350
  const raw = getRaw(db);
1325
1351
  bootstrapCheckpointsTable(raw);
@@ -1332,27 +1358,35 @@ describe("rollbackMemoryMigration", () => {
1332
1358
  // Since the child has a higher version number, rolling back in reverse
1333
1359
  // version order means the child (v5001) is rolled back BEFORE the parent
1334
1360
  // (v5000), which is the correct dependency-safe ordering.
1335
- MIGRATION_REGISTRY.push(
1361
+ const testSteps: MigrationStep[] = [
1336
1362
  {
1337
- key: "test_parent_v5000",
1338
- stepName: "test_parent_v5000",
1339
- version: 5000,
1340
- description: "test parent migration",
1341
- down: () => {
1342
- downCalls.push("test_parent_v5000");
1343
- },
1363
+ name: "test_parent_v5000",
1364
+ run: () => {},
1365
+ rollback: [
1366
+ {
1367
+ version: 5000,
1368
+ description: "test parent migration",
1369
+ down: () => {
1370
+ downCalls.push("test_parent_v5000");
1371
+ },
1372
+ },
1373
+ ],
1344
1374
  },
1345
1375
  {
1346
- key: "test_child_v5001",
1347
- stepName: "test_child_v5001",
1348
- version: 5001,
1376
+ name: "test_child_v5001",
1377
+ run: () => {},
1349
1378
  dependsOn: ["test_parent_v5000"],
1350
- description: "test child migration depending on parent",
1351
- down: () => {
1352
- downCalls.push("test_child_v5001");
1353
- },
1379
+ rollback: [
1380
+ {
1381
+ version: 5001,
1382
+ description: "test child migration depending on parent",
1383
+ down: () => {
1384
+ downCalls.push("test_child_v5001");
1385
+ },
1386
+ },
1387
+ ],
1354
1388
  },
1355
- );
1389
+ ];
1356
1390
 
1357
1391
  // Both are completed (via step checkpoints).
1358
1392
  raw.exec(
@@ -1363,7 +1397,7 @@ describe("rollbackMemoryMigration", () => {
1363
1397
  );
1364
1398
 
1365
1399
  // Roll back to version 4999 — both should be rolled back, child first.
1366
- const rolledBack = rollbackMemoryMigration(db, 4999);
1400
+ const rolledBack = rollbackMemoryMigration(db, 4999, testSteps);
1367
1401
 
1368
1402
  expect(rolledBack).toEqual(["test_child_v5001", "test_parent_v5000"]);
1369
1403
 
@@ -421,8 +421,8 @@ describe("handleMigrationImport — JSON {url} body", () => {
421
421
  describe("handleMigrationImport — no-swap path omits newer-migration warning", () => {
422
422
  test("credentials-only bundle does not inherit live-DB migration warnings", async () => {
423
423
  // Seed the live workspace DB with a step:* checkpoint that's NOT
424
- // in the registry. validateMigrationState treats this as a "newer
425
- // version" and would otherwise push a warning into the report. With
424
+ // in the known step list. validateMigrationState treats this as a
425
+ // "newer version" and would otherwise push a warning into the report. With
426
426
  // the gate in appendNewerMigrationWarningsIfAny the warning must be
427
427
  // suppressed when the import didn't modify the workspace.
428
428
  const dbDir = join(testWorkspaceRoot, "data", "db");