@kontextmind/kxm 0.7.52 → 0.7.54

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.
@@ -58,7 +58,6 @@ export interface KxmProjectBundle {
58
58
  environments: readonly KxmResource[];
59
59
  gateRegistry?: KxmResource;
60
60
  templateProvenance?: JsonObject;
61
- migrationReceipt?: JsonObject;
62
61
  resources: readonly KxmResource[];
63
62
  configRevision: string;
64
63
  }
@@ -69,11 +68,9 @@ export interface KxmConfigOptions {
69
68
  registeredExecutors?: Iterable<string>;
70
69
  registeredToolPresets?: Iterable<string>;
71
70
  registeredHarnesses?: Iterable<string>;
72
- /** Internal: migration apply validates the staged target before the receipt exists. */
73
- allowUnreceiptedLegacyConfig?: boolean;
74
71
  }
75
72
 
76
- export type KxmInitializationMode = "create" | "migrate" | "repair" | "ready";
73
+ export type KxmInitializationMode = "create" | "legacy" | "repair" | "ready";
77
74
 
78
75
  export interface KxmInitializationPlan {
79
76
  mode: KxmInitializationMode;
@@ -161,9 +158,6 @@ export class KxmSchemaRegistry {
161
158
  readonly localBindingsValidator: ValidateFunction;
162
159
  readonly templateProvenanceValidator: ValidateFunction;
163
160
  readonly initOperationValidator: ValidateFunction;
164
- readonly migrationPlanValidator: ValidateFunction;
165
- readonly migrationDecisionValidator: ValidateFunction;
166
- readonly migrationReceiptValidator: ValidateFunction;
167
161
  readonly permissionDiffValidator: ValidateFunction;
168
162
  readonly runEventValidator: ValidateFunction;
169
163
  readonly driveReceiptValidator: ValidateFunction;
@@ -181,9 +175,6 @@ export class KxmSchemaRegistry {
181
175
  const localBindingsFile = "local-repository-bindings.schema.json";
182
176
  const templateProvenanceFile = "template-provenance.schema.json";
183
177
  const initOperationFile = "init-operation.schema.json";
184
- const migrationPlanFile = "migration-plan.schema.json";
185
- const migrationDecisionFile = "migration-decision.schema.json";
186
- const migrationReceiptFile = "migration-receipt.schema.json";
187
178
  const permissionDiffFile = "permission-diff.schema.json";
188
179
  const runEventFile = "run-event.schema.json";
189
180
  const driveReceiptFile = "drive-receipt.schema.json";
@@ -192,9 +183,6 @@ export class KxmSchemaRegistry {
192
183
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, localBindingsFile)));
193
184
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, templateProvenanceFile)));
194
185
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, initOperationFile)));
195
- this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationPlanFile)));
196
- this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationDecisionFile)));
197
- this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationReceiptFile)));
198
186
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, permissionDiffFile)));
199
187
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, runEventFile)));
200
188
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, driveReceiptFile)));
@@ -208,9 +196,6 @@ export class KxmSchemaRegistry {
208
196
  const localBindingsValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${localBindingsFile}`);
209
197
  const templateProvenanceValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${templateProvenanceFile}`);
210
198
  const initOperationValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${initOperationFile}`);
211
- const migrationPlanValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${migrationPlanFile}`);
212
- const migrationDecisionValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${migrationDecisionFile}`);
213
- const migrationReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${migrationReceiptFile}`);
214
199
  const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${permissionDiffFile}`);
215
200
  const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${runEventFile}`);
