import test from "node:test"; import assert from "node:assert/strict"; import { contentDigest, validateMigrationCatalog, selectMigrationPath, type MigrationCatalog, } from "../src/capability-model/index.js"; const before = { fields: ["name"] }, after = { fields: ["first", "last"] }; const from = contentDigest(before), to = contentDigest(after); const catalog = (): MigrationCatalog => ({ schemaVersion: 1, contracts: { [from]: before, [to]: after }, migrations: [ { id: "split-name", scopeId: "person-name", from, to, implementation: { exportId: "migrate-name", file: "src/migrate-name.ts", digest: contentDigest("implementation"), }, predecessors: [], ports: [ { name: "source", view: "old", access: ["read"], contractDigest: from }, { name: "target", view: "new", access: ["write"], contractDigest: to }, ], }, ], }); test("published migrations retain exact old contracts and explicit local bindings", () => { const value = validateMigrationCatalog(catalog(), new Set(["migrate-name"])); const selection = { scopeId: "person-name", from, to, path: ["split-name"], bindings: { source: "old-slot", target: "new-slot" }, }; const result = selectMigrationPath(value, selection); assert.equal(result[0].alreadyApplied, false); assert.equal( selectMigrationPath(value, selection, new Map([["split-name", result[0].digest]]))[0].alreadyApplied, true, ); const changed = catalog(); changed.migrations[0].implementation.digest = contentDigest("new code"); assert.throws( () => selectMigrationPath(changed, selection, new Map([["split-name", result[0].digest]])), /different code/, ); assert.throws(() => selectMigrationPath(value, { ...selection, path: [] }), /does not cover/); assert.throws(() => selectMigrationPath(value, { ...selection, bindings: {} }), /Missing local/); }); test("old migration views cannot be writable and contract hashes are verified", () => { const value = catalog(); value.migrations[0].ports[0].access = ["write"]; assert.throws(() => validateMigrationCatalog(value), /read-only/); const corrupt = catalog(); corrupt.contracts[from] = {}; assert.throws(() => validateMigrationCatalog(corrupt), /digest mismatch/); }); test("migration history rejects cycles and non-boolean compatibility promises", () => { const cyclic = catalog(); cyclic.migrations[0].predecessors = ["split-name"]; assert.throws(() => validateMigrationCatalog(cyclic), /Cyclic/); const misleading = catalog(); (misleading.migrations[0] as unknown as { preservesOldReaders: string }).preservesOldReaders = "false"; assert.throws(() => validateMigrationCatalog(misleading), /must be booleans/); });