216
201
  const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/${driveReceiptFile}`);
@@ -219,9 +204,6 @@ export class KxmSchemaRegistry {
219
204
  if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
220
205
  if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
221
206
  if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
222
- if (!migrationPlanValidator) throw new Error(`schema did not compile: ${migrationPlanFile}`);
223
- if (!migrationDecisionValidator) throw new Error(`schema did not compile: ${migrationDecisionFile}`);
224
- if (!migrationReceiptValidator) throw new Error(`schema did not compile: ${migrationReceiptFile}`);
225
207
  if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
226
208
  if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
227
209
  if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
@@ -230,9 +212,6 @@ export class KxmSchemaRegistry {
230
212
  this.localBindingsValidator = localBindingsValidator;
231
213
  this.templateProvenanceValidator = templateProvenanceValidator;
232
214
  this.initOperationValidator = initOperationValidator;
233
- this.migrationPlanValidator = migrationPlanValidator;
234
- this.migrationDecisionValidator = migrationDecisionValidator;
235
- this.migrationReceiptValidator = migrationReceiptValidator;
236
215
  this.permissionDiffValidator = permissionDiffValidator;
237
216
  this.runEventValidator = runEventValidator;
238
217
  this.driveReceiptValidator = driveReceiptValidator;
@@ -263,18 +242,6 @@ export class KxmSchemaRegistry {
263
242
  return this.validateAuxiliary(value, file, "kxm.init-operation.v1", this.initOperationValidator);
264
243
  }
265
244
 
266
- validateMigrationPlan(value: JsonObject, file: string): KxmConfigIssue[] {
267
- return this.validateAuxiliary(value, file, "kxm.migration-plan.v1", this.migrationPlanValidator);
268
- }
269
-
270
- validateMigrationDecision(value: JsonObject, file: string): KxmConfigIssue[] {
271
- return this.validateAuxiliary(value, file, "kxm.migration-decision.v1", this.migrationDecisionValidator);
272
- }
273
-
274
- validateMigrationReceipt(value: JsonObject, file: string): KxmConfigIssue[] {
275
- return this.validateAuxiliary(value, file, "kxm.migration-receipt.v1", this.migrationReceiptValidator);
276
- }
277
-
278
245
  validatePermissionDiff(value: JsonObject, file: string): KxmConfigIssue[] {
279
246
  return this.validateAuxiliary(value, file, "kxm.permission-diff.v1", this.permissionDiffValidator);
280
247
  }
@@ -1279,11 +1246,17 @@ export function assertNoRegisteredGates(options: object): void {
1279
1246
  export function loadKxmProject(projectRoot: string, options: KxmConfigOptions = {}): KxmProjectBundle {
1280
1247
  assertNoRegisteredGates(options);
1281
1248
  const root = resolve(projectRoot);
1282
- let migrationReceipt: JsonObject | undefined;
1283
- if (legacyConfigFilesAt(root).length > 0 && options.allowUnreceiptedLegacyConfig !== true) {
1284
- const receiptCheck = readKxmMigrationReceipt(root, options);
1285
- if (receiptCheck.issues.length > 0) throw new KxmConfigError(receiptCheck.issues);
1286
- migrationReceipt = receiptCheck.receipt;
1249
+ const legacyPresent = legacyConfigFilesAt(root);
1250
+ if (legacyPresent.length > 0) {
1251
+ // Nothing can unlock this any more. The migration path was deleted under the
1252
+ // single-operator decision, so a tree still holding legacy JSON fails closed here
1253
+ // rather than being loadable on proof of a past conversion.
1254
+ throw new KxmConfigError(legacyPresent.map((file) => issue(
1255
+ "semantic",
1256
+ "legacy_state_unsupported",
1257
+ file,
1258
+ "legacy JSON configuration is present and this build does not migrate it: delete these files once their YAML replacements exist in .kxm/, or initialise a fresh project",
1259
+ )));
1287
1260
  }
1288
1261
  const registry = new KxmSchemaRegistry(options.schemasDir);
1289
1262
  const project = readResource(registry, root, join(root, ".kxm", "project.yaml"), ".kxm/project.yaml", "project");
@@ -1296,9 +1269,6 @@ export function loadKxmProject(projectRoot: string, options: KxmConfigOptions =
1296
1269
  earlyIssues.push(issue("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
1297
1270
  }
1298
1271
  }
1299
- if (migrationReceipt && stringValue(migrationReceipt.projectId) !== stringValue(project.value.id)) {
1300
- earlyIssues.push(issue("semantic", "migration_project_mismatch", KXM_MIGRATION_RECEIPT_PATH, "migration receipt belongs to a different project identity"));
1301
- }
1302
1272
  const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories")
1303
1273
  .map((candidate) => stringValue(objectValue(candidate)?.id))
1304
1274
  .filter((candidate): candidate is string => candidate !== undefined));
@@ -1383,6 +1353,18 @@ export function loadKxmProject(projectRoot: string, options: KxmConfigOptions =
1383
1353
  continue;
1384
1354
  }
1385
1355
  }
1356
+ // A member worktree is authoritative for its own repo.yaml/env.yaml, so the legacy
1357
+ // refusal has to cover it too — checking only the control root let a member full of
1358
+ // legacy JSON load normally. Throws before a single byte of that member is read.
1359
+ const memberLegacy = binding === root ? [] : legacyConfigFilesAt(binding);
1360
+ if (memberLegacy.length > 0) {
1361
+ throw new KxmConfigError(memberLegacy.map((file) => issue(
1362
+ "semantic",
1363
+ "legacy_state_unsupported",
1364
+ `${repositoryId}/${file}`,
1365
+ `repository ${repositoryId} holds legacy JSON configuration and this build does not migrate it: delete those files once their YAML replacements exist, or bind a clean worktree`,
1366
+ )));
1367
+ }
1386
1368
  const foldedBinding = canonicalHostPath(binding).toLocaleLowerCase("en-US");
1387
1369
  const priorBinding = seenBindings.get(foldedBinding);
1388
1370
  if (priorBinding && priorBinding !== repositoryId) {
@@ -1434,64 +1416,12 @@ export function loadKxmProject(projectRoot: string, options: KxmConfigOptions =
1434
1416
  environments,
1435
1417
  ...(gateRegistry ? { gateRegistry } : {}),
1436
1418
  ...(templateProvenance === undefined ? {} : { templateProvenance }),
1437
- ...(migrationReceipt === undefined ? {} : { migrationReceipt }),
1438
1419
  resources,
1439
1420
  configRevision: bundleRevision(resources),
1440
1421
  };
1441
1422
  }
1442
1423
 
1443
- /**
1444
- * Fully verify a migration receipt against the loaded target bundle: target
1445
- * revision and per-resource bytes. Load-time coexistence only pins the
1446
- * legacy sources; this stricter check backs `kxm migrate verify`.
1447
- */
1448
- export function verifyKxmMigrationReceiptTarget(
1449
- root: string,
1450
- receipt: JsonObject,
1451
- resources: readonly KxmResource[],
1452
- configRevision: string,
1453
- ): KxmConfigIssue[] {
1454
- const issues: KxmConfigIssue[] = [];
1455
- if (receipt.configRevision !== configRevision) {
1456
- issues.push(issue("semantic", "migration_target_changed", KXM_MIGRATION_RECEIPT_PATH, "target configuration revision no longer matches the migration receipt"));
1457
- }
1458
- const declared = new Map<string, JsonObject>();
1459
- for (const candidate of Array.isArray(receipt.resources) ? receipt.resources : []) {
1460
- if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
1461
- const record = candidate as JsonObject;
1462
- if (typeof record.path === "string") declared.set(record.path, record);
1463
- }
1464
- }
1465
- const installedPaths = new Set<string>();
1466
- for (const resource of resources) {
1467
- // Repository resources are loaded from their bound worktree paths; only
1468
- // control-tree files participate in receipt byte checks.
1469
- const relative = resource.file.startsWith(root) ? relativePortable(root, resource.file) : undefined;
1470
- if (!relative) continue;
1471
- installedPaths.add(relative);
1472
- const record = declared.get(relative);
1473
- if (!record) {
1474
- issues.push(issue("semantic", "migration_target_unreceipted", relative, "installed resource is not covered by the migration receipt"));
1475
- continue;
1476
- }
1477
- const current = hashFileRecord(root, relative);
1478
- if (!current || current.sha256 !== record.sha256 || current.bytes !== record.bytes) {
1479
- issues.push(issue("semantic", "migration_target_modified", relative, "installed resource bytes differ from the migration receipt"));
1480
- }
1481
- }
1482
- for (const path of declared.keys()) {
1483
- if (!installedPaths.has(path)) {
1484
- issues.push(issue("semantic", "migration_target_missing", path, "receipt-covered resource is not part of the validated configuration"));
1485
- }
1486
- }
1487
- return sortIssues(issues);
1488
- }
1489
1424
 
1490
- function relativePortable(root: string, absolute: string): string | undefined {
1491
- const rel = relative(root, absolute);
1492
- if (!rel || isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`)) return undefined;
1493
- return rel.split(sep).join("/");
1494
- }
1495
1425
 
1496
1426
  function legacyInputsAt(root: string): string[] {
1497
1427
  const candidates = [
@@ -1503,7 +1433,6 @@ function legacyInputsAt(root: string): string[] {
1503
1433
  return candidates.filter((candidate) => existsSync(join(root, ...candidate.split("/"))));
1504
1434
  }
1505
1435
 
1506
- export const KXM_MIGRATION_RECEIPT_PATH = ".kxm/migration-receipt.yaml";
1507
1436
  const LEGACY_CONFIG_FILES = [".kxm/config/agents.json", ".kxm/config/gates.json"] as const;
1508
1437
 
1509
1438
  /** Individual legacy configuration files (not directories, not runtime state) that a migration receipt must cover. */
@@ -1540,83 +1469,9 @@ export function legacyConfigFilesAt(root: string): string[] {
1540
1469
  return files;
1541
1470
  }
1542
1471
 
1543
- export interface KxmMigrationReceiptCheck {
1544
- receipt?: JsonObject;
1545
- issues: KxmConfigIssue[];
1546
- }
1547
1472
 
1548
- function hashFileRecord(root: string, relativePath: string): { path: string; sha256: string; bytes: number } | undefined {
1549
- const absolute = join(root, ...relativePath.split("/"));
1550
- if (!existsSync(absolute)) return undefined;
1551
- const bytes = readFileSync(absolute);
1552
- return {
1553
- path: relativePath,
1554
- sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
1555
- bytes: bytes.byteLength,
1556
- };
1557
- }
1558
1473
 
1559
- export function migrationReceiptSelfHash(receipt: JsonObject): string {
1560
- const { receiptSha256: _ignored, ...unsigned } = receipt;
1561
- return `sha256:${createHash("sha256").update(kxmCanonicalJson(unsigned as JsonObject), "utf8").digest("hex")}`;
1562
- }
1563
1474
 
1564
- /**
1565
- * Read and structurally verify a migration receipt: schema, self-hash, and
1566
- * exact coverage of the legacy configuration files still present. Does not
1567
- * compare the target configuration revision (computed by the caller).
1568
- */
1569
- export function readKxmMigrationReceipt(root: string, options: KxmConfigOptions = {}): KxmMigrationReceiptCheck {
1570
- const receiptPath = join(root, ...KXM_MIGRATION_RECEIPT_PATH.split("/"));
1571
- const legacyFiles = legacyConfigFilesAt(root);
1572
- if (!existsSync(receiptPath)) {
1573
- return {
1574
- issues: legacyFiles.map((file) => issue("semantic", "legacy_kxm_conflict", file, "legacy and KXM configuration cannot coexist before an accepted migration receipt")),
1575
- };
1576
- }
1577
- const receiptStat = lstatSync(receiptPath);
1578
- if (receiptStat.isSymbolicLink() || !receiptStat.isFile()) {
1579
- return { issues: [issue("path", "migration_receipt_invalid", KXM_MIGRATION_RECEIPT_PATH, "migration receipt must be a regular file, not a link")] };
1580
- }
1581
- let receipt: JsonObject;
1582
- try {
1583
- receipt = parseRestrictedYaml(readFileSync(receiptPath), KXM_MIGRATION_RECEIPT_PATH);
1584
- } catch (error) {
1585
- if (error instanceof KxmConfigError) return { issues: [...error.issues] };
1586
- throw error;
1587
- }
1588
- const registry = new KxmSchemaRegistry(options.schemasDir);
1589
- const schemaIssues = registry.validateMigrationReceipt(receipt, KXM_MIGRATION_RECEIPT_PATH);
1590
- if (schemaIssues.length > 0) return { issues: schemaIssues };
1591
- if (receipt.receiptSha256 !== migrationReceiptSelfHash(receipt)) {
1592
- return { issues: [issue("semantic", "migration_receipt_hash_mismatch", KXM_MIGRATION_RECEIPT_PATH, "receipt self-hash does not match its content")] };
1593
- }
1594
- const sources = (Array.isArray(receipt.sources) ? receipt.sources : [])
1595
- .map((candidate) => (candidate && typeof candidate === "object" && !Array.isArray(candidate) ? (candidate as JsonObject).path : undefined))
1596
- .filter((candidate): candidate is string => typeof candidate === "string")
1597
- .sort(compareCodeUnits);
1598
- const issues: KxmConfigIssue[] = [];
1599
- const declared = new Set(sources);
1600
- for (const file of legacyFiles) {
1601
- if (!declared.has(file)) {
1602
- issues.push(issue("semantic", "migration_source_unmigrated", file, "legacy configuration file is not covered by the migration receipt"));
1603
- continue;
1604
- }
1605
- const record = (Array.isArray(receipt.sources) ? receipt.sources : [])
1606
- .map((candidate) => (candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate as JsonObject : undefined))
1607
- .find((candidate) => candidate?.path === file);
1608
- const current = hashFileRecord(root, file);
1609
- if (!current || current.sha256 !== record?.sha256 || current.bytes !== record?.bytes) {
1610
- issues.push(issue("semantic", "migration_source_changed", file, "legacy configuration changed after the migration receipt was issued"));
1611
- }
1612
- }
1613
- for (const file of sources) {
1614
- if (!legacyFiles.includes(file)) {
1615
- issues.push(issue("semantic", "migration_source_missing", file, "receipt covers a legacy configuration file that no longer exists"));
1616
- }
1617
- }
1618
- return issues.length > 0 ? { issues } : { receipt, issues: [] };
1619
- }
1620
1475
 
1621
1476
  function discoverLegacyRoot(start: string): { root: string; inputs: string[] } | undefined {
1622
1477
  let initial = resolve(start);
@@ -1628,7 +1483,11 @@ function discoverLegacyRoot(start: string): { root: string; inputs: string[] } |
1628
1483
  return inputs.length > 0 ? { root: candidate, inputs } : undefined;
1629
1484
  }
1630
1485
 
1631
- /** Classify init without changing files. Applying create/repair/migration is a separate, explicit operation. */
1486
+ /**
1487
+ * Classify init without changing files. Applying create/repair is a separate, explicit
1488
+ * operation; a legacy-only tree is **reported and never written** — this build carries no
1489
+ * migration path, so init says what to do instead of doing it.
1490
+ */
1632
1491
  export function planKxmInitialization(start = process.cwd(), options: KxmConfigOptions = {}): KxmInitializationPlan {
1633
1492
  const inspectedFrom = resolve(start);
1634
1493
  const projectRoot = discoverKxmProjectRoot(inspectedFrom);
@@ -1638,16 +1497,38 @@ export function planKxmInitialization(start = process.cwd(), options: KxmConfigO
1638
1497
  return { mode: "ready", inspectedFrom, projectRoot, changesRequired: false, issues: [], legacyInputs: [], configRevision: bundle.configRevision };
1639
1498
  } catch (error) {
1640
1499
  if (!(error instanceof KxmConfigError)) throw error;
1500
+ const legacyIssues = error.issues.filter((candidate) => candidate.code === "legacy_state_unsupported");
1641
1501
  const legacyInputs = legacyInputsAt(projectRoot);
1642
- if (legacyInputs.length > 0) {
1643
- return { mode: "migrate", inspectedFrom, projectRoot, legacyRoot: projectRoot, changesRequired: true, issues: error.issues, legacyInputs };
1502
+ if (legacyInputs.length > 0 || legacyIssues.length > 0) {
1503
+ // Terminal for **any** legacy input, including one that lives in a bound member
1504
+ // worktree. Classifying a member's legacy JSON as `repair` let init take the
1505
+ // mutation lock and resume an interrupted create/repair journal, installing
1506
+ // resources into a tree the loader had just refused.
1507
+ return {
1508
+ mode: "legacy",
1509
+ inspectedFrom,
1510
+ projectRoot,
1511
+ legacyRoot: projectRoot,
1512
+ changesRequired: true,
1513
+ issues: [...error.issues, ...(legacyInputs.length > 0
1514
+ ? [issue("discovery", "legacy_state_unsupported", ".kxm", "legacy configuration is present and this build does not migrate it: initialise a fresh project directory and carry over the YAML definitions you want to keep")]
1515
+ : [])],
1516
+ legacyInputs: [...new Set([...legacyInputs, ...legacyIssues.map((candidate) => candidate.file)])],
1517
+ };
1644
1518
  }
1645
1519
  return { mode: "repair", inspectedFrom, projectRoot, changesRequired: true, issues: error.issues, legacyInputs: [] };
1646
1520
  }
1647
1521
  }
1648
1522
  const legacy = discoverLegacyRoot(inspectedFrom);
1649
1523
  if (legacy) {
1650
- return { mode: "migrate", inspectedFrom, legacyRoot: legacy.root, changesRequired: true, issues: [], legacyInputs: legacy.inputs };
1524
+ return {
1525
+ mode: "legacy",
1526
+ inspectedFrom,
1527
+ legacyRoot: legacy.root,
1528
+ changesRequired: true,
1529
+ issues: [issue("discovery", "legacy_state_unsupported", legacy.root, "legacy KXM/Mesh configuration discovered; this build does not migrate it — initialise a fresh project directory and copy the YAML definitions you want to keep")],
1530
+ legacyInputs: legacy.inputs,
1531
+ };
1651
1532
  }
1652
1533
  const gitRoot = discoverGitRoot(inspectedFrom);
1653
1534
  const candidateRoot = gitRoot ?? inspectedFrom;
@@ -782,74 +782,6 @@ CREATE TABLE project_controls (
782
782
  ) STRICT;
783
783
  `;
784
784
 
785
- const COORDINATOR_INTAKE_DDL = `
786
- CREATE TABLE IF NOT EXISTS coordinators (
787
- coordinator_id TEXT PRIMARY KEY,
788
- project_id TEXT NOT NULL,
789
- role TEXT NOT NULL,
790
- channel TEXT NOT NULL,
791
- ceiling_hash TEXT NOT NULL,
792
- config_revision TEXT NOT NULL,
793
- bound_at TEXT NOT NULL,
794
- schema TEXT NOT NULL,
795
- record TEXT NOT NULL
796
- ) STRICT;
797
- CREATE UNIQUE INDEX IF NOT EXISTS coordinators_slot ON coordinators(project_id, role, channel);
798
- CREATE TABLE IF NOT EXISTS intake_messages (
799
- message_id TEXT PRIMARY KEY,
800
- project_id TEXT NOT NULL,
801
- coordinator_id TEXT NOT NULL,
802
- idempotency_key TEXT NOT NULL,
803
- content_hash TEXT NOT NULL,
804
- received_at TEXT NOT NULL,
805
- dispatch_state TEXT NOT NULL CHECK (dispatch_state IN ('ready','held_paused','admitted','refused')),
806
- schema TEXT NOT NULL,
807
- record TEXT NOT NULL
808
- ) STRICT;
809
- CREATE UNIQUE INDEX IF NOT EXISTS intake_idempotency ON intake_messages(project_id, coordinator_id, idempotency_key);
810
- CREATE INDEX IF NOT EXISTS intake_dispatch ON intake_messages(project_id, dispatch_state, received_at, message_id);
811
- CREATE TABLE IF NOT EXISTS project_controls (
812
- project_id TEXT PRIMARY KEY,
813
- paused INTEGER NOT NULL CHECK (paused IN (0, 1)),
814
- reason TEXT,
815
- updated_at TEXT NOT NULL,
816
- actor TEXT NOT NULL,
817
- schema TEXT NOT NULL,
818
- record TEXT NOT NULL
819
- ) STRICT;
820
- `;
821
-
822
- const DRIVE_RECEIPTS_DDL = `
823
- CREATE TABLE IF NOT EXISTS drive_receipts (
824
- drive_id TEXT PRIMARY KEY,
825
- run_id TEXT NOT NULL,
826
- project_id TEXT NOT NULL,
827
- opened_sequence INTEGER NOT NULL,
828
- last_sequence INTEGER NOT NULL,
829
- closed_at TEXT NOT NULL,
830
- schema TEXT NOT NULL,
831
- receipt TEXT NOT NULL
832
- ) STRICT;
833
- CREATE INDEX IF NOT EXISTS drive_receipts_run ON drive_receipts(run_id);
834
- `;
835
-
836
- const EVENT_STORE_MIGRATIONS = [
837
- {
838
- fromVersion: 3,
839
- toVersion: 4,
840
- migrate(database: DatabaseSync): void {
841
- database.exec(DRIVE_RECEIPTS_DDL);
842
- },
843
- },
844
- {
845
- fromVersion: 4,
846
- toVersion: 5,
847
- migrate(database: DatabaseSync): void {
848
- database.exec(COORDINATOR_INTAKE_DDL);
849
- },
850
- },
851
- ];
852
-
853
785
  /** One persisted coordinator identity (`kxm.coordinator.v1`). */
854
786
  export interface KxmCoordinatorRow {
855
787
  coordinatorId: string;
@@ -894,7 +826,6 @@ export class KxmRunEventStore {
894
826
  schema: EVENT_STORE_SCHEMA,
895
827
  version: KXM_EVENT_STORE_SCHEMA_VERSION,
896
828
  tables: EVENT_STORE_TABLES,
897
- migrations: EVENT_STORE_MIGRATIONS,
898
829
  });
899
830
  }
900
831
 
@@ -6,7 +6,6 @@ import type { WorkflowJournalEntry, WorkflowRun } from "./workflow.ts";
6
6
  import {
7
7
  openDatabase,
8
8
  withDatabaseTransaction,
9
- type DatabaseMigrationStep,
10
9
  type DatabaseSchemaSpec,
11
10
  } from "./database.ts";
12
11
 
@@ -73,101 +72,10 @@ export const HUB_STORE_SCHEMA_V3 = `
73
72
  CREATE INDEX IF NOT EXISTS context_items_project ON context_items(project);
74
73
  `;
75
74
 
76
- export const HUB_STORE_MIGRATIONS: readonly DatabaseMigrationStep[] = Object.freeze([
77
- {
78
- fromVersion: 1,
79
- toVersion: 2,
80
- migrate: (db: DatabaseSync) => {
81
- db.exec(`
82
- CREATE TABLE IF NOT EXISTS agents (
83
- id TEXT PRIMARY KEY,
84
- record TEXT NOT NULL
85
- ) STRICT;
86
- CREATE TABLE IF NOT EXISTS messages (
87
- id TEXT PRIMARY KEY,
88
- record TEXT NOT NULL
89
- ) STRICT;
90
- CREATE TABLE IF NOT EXISTS workflow_runs (
91
- id TEXT PRIMARY KEY,
92
- definition_id TEXT NOT NULL,
93
- delivery_id TEXT NOT NULL,
94
- record TEXT NOT NULL,
95
- UNIQUE(definition_id, delivery_id)
96
- ) STRICT;
97
- CREATE TABLE IF NOT EXISTS workflow_journal (
98
- id TEXT PRIMARY KEY,
99
- run_id TEXT NOT NULL,
100
- category TEXT NOT NULL,
101
- area TEXT NOT NULL,
102
- record TEXT NOT NULL
103
- ) STRICT;
104
- CREATE INDEX IF NOT EXISTS workflow_journal_run_id ON workflow_journal(run_id);
105
- `);
106
- },
107
- },
108
- {
109
- fromVersion: 2,
110
- toVersion: 3,
111
- migrate: (db: DatabaseSync) => {
112
- db.exec(`
113
- CREATE TABLE IF NOT EXISTS agents (
114
- id TEXT PRIMARY KEY,
115
- record TEXT NOT NULL
116
- ) STRICT;
117
- CREATE TABLE IF NOT EXISTS messages (
118
- id TEXT PRIMARY KEY,
119
- record TEXT NOT NULL
120
- ) STRICT;
121
- CREATE TABLE IF NOT EXISTS workflow_runs (
122
- id TEXT PRIMARY KEY,
123
- definition_id TEXT NOT NULL,
124
- delivery_id TEXT NOT NULL,
125
- record TEXT NOT NULL,
126
- UNIQUE(definition_id, delivery_id)
127
- ) STRICT;
128
- CREATE TABLE IF NOT EXISTS workflow_journal (
129
- id TEXT PRIMARY KEY,
130
- run_id TEXT NOT NULL,
131
- category TEXT NOT NULL,
132
- area TEXT NOT NULL,
133
- record TEXT NOT NULL
134
- ) STRICT;
135
- CREATE INDEX IF NOT EXISTS workflow_journal_run_id ON workflow_journal(run_id);
136
- CREATE TABLE IF NOT EXISTS context_items (
137
- id TEXT PRIMARY KEY,
138
- project TEXT NOT NULL,
139
- kind TEXT NOT NULL,
140
- record TEXT NOT NULL
141
- ) STRICT;
142
- CREATE INDEX IF NOT EXISTS context_items_project ON context_items(project);
143
- CREATE UNIQUE INDEX IF NOT EXISTS messages_from_idempotency
144
- ON messages(
145
- json_extract(record, '$.from'),
146
- json_extract(record, '$.idempotencyKey')
147
- ) WHERE json_extract(record, '$.idempotencyKey') IS NOT NULL;
148
- CREATE INDEX IF NOT EXISTS messages_to_seq
149
- ON messages(
150
- json_extract(record, '$.to'),
151
- COALESCE(json_extract(record, '$.seq'), 0)
152
- );
153
- CREATE TABLE IF NOT EXISTS consumer_cursors (
154
- agent_id TEXT PRIMARY KEY,
155
- cursor INTEGER NOT NULL
156
- ) STRICT;
157
- CREATE TABLE IF NOT EXISTS agent_sequences (
158
- agent_id TEXT PRIMARY KEY,
159
- next_seq INTEGER NOT NULL
160
- ) STRICT;
161
- `);
162
- },
163
- },
164
- ]);
165
-
166
75
  export const HUB_STORE_SCHEMA_SPEC: DatabaseSchemaSpec = Object.freeze({
167
76
  schema: HUB_STORE_SCHEMA_V3,
168
77
  version: HUB_STORE_SCHEMA_VERSION,
169
78
  tables: HUB_STORE_TABLES,
170
- migrations: HUB_STORE_MIGRATIONS,
171
79
  });
172
80
 
173
81
  export interface StoredAgent extends AgentRecord {
package/schemas/README.md CHANGED
@@ -14,9 +14,6 @@ Runtime-local records, and JSON events/results for the planned KXM contract.
14
14
  | `template-provenance.schema.json` | `kxm.template-provenance.v1` |
15
15
  | `local-repository-bindings.schema.json` | `kxm.local-repository-bindings.v1` |
16
16
  | `init-operation.schema.json` | `kxm.init-operation.v1` |
17
- | `migration-plan.schema.json` | `kxm.migration-plan.v1` |
18
- | `migration-decision.schema.json` | `kxm.migration-decision.v1` |
19
- | `migration-receipt.schema.json` | `kxm.migration-receipt.v1` |
20
17
  | `permission-diff.schema.json` | `kxm.permission-diff.v1` |
21
18
  | `run-event.schema.json` | `kxm.run-event.v1` |
22
19
  | `coordinator.schema.json` | `kxm.coordinator.v1` |