@kontextmind/kxm 0.7.128 → 0.7.131

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.
Files changed (65) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/README.md +4 -1
  3. package/.kxm/agents/coordinator.yaml +1 -0
  4. package/.kxm/agents/critic-arch.yaml +1 -4
  5. package/.kxm/agents/critic-cli.yaml +1 -4
  6. package/.kxm/agents/implementer.yaml +1 -4
  7. package/.kxm/workflows/implement-only.yaml +1 -1
  8. package/.kxm/workflows/review-arch-only.yaml +1 -1
  9. package/.kxm/workflows/review-cli-only.yaml +1 -1
  10. package/CHANGELOG.md +42 -1
  11. package/docs/architecture/inventory.md +1 -1
  12. package/docs/concepts/architecture.md +1 -1
  13. package/docs/concepts/data-and-storage.md +1 -1
  14. package/docs/contracts/README.md +1 -1
  15. package/docs/contracts/migration.md +12 -8
  16. package/docs/contracts/routing.md +3 -3
  17. package/docs/contributing/assignment-runner.md +14 -15
  18. package/docs/contributing/development.md +22 -6
  19. package/docs/contributing/harness-routing-internals.md +8 -8
  20. package/docs/glossary.md +1 -1
  21. package/docs/guides/agent-skills.md +1 -1
  22. package/docs/operations/backup-and-restore.md +7 -7
  23. package/docs/operations/upgrade.md +2 -2
  24. package/docs/reference/cli-reference.md +37 -14
  25. package/docs/reference/config-reference.md +81 -155
  26. package/docs/reference/harness-routing.md +29 -21
  27. package/docs/reference/workflow-catalog.md +4 -4
  28. package/docs/start/first-workflow.md +1 -1
  29. package/examples/project/.kxm/agents/coordinator.yaml +1 -2
  30. package/examples/project/.kxm/agents/critic-1.yaml +1 -3
  31. package/examples/project/.kxm/agents/critic-2.yaml +1 -2
  32. package/examples/project/.kxm/agents/critic-3.yaml +2 -3
  33. package/examples/project/.kxm/agents/implementer.yaml +1 -2
  34. package/examples/project/.kxm/agents/planner.yaml +1 -2
  35. package/examples/project/.kxm/agents/reproducer.yaml +1 -2
  36. package/examples/project/.kxm/agents/reviewer.yaml +1 -2
  37. package/examples/project/.kxm/workflows/fix.yaml +0 -12
  38. package/package.json +8 -8
  39. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  40. package/plugins/kxm/dist/cli.js +2565 -465
  41. package/plugins/kxm/dist/mcp-server.js +1 -1
  42. package/plugins/kxm/dist/runtime-supervisor.js +683 -348
  43. package/plugins/kxm/dist/runtime.js +694 -358
  44. package/plugins/kxm/dist/server.js +17 -14
  45. package/plugins/kxm/package.json +1 -1
  46. package/plugins/kxm/skills/kxm/SKILL.md +1 -1
  47. package/plugins/kxm/skills/kxm-definitions/SKILL.md +8 -0
  48. package/plugins/kxm/src/cli/lanes.ts +109 -4
  49. package/plugins/kxm/src/cli/project.ts +6 -6
  50. package/plugins/kxm/src/database.ts +25 -19
  51. package/plugins/kxm/src/engine.ts +168 -163
  52. package/plugins/kxm/src/init-guide-setup.ts +75 -15
  53. package/plugins/kxm/src/mcp-server.ts +1 -1
  54. package/plugins/kxm/src/oneshot-producer.ts +3 -15
  55. package/plugins/kxm/src/project-config.ts +83 -70
  56. package/plugins/kxm/src/routes.ts +26 -18
  57. package/plugins/kxm/src/runtime-service.ts +1 -0
  58. package/plugins/kxm/src/runtime-store.ts +390 -77
  59. package/plugins/kxm/src/runtime-supervisor.ts +87 -21
  60. package/plugins/kxm/src/suggest.ts +2 -2
  61. package/plugins/kxm/src/template.ts +2 -14
  62. package/schemas/agent.schema.json +8 -0
  63. package/scripts/roster-policy.d.mts +2 -1
  64. package/scripts/roster-policy.mjs +119 -22
  65. package/scripts/run-bounded.mjs +53 -0
@@ -17362,21 +17362,9 @@ function coreTemplate(projectId, projectName, variant) {
17362
17362
  ]);
17363
17363
  if (variant === "v4-registry") {
17364
17364
  const coordinator = files.get(".kxm/agents/coordinator.yaml");
17365
- if (coordinator) {
17366
- files.set(".kxm/agents/coordinator.yaml", {
17367
- ...coordinator,
17368
- harness: "claude",
17369
- model: { provider: "anthropic", model: "fable" }
17370
- });
17371
- }
17365
+ if (coordinator) files.set(".kxm/agents/coordinator.yaml", { ...coordinator, role: "planner" });
17372
17366
  const implementer = files.get(".kxm/agents/implementer.yaml");
17373
- if (implementer) {
17374
- files.set(".kxm/agents/implementer.yaml", {
17375
- ...implementer,
17376
- harness: "grok",
17377
- model: { provider: "xai", model: "grok-4.6" }
17378
- });
17379
- }
17367
+ if (implementer) files.set(".kxm/agents/implementer.yaml", { ...implementer, role: "writer" });
17380
17368
  const workflow = files.get(".kxm/workflows/default.yaml");
17381
17369
  if (workflow) {
17382
17370
  const limits = { ...workflow.limits };
@@ -18708,6 +18696,69 @@ function readJsonObject(file) {
18708
18696
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${file} is not a JSON object`);
18709
18697
  return parsed;
18710
18698
  }
18699
+ function validateRunEvent(value, file) {
18700
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18701
+ if (!registry.runEventValidator(value)) {
18702
+ throw new KxmConfigError([issue3(
18703
+ "schema",
18704
+ "run_event_invalid",
18705
+ file,
18706
+ registry.ajv.errorsText(registry.runEventValidator.errors, { separator: "; " })
18707
+ )]);
18708
+ }
18709
+ }
18710
+ function syncEventSchemaErrors(value) {
18711
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18712
+ if (registry.syncEventValidator(value)) return void 0;
18713
+ return registry.ajv.errorsText(registry.syncEventValidator.errors, { separator: "; " });
18714
+ }
18715
+ function validateDriveReceipt(value, file) {
18716
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18717
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.drive-receipt.v1") {
18718
+ throw new KxmConfigError([issue3(
18719
+ "schema",
18720
+ "drive_receipt_invalid",
18721
+ file,
18722
+ "expected kxm.drive-receipt.v1"
18723
+ )]);
18724
+ }
18725
+ if (!registry.driveReceiptValidator(value)) {
18726
+ throw new KxmConfigError([issue3(
18727
+ "schema",
18728
+ "drive_receipt_invalid",
18729
+ file,
18730
+ registry.ajv.errorsText(registry.driveReceiptValidator.errors, { separator: "; " })
18731
+ )]);
18732
+ }
18733
+ }
18734
+ function validateCoordinator(value, file) {
18735
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18736
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.coordinator.v1") {
18737
+ throw new KxmConfigError([issue3("schema", "coordinator_invalid", file, "expected kxm.coordinator.v1")]);
18738
+ }
18739
+ if (!registry.coordinatorValidator(value)) {
18740
+ throw new KxmConfigError([issue3(
18741
+ "schema",
18742
+ "coordinator_invalid",
18743
+ file,
18744
+ registry.ajv.errorsText(registry.coordinatorValidator.errors, { separator: "; " })
18745
+ )]);
18746
+ }
18747
+ }
18748
+ function validateIntakeMessage(value, file) {
18749
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18750
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.intake-message.v1") {
18751
+ throw new KxmConfigError([issue3("schema", "intake_message_invalid", file, "expected kxm.intake-message.v1")]);
18752
+ }
18753
+ if (!registry.intakeMessageValidator(value)) {
18754
+ throw new KxmConfigError([issue3(
18755
+ "schema",
18756
+ "intake_message_invalid",
18757
+ file,
18758
+ registry.ajv.errorsText(registry.intakeMessageValidator.errors, { separator: "; " })
18759
+ )]);
18760
+ }
18761
+ }
18711
18762
  function schemaIssue(file, error) {
18712
18763
  const location = error.instancePath || "/";
18713
18764
  const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
@@ -18847,7 +18898,7 @@ function readTemplateProvenance(registry, root) {
18847
18898
  }
18848
18899
  return value;
18849
18900
  }
18850
- function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId) {
18901
+ function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId, collected) {
18851
18902
  const resources = /* @__PURE__ */ new Map();
18852
18903
  if (!existsSync6(directory)) return resources;
18853
18904
  const stat = lstatSync(directory);
@@ -18886,7 +18937,10 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
18886
18937
  else throw error;
18887
18938
  }
18888
18939
  }
18889
- if (issues.length > 0) throw new KxmConfigError(issues);
18940
+ if (issues.length > 0) {
18941
+ if (collected) collected.push(...issues);
18942
+ else throw new KxmConfigError(issues);
18943
+ }
18890
18944
  return resources;
18891
18945
  }
18892
18946
  function valuesOf(object2, field) {
@@ -19146,6 +19200,9 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
19146
19200
  for (const repositoryId of Object.keys(objectValue(step.repositories) ?? {})) {
19147
19201
  if (!repositories.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
19148
19202
  }
19203
+ if ((kind === "agent" || kind === "moa") && step.model !== void 0) {
19204
+ issues.push(issue3("semantic", "producer_route_unsupported", file, `${stepId} model is not honored; remove model from the step`));
19205
+ }
19149
19206
  selectorCandidates(step.model, models, file, `${stepId}.model`, issues);
19150
19207
  const assignment = objectValue(step.assignments);
19151
19208
  const declaredAgents = assignment ? names(assignment.allowedAgents) : [];
@@ -19328,7 +19385,7 @@ function validateModelReferences(models, issues) {
19328
19385
  };
19329
19386
  for (const id of models.keys()) walk(id, []);
19330
19387
  }
19331
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, writerRole) {
19388
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, roles = /* @__PURE__ */ new Map()) {
19332
19389
  const issues = [];
19333
19390
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
19334
19391
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -19369,84 +19426,84 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
19369
19426
  const defaultHarness = stringValue(project.value.defaultHarness) ?? DEFAULT_HARNESS;
19370
19427
  if (!harnesses.has(defaultHarness)) issues.push(issue3("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
19371
19428
  for (const agent of agents.values()) {
19429
+ if (agent.value.model !== void 0 || agent.value.harness !== void 0) {
19430
+ issues.push(issue3("semantic", "retired_agent_routing_fields", agent.logicalPath, "routing resolves from role; remove model and harness"));
19431
+ }
19372
19432
  validateToolPolicy(agent, objectValue(agent.value.tools), "tools", issues);
19373
19433
  const executor = stringValue(agent.value.executor);
19374
19434
  if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
19375
- const harness = stringValue(agent.value.harness) ?? defaultHarness;
19376
- if (!harnesses.has(harness)) issues.push(issue3("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
19377
19435
  const preset = stringValue(objectValue(agent.value.tools)?.preset);
19378
19436
  if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
19379
19437
  for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
19380
19438
  if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
19381
19439
  }
19382
- const candidates = selectorCandidates(agent.value.model, models, agent.logicalPath, "model", issues);
19383
- const declaredHarness = stringValue(agent.value.harness);
19384
- if (declaredHarness && harnesses.has(declaredHarness)) {
19385
- for (const candidate of candidates) {
19386
- const candidateProvider = stringValue(candidate.value.provider);
19387
- const candidateModel = stringValue(candidate.value.model);
19388
- const validation = validateHarnessModelPair(declaredHarness, { provider: candidateProvider, model: candidateModel });
19389
- if (!validation.valid) {
19390
- issues.push(issue3("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
19391
- }
19392
- }
19393
- }
19394
19440
  }
19395
19441
  validateModelReferences(models, issues);
19396
19442
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
19397
19443
  if (!workflows.has(defaultWorkflow)) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
19398
19444
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
19445
+ if (projectRoot && existsSync6(join6(projectRoot, ".kxm", "roster.yaml"))) {
19446
+ issues.push(issue3("semantic", "retired_roster_file", ".kxm/roster.yaml", "create the role and model files and delete roster.yaml"));
19447
+ }
19448
+ if (projectRoot && existsSync6(join6(projectRoot, ".kxm", "roles"))) {
19449
+ issues.push(...developerRolePolicyIssues(projectRoot));
19450
+ }
19399
19451
  if (projectRoot) {
19400
- const writerRolePath = join6(projectRoot, ".kxm", "roles", "writer.yaml");
19401
- const implementerAgent = agents.get("implementer") ?? agents.get("writer");
19402
- if ((writerRole !== void 0 || existsSync6(writerRolePath)) && implementerAgent) {
19452
+ issues.push(...rosterModelIssues(projectRoot, roles));
19453
+ issues.push(...originHashIssues(projectRoot, models));
19454
+ }
19455
+ return sortIssues3(issues);
19456
+ }
19457
+ function rosterModelIssues(projectRoot, roles) {
19458
+ const issues = [];
19459
+ for (const role of roles.values()) {
19460
+ for (const entry of valuesOf(role.value, "roster")) {
19461
+ const route = stringValue(objectValue(entry)?.route);
19462
+ if (!route) continue;
19463
+ const logical = `.kxm/models/${route}.yaml`;
19464
+ const file = join6(projectRoot, ".kxm", "models", `${route}.yaml`);
19465
+ let harness;
19403
19466
  try {
19404
- const rawRole = parseRestrictedYaml2(writerRole ?? readFileSync5(writerRolePath, "utf8"));
19405
- const roleObj = objectValue(rawRole);
19406
- const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
19407
- const enabledRosterModels = rosterEntries.flatMap((entry) => {
19408
- const direct = stringValue(entry.model);
19409
- const route = stringValue(entry.route);
19410
- const named = direct ? [direct] : [];
19411
- if (route) {
19412
- const modelFile = join6(projectRoot, ".kxm", "models", `${route}.yaml`);
19413
- if (existsSync6(modelFile)) {
19414
- try {
19415
- const modelDoc = objectValue(parseRestrictedYaml2(readFileSync5(modelFile, "utf8"), modelFile));
19416
- const model = modelDoc ? stringValue(modelDoc.model) : void 0;
19417
- const vendor = modelDoc ? stringValue(modelDoc.vendor) : void 0;
19418
- const harness = modelDoc ? stringValue(modelDoc.harness) : void 0;
19419
- if (model) named.push(model);
19420
- if (vendor && model) named.push(`${vendor}/${model}`);
19421
- if (harness && model) named.push(`${harness}/${model}`);
19422
- } catch {
19423
- }
19424
- }
19425
- }
19426
- return named;
19427
- });
19428
- const agentModelObj = objectValue(implementerAgent.value.model);
19429
- const agentModelStr = stringValue(implementerAgent.value.model);
19430
- const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
19431
- const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
19432
- const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
19433
- if (enabledRosterModels.length > 0 && canonicalAgentModel) {
19434
- const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
19435
- if (!matches) {
19436
- issues.push(issue3("semantic", "role_roster_conflicts_with_agent", ".kxm/roles/writer.yaml", `role roster in .kxm/roles/writer.yaml does not include agent model ${canonicalAgentModel} from ${implementerAgent.logicalPath}`));
19437
- }
19438
- }
19439
- } catch (error) {
19440
- if (error instanceof KxmConfigError) {
19441
- issues.push(...error.issues);
19442
- }
19467
+ if (existsSync6(file)) harness = stringValue(parseRestrictedYaml2(readFileSync5(file, "utf8"), logical).harness);
19468
+ } catch {
19469
+ harness = void 0;
19470
+ }
19471
+ if (!harness) {
19472
+ issues.push(issue3("semantic", "roster_model_unreadable", role.logicalPath, `roster route ${route} does not resolve to a readable ${logical} carrying harness`));
19443
19473
  }
19444
19474
  }
19445
19475
  }
19446
- if (projectRoot && existsSync6(join6(projectRoot, ".kxm", "roster.yaml"))) {
19447
- issues.push(...developerRolePolicyIssues(projectRoot));
19476
+ return issues;
19477
+ }
19478
+ function originHashIssues(projectRoot, models) {
19479
+ const issues = [];
19480
+ for (const model of models.values()) {
19481
+ const origin = objectValue(model.value.origin);
19482
+ const source = origin ? stringValue(origin.source) : void 0;
19483
+ const digest = origin ? stringValue(origin.sha256) : void 0;
19484
+ if (!source || !digest) continue;
19485
+ const file = resolve3(projectRoot, source);
19486
+ const escaped = relative(projectRoot, file);
19487
+ if (isAbsolute2(source) || escaped.startsWith("..") || isAbsolute2(escaped)) {
19488
+ issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
19489
+ continue;
19490
+ }
19491
+ let actual;
19492
+ try {
19493
+ actual = createHash7("sha256").update(readFileSync5(file, "utf8"), "utf8").digest("hex");
19494
+ } catch (error) {
19495
+ const code = error.code;
19496
+ if (code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
19497
+ issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
19498
+ continue;
19499
+ }
19500
+ throw error;
19501
+ }
19502
+ if (actual !== digest) {
19503
+ issues.push(issue3("semantic", "origin_hash_mismatch", model.logicalPath, "origin evidence hash does not match supplied bytes"));
19504
+ }
19448
19505
  }
19449
- return sortIssues3(issues);
19506
+ return issues;
19450
19507
  }
19451
19508
  function developerCeilings() {
19452
19509
  const script = join6(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
@@ -19475,6 +19532,7 @@ function developerCeilings() {
19475
19532
  }
19476
19533
  }
19477
19534
  function developerRolePolicyIssues(projectRoot) {
19535
+ if (resolve3(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
19478
19536
  const rolesDir = join6(projectRoot, ".kxm", "roles");
19479
19537
  const modelsDir = join6(projectRoot, ".kxm", "models");
19480
19538
  const roles = {};
@@ -19495,6 +19553,7 @@ function developerRolePolicyIssues(projectRoot) {
19495
19553
  parseIssues.push(...error.issues);
19496
19554
  continue;
19497
19555
  }
19556
+ throw error;
19498
19557
  }
19499
19558
  }
19500
19559
  };
@@ -19509,7 +19568,7 @@ function developerRolePolicyIssues(projectRoot) {
19509
19568
  }
19510
19569
  const ceilings = developerCeilings();
19511
19570
  if (!ceilings.ok) {
19512
- return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/roster.yaml", `developer ceilings could not be loaded: ${ceilings.detail}`)];
19571
+ return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
19513
19572
  }
19514
19573
  const result = validatePolicyDraft({ models, roles, evidence }, {
19515
19574
  ceilings: ceilings.ROUTES,
@@ -19617,7 +19676,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
19617
19676
  }
19618
19677
  if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
19619
19678
  const agents = listNamedResources(registry, root, join6(root, ".kxm", "agents"), ".kxm/agents", "agent");
19620
- const models = listNamedResources(registry, root, join6(root, ".kxm", "models"), ".kxm/models", "model");
19679
+ const modelIssues = [];
19680
+ const models = listNamedResources(registry, root, join6(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
19621
19681
  const roles = listNamedResources(registry, root, join6(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
19622
19682
  if (writerRole !== void 0) {
19623
19683
  const logicalPath = ".kxm/roles/writer.yaml";
@@ -19744,8 +19804,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
19744
19804
  }
19745
19805
  }
19746
19806
  }
19747
- if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
19748
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
19807
+ if (loadIssues.length > 0) throw new KxmConfigError([...loadIssues, ...modelIssues]);
19808
+ const issues = [...modelIssues, ...validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, roles)];
19749
19809
  if (issues.length > 0) throw new KxmConfigError(issues);
19750
19810
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...roles.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath));
19751
19811
  return {
@@ -19853,7 +19913,7 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
19853
19913
  }
19854
19914
  return { mode: "create", inspectedFrom, projectRoot: candidateRoot, changesRequired: true, issues: [], legacyInputs: [] };
19855
19915
  }
19856
- var import__, KxmConfigError, DEFAULT_SCHEMA_DIR, RESOURCE_SCHEMA, IDENTIFIER2, WINDOWS_RESERVED, BUILTIN_EXECUTORS, BUILTIN_TOOL_PRESETS, SECRET_VALUE_PATTERNS, KxmSchemaRegistry, GATE_STEP_OUTCOMES, LEGACY_CONFIG_FILES;
19916
+ var import__, KxmConfigError, DEFAULT_SCHEMA_DIR, RESOURCE_SCHEMA, IDENTIFIER2, WINDOWS_RESERVED, BUILTIN_EXECUTORS, BUILTIN_TOOL_PRESETS, SECRET_VALUE_PATTERNS, KxmSchemaRegistry, cachedRunEventRegistry, GATE_STEP_OUTCOMES, LEGACY_CONFIG_FILES;
19857
19917
  var init_project_config = __esm({
19858
19918
  "plugins/kxm/src/project-config.ts"() {
19859
19919
  "use strict";
@@ -20418,12 +20478,169 @@ var init_runtime_paths = __esm({
20418
20478
 
20419
20479
  // plugins/kxm/src/sync-transform.ts
20420
20480
  import { createHash as createHash10 } from "node:crypto";
20421
- var KXM_DEFAULT_SYNC_POLICY, KXM_DEFAULT_SYNC_POLICY_REVISION, SCALAR_FIELDS, CARRIED_FIELDS;
20481
+ function isRecord(value) {
20482
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
20483
+ }
20484
+ function pickScalars(source, keys, redactor) {
20485
+ if (!isRecord(source)) return void 0;
20486
+ const result = {};
20487
+ for (const key of keys) {
20488
+ const value = source[key];
20489
+ if (typeof value === "number" || typeof value === "boolean") {
20490
+ result[key] = value;
20491
+ } else if (typeof value === "string") {
20492
+ if (redactor) {
20493
+ const scrubbed = redactor.scrub(value);
20494
+ if (scrubbed.text.length > 0) result[key] = scrubbed.text.slice(0, 200);
20495
+ } else {
20496
+ result[key] = value;
20497
+ }
20498
+ }
20499
+ }
20500
+ return Object.keys(result).length > 0 ? result : void 0;
20501
+ }
20502
+ function buildPayload(source, builder) {
20503
+ for (const key of SCALAR_FIELDS) {
20504
+ const value = source[key];
20505
+ if (typeof value === "string" || typeof value === "number") builder.set(key, value);
20506
+ }
20507
+ builder.set("displayTitle", builder.text(source.displayTitle, 200));
20508
+ builder.set("summary", builder.text(source.summary, 1e3));
20509
+ if (source.reason !== void 0) {
20510
+ builder.set("reason", builder.text(source.reason, 1e3) ?? REDACTED);
20511
+ }
20512
+ builder.set("executor", builder.record(source.executor, ["id", "kind", "harness"], {
20513
+ executorVersion: 128,
20514
+ harnessVersion: 128,
20515
+ helperGeneration: 256
20516
+ }));
20517
+ const model = builder.record(source.model, ["profile", "provider"], { model: 200, thinking: 64 });
20518
+ if (model && isRecord(source.model) && Array.isArray(source.model.tags)) {
20519
+ model.tags = source.model.tags.filter((tag) => typeof tag === "string").slice(0, 32);
20520
+ }
20521
+ builder.set("model", model);
20522
+ builder.set("timing", builder.record(source.timing, ["startedAt", "finishedAt", "durationMs", "queueMs", "agentMs"]));
20523
+ builder.set("usage", builder.record(source.usage, [
20524
+ "inputTokens",
20525
+ "outputTokens",
20526
+ "cacheReadTokens",
20527
+ "cacheWriteTokens",
20528
+ "cost",
20529
+ "currency",
20530
+ "costKind"
20531
+ ]));
20532
+ const refs = (value, keys) => Array.isArray(value) ? value.slice(0, MAX_REFS).map((entry) => pickScalars(entry, keys)).filter((entry) => entry !== void 0) : void 0;
20533
+ builder.set("evidenceRefs", refs(source.evidenceRefs, REF_KEYS));
20534
+ builder.set("suppliedRefs", refs(source.suppliedRefs, REF_KEYS));
20535
+ builder.set("artifacts", refs(source.artifacts, ARTIFACT_KEYS));
20536
+ if (isRecord(source.receipt)) {
20537
+ const receipt = builder.record(source.receipt, ["provider", "kind", "hash", "observedAt"], { id: 512 });
20538
+ if (receipt) builder.set("receipts", [receipt]);
20539
+ if (source.receipt.url !== void 0) builder.omitted.add("receipt.url");
20540
+ }
20541
+ builder.set("error", builder.record(source.error, ["class", "retryable"], { component: 128 }));
20542
+ if (isRecord(source.effect)) {
20543
+ const effect = pickScalars(source.effect, ["id", "idempotencyKeyHash"], builder.redactor) ?? {};
20544
+ const policy = pickScalars(source.effect.policy, ["class", "sharedMutable", "idempotencyKey", "receiptQuery"], builder.redactor);
20545
+ if (policy) effect.policy = policy;
20546
+ builder.set("effect", effect);
20547
+ }
20548
+ builder.set("lease", pickScalars(source.lease, LEASE_KEYS, builder.redactor));
20549
+ builder.set("actor", builder.record(source.actor, ["kind"], { id: 200 }));
20550
+ builder.set("resolvedBy", builder.record(source.resolvedBy, ["kind"], { id: 200 }));
20551
+ if (isRecord(source.counts)) {
20552
+ const counts = Object.fromEntries(Object.entries(source.counts).filter((entry) => Number.isInteger(entry[1]) && entry[1] >= 0).slice(0, 32));
20553
+ builder.set("counts", counts);
20554
+ }
20555
+ }
20556
+ function envelope(event, policyRevision, payload, fieldsOmitted, valuesReplaced) {
20557
+ return {
20558
+ schema: KXM_SYNC_EVENT_SCHEMA,
20559
+ sourceEventId: event.eventId,
20560
+ eventType: event.eventType,
20561
+ projectId: event.projectId,
20562
+ runId: event.runId,
20563
+ homeRuntimeId: event.homeRuntimeId,
20564
+ sequence: event.sequence,
20565
+ occurredAt: event.occurredAt,
20566
+ configRevision: event.configRevision,
20567
+ memoryRevision: event.memoryRevision,
20568
+ executorPolicyRevision: event.executorPolicyRevision,
20569
+ toolPolicyRevision: event.toolPolicyRevision,
20570
+ syncPolicyRevision: policyRevision,
20571
+ redaction: {
20572
+ version: KXM_SYNC_REDACTOR_VERSION,
20573
+ fieldsOmitted: [...new Set(fieldsOmitted)].sort().slice(0, 128),
20574
+ valuesReplaced: Math.min(valuesReplaced, 1e5)
20575
+ },
20576
+ payload
20577
+ };
20578
+ }
20579
+ function deriveKxmSyncEvent(event, options = {}) {
20580
+ const redactor = options.redactor ?? new KxmSyncRedactor();
20581
+ const policyRevision = options.policyRevision ?? KXM_DEFAULT_SYNC_POLICY_REVISION;
20582
+ const source = isRecord(event.payload) ? event.payload : {};
20583
+ const builder = new SyncPayloadBuilder(redactor);
20584
+ buildPayload(source, builder);
20585
+ const omitted = new Set(builder.omitted);
20586
+ for (const key of Object.keys(source)) if (!CARRIED_FIELDS.has(key)) omitted.add(key);
20587
+ if (event.protectedPayload !== void 0) omitted.add("protectedPayload");
20588
+ const payload = Object.keys(builder.payload).length > 0 ? builder.payload : { counts: {} };
20589
+ const full = envelope(event, policyRevision, payload, omitted, builder.replaced);
20590
+ if (syncEventSchemaErrors(full) === void 0) return full;
20591
+ const degradedPayload = { degraded: true };
20592
+ for (const key of CONTROL_FIELDS) {
20593
+ const value = builder.payload[key];
20594
+ const restoreNested = (built, orig) => {
20595
+ if (!isRecord(orig)) return built !== void 0 ? built : "[redacted]";
20596
+ const result = {};
20597
+ const builtRecord = isRecord(built) ? built : {};
20598
+ for (const [k2, v2] of Object.entries(orig)) {
20599
+ const builtValue = builtRecord[k2];
20600
+ if (builtValue === void 0 || typeof builtValue === "string" && builtValue.trim().length === 0) {
20601
+ result[k2] = isRecord(v2) ? restoreNested(void 0, v2) : "[redacted]";
20602
+ } else {
20603
+ result[k2] = builtValue;
20604
+ }
20605
+ }
20606
+ return result;
20607
+ };
20608
+ if (value === void 0) {
20609
+ const sourceValue = source[key];
20610
+ if (sourceValue !== void 0) {
20611
+ degradedPayload[key] = restoreNested(void 0, sourceValue);
20612
+ }
20613
+ } else if (typeof value === "string" && value.trim().length === 0) {
20614
+ degradedPayload[key] = "[redacted]";
20615
+ } else if (isRecord(value)) {
20616
+ degradedPayload[key] = restoreNested(value, source[key]);
20617
+ } else {
20618
+ degradedPayload[key] = value;
20619
+ }
20620
+ }
20621
+ const degradedOmitted = new Set(omitted);
20622
+ for (const key of Object.keys(builder.payload)) if (!(key in degradedPayload)) degradedOmitted.add(key);
20623
+ const degraded = envelope(event, policyRevision, degradedPayload, degradedOmitted, builder.replaced);
20624
+ const errors = syncEventSchemaErrors(degraded);
20625
+ if (errors === void 0) return degraded;
20626
+ throw new KxmConfigError([{
20627
+ phase: "schema",
20628
+ code: "sync_event_invalid",
20629
+ file: `${event.runId}#${event.sequence}`,
20630
+ message: `event cannot be derived into kxm.sync-event.v1: ${errors}`
20631
+ }]);
20632
+ }
20633
+ function kxmSyncEventBytes(event) {
20634
+ return kxmCanonicalJson(event);
20635
+ }
20636
+ var KXM_SYNC_EVENT_SCHEMA, KXM_SYNC_REDACTOR_VERSION, KXM_DEFAULT_SYNC_POLICY, KXM_DEFAULT_SYNC_POLICY_REVISION, MIN_REGISTERED_SECRET_CHARS, REDACTED, PATH_REDACTED, CREDENTIAL_SHAPES, URL_USERINFO, ABSOLUTE_PATHS, CONTROL_CHARACTERS, KxmSyncRedactor, SyncPayloadBuilder, SCALAR_FIELDS, REF_KEYS, ARTIFACT_KEYS, LEASE_KEYS, MAX_REFS, CARRIED_FIELDS, CONTROL_FIELDS;
20422
20637
  var init_sync_transform = __esm({
20423
20638
  "plugins/kxm/src/sync-transform.ts"() {
20424
20639
  "use strict";
20425
20640
  init_project_config();
20426
20641
  init_redact();
20642
+ KXM_SYNC_EVENT_SCHEMA = "kxm.sync-event.v1";
20643
+ KXM_SYNC_REDACTOR_VERSION = "redactor-v1";
20427
20644
  KXM_DEFAULT_SYNC_POLICY = Object.freeze({
20428
20645
  prompts: "title-only",
20429
20646
  results: "bounded-summary",
@@ -20435,6 +20652,98 @@ var init_sync_transform = __esm({
20435
20652
  environmentValues: false
20436
20653
  });
20437
20654
  KXM_DEFAULT_SYNC_POLICY_REVISION = `sha256:${createHash10("sha256").update(kxmCanonicalJson(KXM_DEFAULT_SYNC_POLICY), "utf8").digest("hex")}`;
20655
+ MIN_REGISTERED_SECRET_CHARS = 4;
20656
+ REDACTED = "[redacted]";
20657
+ PATH_REDACTED = "[path]";
20658
+ CREDENTIAL_SHAPES = [
20659
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g,
20660
+ /\bssh-(?:rsa|ed25519|dss|ecdsa-[a-z0-9-]+) AAAA[0-9A-Za-z+/=]+/g,
20661
+ /\bAKIA[0-9A-Z]{16}\b/g,
20662
+ /\bgh[opsu]_[A-Za-z0-9]{20,}\b/g,
20663
+ /\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*\S+/gi
20664
+ ];
20665
+ URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi;
20666
+ ABSOLUTE_PATHS = [
20667
+ /\\\\[^\s\\"'`]+\\[^\s"'`]*/g,
20668
+ // UNC
20669
+ /\b[A-Za-z]:[\\/][^\s"'`]*/g,
20670
+ // Windows drive
20671
+ /(?<![\w.~:/-])~[\\/][^\s"'`]*/g,
20672
+ // home
20673
+ /(?<![\w.~:/-])\/(?:[\w.@%+-]+\/)*[\w.@%+-]+\/?/g
20674
+ // POSIX
20675
+ ];
20676
+ CONTROL_CHARACTERS = /[\u0000-\u001F\u007F]/g;
20677
+ KxmSyncRedactor = class {
20678
+ values = /* @__PURE__ */ new Set();
20679
+ register(value) {
20680
+ const trimmed = value.trim();
20681
+ if (trimmed.length >= MIN_REGISTERED_SECRET_CHARS) this.values.add(trimmed);
20682
+ }
20683
+ get size() {
20684
+ return this.values.size;
20685
+ }
20686
+ /** Scrub one free-text value; `replaced` counts every substitution. */
20687
+ scrub(input) {
20688
+ let replaced = 0;
20689
+ let text = input;
20690
+ for (const secret of [...this.values].sort((left, right) => right.length - left.length)) {
20691
+ const parts = text.split(secret);
20692
+ if (parts.length > 1) {
20693
+ replaced += parts.length - 1;
20694
+ text = parts.join(REDACTED);
20695
+ }
20696
+ }
20697
+ const substitute = (pattern, replacement) => {
20698
+ text = text.replace(pattern, (...args) => {
20699
+ replaced += 1;
20700
+ return typeof replacement === "string" ? replacement : replacement(...args);
20701
+ });
20702
+ };
20703
+ for (const pattern of CREDENTIAL_SHAPES) substitute(pattern, REDACTED);
20704
+ substitute(URL_USERINFO, (_match, scheme) => `${scheme}${REDACTED}@`);
20705
+ const shaped = redactSecrets(text);
20706
+ if (shaped !== text) {
20707
+ replaced += shaped.split(REDACTED).length - text.split(REDACTED).length;
20708
+ text = shaped;
20709
+ }
20710
+ for (const pattern of ABSOLUTE_PATHS) substitute(pattern, PATH_REDACTED);
20711
+ text = text.replace(CONTROL_CHARACTERS, " ").replace(/\s+/g, " ").trim();
20712
+ return { text, replaced };
20713
+ }
20714
+ };
20715
+ SyncPayloadBuilder = class {
20716
+ payload = {};
20717
+ omitted = /* @__PURE__ */ new Set();
20718
+ replaced = 0;
20719
+ redactor;
20720
+ constructor(redactor) {
20721
+ this.redactor = redactor;
20722
+ }
20723
+ /** Bounded, scrubbed free text; empty after scrubbing means absent. */
20724
+ text(value, max) {
20725
+ if (typeof value !== "string") return void 0;
20726
+ const { text, replaced } = this.redactor.scrub(value);
20727
+ this.replaced += replaced;
20728
+ if (text.length === 0) return void 0;
20729
+ return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
20730
+ }
20731
+ /** A nested record rebuilt key by key, with the named keys scrubbed as text. */
20732
+ record(source, keys, textKeys = {}) {
20733
+ const scalars = pickScalars(source, keys.filter((key) => !(key in textKeys)));
20734
+ const result = { ...scalars ?? {} };
20735
+ if (isRecord(source)) {
20736
+ for (const [key, max] of Object.entries(textKeys)) {
20737
+ const value = this.text(source[key], max);
20738
+ if (value !== void 0) result[key] = value;
20739
+ }
20740
+ }
20741
+ return Object.keys(result).length > 0 ? result : void 0;
20742
+ }
20743
+ set(key, value) {
20744
+ if (value !== void 0) this.payload[key] = value;
20745
+ }
20746
+ };
20438
20747
  SCALAR_FIELDS = [
20439
20748
  "workflowId",
20440
20749
  "promptHash",
@@ -20452,6 +20761,10 @@ var init_sync_transform = __esm({
20452
20761
  "scopeEpoch",
20453
20762
  "resolutionAction"
20454
20763
  ];
20764
+ REF_KEYS = ["id", "kind", "hash", "status", "producerId"];
20765
+ ARTIFACT_KEYS = ["id", "kind", "hash", "size", "classification", "availability"];
20766
+ LEASE_KEYS = ["id", "fencingToken", "acquiredAt", "expiresAt", "operation"];
20767
+ MAX_REFS = 64;
20455
20768
  CARRIED_FIELDS = /* @__PURE__ */ new Set([
20456
20769
  ...SCALAR_FIELDS,
20457
20770
  "displayTitle",
@@ -20472,6 +20785,7 @@ var init_sync_transform = __esm({
20472
20785
  "suppliedRefs",
20473
20786
  "counts"
20474
20787
  ]);
20788
+ CONTROL_FIELDS = ["effect", "lease", "actor", "resolvedBy", "resolutionAction", "reason", "suppliedRefs", "outcome"];
20475
20789
  }
20476
20790
  });
20477
20791
 
@@ -20551,22 +20865,25 @@ function ensureWalJournalMode(database, file, description, timeoutMs = 5e3) {
20551
20865
  Atomics.wait(sleeper, 0, 0, 10);
20552
20866
  }
20553
20867
  }
20868
+ function assertDatabaseFile(file, description) {
20869
+ checkedParent(file, description);
20870
+ const stat = lstatSync3(file, { throwIfNoEntry: false });
20871
+ if (stat) {
20872
+ if (stat.isSymbolicLink() || !stat.isFile()) {
20873
+ throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
20874
+ }
20875
+ }
20876
+ for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
20877
+ const info = lstatSync3(sidecar, { throwIfNoEntry: false });
20878
+ if (info?.isSymbolicLink()) {
20879
+ throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
20880
+ }
20881
+ }
20882
+ }
20554
20883
  function openDatabase(file, description, spec) {
20555
20884
  const isMemory = file === ":memory:";
20556
20885
  if (!isMemory) {
20557
- checkedParent(file, description);
20558
- const stat = lstatSync3(file, { throwIfNoEntry: false });
20559
- if (stat) {
20560
- if (stat.isSymbolicLink() || !stat.isFile()) {
20561
- throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
20562
- }
20563
- }
20564
- for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
20565
- const info = lstatSync3(sidecar, { throwIfNoEntry: false });
20566
- if (info?.isSymbolicLink()) {
20567
- throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
20568
- }
20569
- }
20886
+ assertDatabaseFile(file, description);
20570
20887
  }
20571
20888
  const database = new DatabaseSync(file);
20572
20889
  let transaction = false;
@@ -20595,7 +20912,7 @@ function openDatabase(file, description, spec) {
20595
20912
  throw databaseError(
20596
20913
  "runtime_schema_outdated",
20597
20914
  file,
20598
- `${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file to start fresh and let its owning process recreate it (\`kxm hub start\` for hub state, the Runtime for registry/event stores); \`kxm init\` is project-only and rebuilds no database \u2014 upgrading old state in place is deliberately unsupported`
20915
+ `${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file to start fresh and let its owning process recreate it (\`kxm hub start\` for hub state, the Runtime for event stores). The Runtime registry copy from schema 1 to 2 is the one exception. \`kxm init\` is project-only and rebuilds no database. Upgrading any other old state in place is deliberately unsupported`
20599
20916
  );
20600
20917
  }
20601
20918
  if (spec.tables) {
@@ -20615,6 +20932,84 @@ function openDatabase(file, description, spec) {
20615
20932
  throw error;
20616
20933
  }
20617
20934
  }
20935
+ function monotonicNowMs() {
20936
+ return Number(process.hrtime.bigint() / 1000000n);
20937
+ }
20938
+ function finiteNow(clock, label) {
20939
+ const now = clock();
20940
+ if (typeof now !== "number" || !Number.isFinite(now)) {
20941
+ throw databaseError(
20942
+ "runtime_transaction_clock_invalid",
20943
+ "transaction",
20944
+ `${label} must return a finite monotonic number; got ${String(now)}`
20945
+ );
20946
+ }
20947
+ return now;
20948
+ }
20949
+ function isTransactionContention(error) {
20950
+ const carrier = error;
20951
+ for (const value of [carrier?.errcode, carrier?.errCode, carrier?.errno]) {
20952
+ if (typeof value === "number" && Number.isInteger(value)) return CONTENTION_PRIMARY_CODES.includes(value & 255);
20953
+ }
20954
+ for (const value of [carrier?.code, carrier?.name]) {
20955
+ if (typeof value === "string" && SQLITE_RESULT_NAMES.test(value)) {
20956
+ return CONTENTION_SYMBOLIC_NAMES.test(value);
20957
+ }
20958
+ }
20959
+ return CONTENTION_MESSAGES.test(error instanceof Error ? error.message : String(error));
20960
+ }
20961
+ function withDatabaseTransaction(database, work, mode = "IMMEDIATE", clock = monotonicNowMs) {
20962
+ if (activeTransactions.has(database)) {
20963
+ throw databaseError("runtime_transaction_nested", "transaction", "nested transactions are not allowed");
20964
+ }
20965
+ if (mode !== "DEFERRED") {
20966
+ const deadlines = transactionThrottles.get(database);
20967
+ const until = deadlines?.get(clock);
20968
+ if (until !== void 0) {
20969
+ const remaining = until - finiteNow(clock, "the transaction clock");
20970
+ if (remaining > 0) {
20971
+ throw databaseError(
20972
+ "runtime_transaction_busy",
20973
+ "transaction",
20974
+ `a previous BEGIN was blocked on this database; retry deferred ${String(remaining)}ms`
20975
+ );
20976
+ }
20977
+ deadlines?.delete(clock);
20978
+ }
20979
+ }
20980
+ try {
20981
+ database.exec(`BEGIN ${mode}`);
20982
+ } catch (error) {
20983
+ if (!isTransactionContention(error)) throw error;
20984
+ const now = finiteNow(clock, "the transaction clock");
20985
+ let deadlines = transactionThrottles.get(database);
20986
+ if (deadlines === void 0) {
20987
+ deadlines = /* @__PURE__ */ new WeakMap();
20988
+ transactionThrottles.set(database, deadlines);
20989
+ }
20990
+ deadlines.set(clock, now + TRANSACTION_BUSY_BACKOFF_MS);
20991
+ throw databaseError(
20992
+ "runtime_transaction_busy",
20993
+ "transaction",
20994
+ `BEGIN ${mode} blocked by another transaction: ${error instanceof Error ? error.message : String(error)}`
20995
+ );
20996
+ }
20997
+ activeTransactions.add(database);
20998
+ if (mode !== "DEFERRED") transactionThrottles.get(database)?.delete(clock);
20999
+ try {
21000
+ const result = work();
21001
+ database.exec("COMMIT");
21002
+ return result;
21003
+ } catch (error) {
21004
+ try {
21005
+ database.exec("ROLLBACK");
21006
+ } catch {
21007
+ }
21008
+ throw error;
21009
+ } finally {
21010
+ activeTransactions.delete(database);
21011
+ }
21012
+ }
20618
21013
  function checkpointWal(database, mode = "TRUNCATE") {
20619
21014
  const row = database.prepare(`PRAGMA wal_checkpoint(${mode})`).get();
20620
21015
  return {
@@ -21083,16 +21478,23 @@ function applyRestorePlan(plan) {
21083
21478
  restoredStores
21084
21479
  };
21085
21480
  }
21086
- var KXM_BACKUP_CEILINGS;
21481
+ var activeTransactions, TRANSACTION_BUSY_BACKOFF_MS, transactionThrottles, CONTENTION_PRIMARY_CODES, CONTENTION_SYMBOLIC_NAMES, SQLITE_RESULT_NAMES, CONTENTION_MESSAGES, KXM_BACKUP_CEILINGS;
21087
21482
  var init_database = __esm({
21088
21483
  "plugins/kxm/src/database.ts"() {
21089
21484
  "use strict";
21090
21485
  init_sqlite();
21091
21486
  init_project_config();
21092
21487
  init_runtime_paths();
21488
+ activeTransactions = /* @__PURE__ */ new WeakSet();
21489
+ TRANSACTION_BUSY_BACKOFF_MS = 1e3;
21490
+ transactionThrottles = /* @__PURE__ */ new WeakMap();
21491
+ CONTENTION_PRIMARY_CODES = [5, 6, 15];
21492
+ CONTENTION_SYMBOLIC_NAMES = /^SQLITE_(?:BUSY|LOCKED|PROTOCOL)(?:_[A-Z0-9]+)?$/;
21493
+ SQLITE_RESULT_NAMES = /^SQLITE_[A-Z][A-Z0-9_]*$/;
21494
+ CONTENTION_MESSAGES = /^(?:database is locked|database table is locked|locking protocol|SQLITE_BUSY|SQLITE_LOCKED|SQLITE_PROTOCOL)(?:$|[\s.:])/i;
21093
21495
  KXM_BACKUP_CEILINGS = {
21094
21496
  "hub-store": 5,
21095
- registry: 1,
21497
+ registry: 2,
21096
21498
  "binding-store": 1,
21097
21499
  events: 7
21098
21500
  };
@@ -21100,8 +21502,10 @@ var init_database = __esm({
21100
21502
  });
21101
21503
 
21102
21504
  // plugins/kxm/src/runtime-store.ts
21103
- import { existsSync as existsSync10, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
21104
- import { dirname as dirname7, resolve as resolve8 } from "node:path";
21505
+ import { spawnSync as spawnSync4 } from "node:child_process";
21506
+ import { createHash as createHash12, randomUUID as randomUUID4 } from "node:crypto";
21507
+ import { existsSync as existsSync10, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync4, statSync, writeFileSync as writeFileSync6 } from "node:fs";
21508
+ import { dirname as dirname7, isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
21105
21509
  function runtimeIssue(phase, code, file, message) {
21106
21510
  return { phase, code, file, message };
21107
21511
  }
@@ -21119,6 +21523,106 @@ function projectRuntimeOwnsRun(projectRoot, runId, env) {
21119
21523
  database.close();
21120
21524
  }
21121
21525
  }
21526
+ function registrationFromRow(row) {
21527
+ return {
21528
+ projectId: row.project_id,
21529
+ projectRoot: row.project_root,
21530
+ projectKey: row.project_key,
21531
+ homeRuntimeId: row.home_runtime_id,
21532
+ ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
21533
+ ...row.lane_of !== null ? { laneOf: row.lane_of } : {},
21534
+ registeredAt: row.registered_at
21535
+ };
21536
+ }
21537
+ function controlRootDirectoryExists(projectRoot) {
21538
+ const stat = statSync(projectRoot, { throwIfNoEntry: false });
21539
+ return Boolean(stat?.isDirectory());
21540
+ }
21541
+ function gitEnv() {
21542
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
21543
+ }
21544
+ function gitCommonDirectory(projectRoot) {
21545
+ const result = spawnSync4("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
21546
+ encoding: "utf8",
21547
+ env: gitEnv(),
21548
+ timeout: 3e4,
21549
+ windowsHide: true
21550
+ });
21551
+ if (result.status !== 0) return void 0;
21552
+ const text = (result.stdout ?? "").trim();
21553
+ if (!text) return void 0;
21554
+ const absolute = isAbsolute5(text) ? text : resolve8(projectRoot, text);
21555
+ let canonical2;
21556
+ try {
21557
+ canonical2 = realpathSync4.native(absolute);
21558
+ } catch {
21559
+ canonical2 = resolve8(absolute);
21560
+ }
21561
+ return process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
21562
+ }
21563
+ function isPrimaryWorktree(projectRoot, commonDir) {
21564
+ if (commonDir === void 0) return false;
21565
+ const dotGit = resolve8(projectRoot, ".git");
21566
+ let canonical2;
21567
+ try {
21568
+ canonical2 = realpathSync4.native(dotGit);
21569
+ } catch {
21570
+ canonical2 = resolve8(dotGit);
21571
+ }
21572
+ if (process.platform === "win32") canonical2 = canonical2.toLocaleLowerCase("en-US");
21573
+ return canonical2 === commonDir;
21574
+ }
21575
+ function migrateRegistryLanes(file) {
21576
+ const stat = lstatSync4(file, { throwIfNoEntry: false });
21577
+ if (!stat) return;
21578
+ assertDatabaseFile(file, "runtime registry");
21579
+ const database = new DatabaseSync(file);
21580
+ let transaction = false;
21581
+ try {
21582
+ database.exec("PRAGMA busy_timeout = 5000");
21583
+ const current = database.prepare("PRAGMA user_version").get();
21584
+ if ((current?.user_version ?? 0) !== 1) return;
21585
+ database.exec("BEGIN IMMEDIATE");
21586
+ transaction = true;
21587
+ const locked = database.prepare("PRAGMA user_version").get();
21588
+ if ((locked?.user_version ?? 0) !== 1) {
21589
+ database.exec("ROLLBACK");
21590
+ transaction = false;
21591
+ return;
21592
+ }
21593
+ if (!tableColumns(database, "projects").includes("lane_of")) {
21594
+ database.exec(`
21595
+ CREATE TABLE projects_v2 (
21596
+ project_key TEXT PRIMARY KEY,
21597
+ project_id TEXT NOT NULL,
21598
+ project_root TEXT NOT NULL,
21599
+ home_runtime_id TEXT NOT NULL,
21600
+ config_revision TEXT,
21601
+ registered_at TEXT NOT NULL,
21602
+ lane_of TEXT
21603
+ ) STRICT;
21604
+ INSERT INTO projects_v2 (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
21605
+ SELECT project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, NULL FROM projects;
21606
+ DROP TABLE projects;
21607
+ ALTER TABLE projects_v2 RENAME TO projects;
21608
+ CREATE INDEX projects_by_project_id ON projects (project_id);
21609
+ `);
21610
+ }
21611
+ database.exec("PRAGMA user_version = 2");
21612
+ database.exec("COMMIT");
21613
+ transaction = false;
21614
+ } catch (error) {
21615
+ if (transaction) {
21616
+ try {
21617
+ database.exec("ROLLBACK");
21618
+ } catch {
21619
+ }
21620
+ }
21621
+ throw error;
21622
+ } finally {
21623
+ database.close();
21624
+ }
21625
+ }
21122
21626
  function readKxmSupervisorRecord(database) {
21123
21627
  const row = database.prepare("SELECT runtime_id, pid, port, token_hash, started_at, heartbeat_at, state FROM supervisor WHERE singleton_id = 1").get();
21124
21628
  return row ? {
@@ -21131,7 +21635,326 @@ function readKxmSupervisorRecord(database) {
21131
21635
  state: row.state
21132
21636
  } : void 0;
21133
21637
  }
21134
- var KXM_REGISTRY_SCHEMA_VERSION, REGISTRY_TABLES, REGISTRY_SCHEMA, KxmRuntimeRegistry, DRIVE_RECEIPT_MAX_BYTES, EVENT_STORE_TABLES, KXM_EVENT_STORE_TABLE_NAMES;
21638
+ function coordinatorFromRow(row) {
21639
+ const parsed = JSON.parse(row.record);
21640
+ if (row.schema !== "kxm.coordinator.v1") {
21641
+ throw runtimeError("coordinator_record_divergent", row.coordinator_id, `unexpected coordinator schema ${row.schema}`);
21642
+ }
21643
+ validateCoordinator(parsed, row.coordinator_id);
21644
+ if (parsed.coordinatorId !== row.coordinator_id || parsed.projectId !== row.project_id || parsed.role !== row.role || parsed.channel !== row.channel || parsed.ceilingHash !== row.ceiling_hash || parsed.configRevision !== row.config_revision || parsed.boundAt !== row.bound_at) {
21645
+ throw runtimeError("coordinator_record_divergent", row.coordinator_id, "coordinator columns do not match the persisted record");
21646
+ }
21647
+ return {
21648
+ coordinatorId: row.coordinator_id,
21649
+ projectId: row.project_id,
21650
+ role: row.role,
21651
+ channel: row.channel,
21652
+ ceilingHash: row.ceiling_hash,
21653
+ configRevision: row.config_revision,
21654
+ boundAt: row.bound_at,
21655
+ record: row.record
21656
+ };
21657
+ }
21658
+ function intakeFromRow(row) {
21659
+ const parsed = JSON.parse(row.record);
21660
+ if (row.schema !== "kxm.intake-message.v1") {
21661
+ throw runtimeError("intake_record_divergent", row.message_id, `unexpected intake schema ${row.schema}`);
21662
+ }
21663
+ validateIntakeMessage(parsed, row.message_id);
21664
+ if (parsed.messageId !== row.message_id || parsed.projectId !== row.project_id || parsed.coordinatorId !== row.coordinator_id || parsed.idempotencyKey !== row.idempotency_key || parsed.contentHash !== row.content_hash || parsed.receivedAt !== row.received_at || parsed.dispatch?.state !== row.dispatch_state) {
21665
+ throw runtimeError("intake_record_divergent", row.message_id, "intake columns do not match the persisted record");
21666
+ }
21667
+ return {
21668
+ messageId: row.message_id,
21669
+ projectId: row.project_id,
21670
+ coordinatorId: row.coordinator_id,
21671
+ idempotencyKey: row.idempotency_key,
21672
+ contentHash: row.content_hash,
21673
+ receivedAt: row.received_at,
21674
+ dispatchState: row.dispatch_state,
21675
+ record: row.record
21676
+ };
21677
+ }
21678
+ function controlFromRow(row) {
21679
+ const paused = row.paused === 1;
21680
+ const expected = kxmCanonicalJson({
21681
+ schema: "kxm.project-control.v1",
21682
+ projectId: row.project_id,
21683
+ paused,
21684
+ ...row.reason !== null ? { reason: row.reason } : {},
21685
+ updatedAt: row.updated_at,
21686
+ actor: row.actor
21687
+ });
21688
+ if (row.record !== expected) {
21689
+ throw runtimeError("project_control_divergent", row.project_id, "project control record does not match its columns");
21690
+ }
21691
+ return {
21692
+ projectId: row.project_id,
21693
+ paused,
21694
+ ...row.reason !== null ? { reason: row.reason } : {},
21695
+ updatedAt: row.updated_at,
21696
+ actor: row.actor,
21697
+ record: row.record
21698
+ };
21699
+ }
21700
+ function parseDriveReceipt(raw) {
21701
+ const parsed = JSON.parse(raw);
21702
+ validateDriveReceipt(parsed, "drive-receipt");
21703
+ return parsed;
21704
+ }
21705
+ function outboxFromRow(row) {
21706
+ return {
21707
+ seq: row.seq,
21708
+ runId: row.run_id,
21709
+ sequence: row.sequence,
21710
+ syncEvent: row.sync_event,
21711
+ attemptCount: row.attempt_count,
21712
+ ...row.attempted_at !== null ? { attemptedAt: row.attempted_at } : {},
21713
+ ...row.acked_at !== null ? { ackedAt: row.acked_at } : {},
21714
+ ...row.refused_code !== null ? { refusedCode: row.refused_code } : {},
21715
+ ...row.refused_at !== null ? { refusedAt: row.refused_at } : {}
21716
+ };
21717
+ }
21718
+ function runFromRow(row) {
21719
+ if (typeof row.memory_revision !== "string" || row.memory_revision.length === 0) {
21720
+ throw runtimeError("runtime_schema_shape_invalid", row.run_id, "memory_revision is required");
21721
+ }
21722
+ return {
21723
+ runId: row.run_id,
21724
+ projectId: row.project_id,
21725
+ homeRuntimeId: row.home_runtime_id,
21726
+ workflowId: row.workflow_id,
21727
+ promptSha256: row.prompt_sha256,
21728
+ status: row.status,
21729
+ configRevision: row.config_revision,
21730
+ memoryRevision: row.memory_revision,
21731
+ executorPolicyRevision: row.executor_policy_revision,
21732
+ toolPolicyRevision: row.tool_policy_revision,
21733
+ createdAt: row.created_at,
21734
+ updatedAt: row.updated_at
21735
+ };
21736
+ }
21737
+ function gateRowContentHash(table, row) {
21738
+ const copy = { ...row };
21739
+ delete copy.contentHash;
21740
+ void table;
21741
+ return `sha256:${createHash12("sha256").update(kxmCanonicalJson(copy), "utf8").digest("hex")}`;
21742
+ }
21743
+ function assertClosedGateObservation(kind, row, id = "observation") {
21744
+ if (row.completeness === "incomplete") return;
21745
+ if (row.completeness === "no-start") {
21746
+ const legitimateStartFailure = (row.errorClass === "validation" || row.errorClass === "spawn-error") && row.stopCause === "none";
21747
+ const requestedStop = row.stopCause === "cancel" && row.errorClass === null;
21748
+ if (row.spawned !== 0 || row.pid !== null || row.exitCode !== null || row.signal !== null || row.exitObserved !== 0 || row.closeObserved !== 0 || row.signalsAttempted !== "none" || row.stdoutSha256 !== null || row.stdoutBytes !== null || row.stdoutComplete !== null || row.stderrSha256 !== null || row.stderrBytes !== null || row.stderrComplete !== null || row.checkedCount !== null || row.failedCount !== null || !legitimateStartFailure && !requestedStop) {
21749
+ throw gateRowInvalid(id, "no-start observation forbids PID/exit/close/stream/check facts and requires a legitimate no-start cause");
21750
+ }
21751
+ return;
21752
+ }
21753
+ if (kind === "command") {
21754
+ if (row.spawned !== 1 || typeof row.pid !== "number" || typeof row.exitCode !== "number" || row.signal !== null || row.exitObserved !== 1 || row.closeObserved !== 1 || row.stopCause !== "none" || row.signalsAttempted !== "none" || row.stdoutComplete !== 1 || row.stderrComplete !== 1 || typeof row.stdoutSha256 !== "string" || typeof row.stderrSha256 !== "string" || typeof row.stdoutBytes !== "number" || typeof row.stderrBytes !== "number" || row.checkedCount !== null || row.failedCount !== null) {
21755
+ throw gateRowInvalid(id, "complete command observation requires spawn, numeric exit, no signal, exit/close, complete streams, and stopCause none");
21756
+ }
21757
+ return;
21758
+ }
21759
+ if (row.spawned !== 0 || row.pid !== null || row.exitCode !== null || row.signal !== null || row.exitObserved !== 0 || row.closeObserved !== 0 || row.stopCause !== "none" || row.signalsAttempted !== "none" || row.stdoutSha256 !== null || row.stdoutBytes !== null || row.stdoutComplete !== null || row.stderrSha256 !== null || row.stderrBytes !== null || row.stderrComplete !== null || typeof row.checkedCount !== "number" || typeof row.failedCount !== "number" || row.checkedCount < 0 || row.failedCount < 0 || row.failedCount > row.checkedCount) {
21760
+ throw gateRowInvalid(id, "complete artifacts observation requires consistent counts and no command facts");
21761
+ }
21762
+ }
21763
+ function gateRowInvalid(file, message) {
21764
+ return runtimeError("gate_row_invalid", file, message);
21765
+ }
21766
+ function assertExactRowKeys(row, keys, id) {
21767
+ const actual = Object.keys(row);
21768
+ if (actual.length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(row, key))) {
21769
+ throw gateRowInvalid(id, "gate row does not have the exact key set");
21770
+ }
21771
+ for (const key of actual) {
21772
+ if (FORBIDDEN_TEXT_KEYS.has(key)) throw gateRowInvalid(id, "gate row must not carry raw stdout/stderr/error text");
21773
+ }
21774
+ }
21775
+ function assertId(value, prefix, id, label) {
21776
+ if (typeof value !== "string" || !value.startsWith(prefix) || !OPAQUE.test(value)) {
21777
+ throw gateRowInvalid(id, `${label} is not a valid ${prefix} opaque id`);
21778
+ }
21779
+ }
21780
+ function assertSha(value, id, label) {
21781
+ if (typeof value !== "string" || !SHA2562.test(value)) throw gateRowInvalid(id, `${label} is not a sha256 hash`);
21782
+ }
21783
+ function assertFlag(value, id, label) {
21784
+ if (value !== 0 && value !== 1) throw gateRowInvalid(id, `${label} must be 0 or 1`);
21785
+ }
21786
+ function assertNullInt(value, id, label) {
21787
+ if (value !== null && (typeof value !== "number" || !Number.isInteger(value))) {
21788
+ throw gateRowInvalid(id, `${label} must be an integer or null`);
21789
+ }
21790
+ }
21791
+ function assertGateAttemptRow(row) {
21792
+ assertExactRowKeys(row, GATE_ATTEMPT_KEYS, row.attemptId);
21793
+ assertId(row.attemptId, "att_", row.attemptId, "attemptId");
21794
+ assertId(row.effectId, "eff_", row.attemptId, "effectId");
21795
+ assertId(row.intentEventId, "evt_", row.attemptId, "intentEventId");
21796
+ assertSha(row.gateDefinitionHash, row.attemptId, "gateDefinitionHash");
21797
+ assertSha(row.registryHash, row.attemptId, "registryHash");
21798
+ assertSha(row.runPlanHash, row.attemptId, "runPlanHash");
21799
+ if (row.producerId !== "kxm-gate") throw gateRowInvalid(row.attemptId, "producerId must be kxm-gate");
21800
+ if (row.gateKind !== "command" && row.gateKind !== "artifacts-exist") {
21801
+ throw gateRowInvalid(row.attemptId, "gateKind is invalid");
21802
+ }
21803
+ if (row.expect !== "pass" && row.expect !== "fail") throw gateRowInvalid(row.attemptId, "expect is invalid");
21804
+ if (row.gateKind === "artifacts-exist" && row.expect === "fail") {
21805
+ throw gateRowInvalid(row.attemptId, "artifacts-exist cannot expect fail");
21806
+ }
21807
+ if (typeof row.controlProjectKey !== "string" || row.controlProjectKey.length === 0) {
21808
+ throw gateRowInvalid(row.attemptId, "controlProjectKey is required");
21809
+ }
21810
+ if (gateRowContentHash("gate_attempts", row) !== row.contentHash) {
21811
+ throw gateRowInvalid(row.attemptId, "contentHash does not match the canonical row");
21812
+ }
21813
+ }
21814
+ function assertGateObservationRow(row) {
21815
+ assertExactRowKeys(row, GATE_OBSERVATION_KEYS, row.observationId);
21816
+ assertId(row.observationId, "obs_", row.observationId, "observationId");
21817
+ assertId(row.attemptId, "att_", row.observationId, "attemptId");
21818
+ assertId(row.effectId, "eff_", row.observationId, "effectId");
21819
+ assertId(row.recordedEventId, "evt_", row.observationId, "recordedEventId");
21820
+ if (!["complete", "incomplete", "no-start"].includes(row.completeness)) {
21821
+ throw gateRowInvalid(row.observationId, "completeness is invalid");
21822
+ }
21823
+ assertFlag(row.spawned, row.observationId, "spawned");
21824
+ assertFlag(row.exitObserved, row.observationId, "exitObserved");
21825
+ assertFlag(row.closeObserved, row.observationId, "closeObserved");
21826
+ assertNullInt(row.pid, row.observationId, "pid");
21827
+ assertNullInt(row.exitCode, row.observationId, "exitCode");
21828
+ assertNullInt(row.stdoutBytes, row.observationId, "stdoutBytes");
21829
+ assertNullInt(row.stderrBytes, row.observationId, "stderrBytes");
21830
+ assertNullInt(row.checkedCount, row.observationId, "checkedCount");
21831
+ assertNullInt(row.failedCount, row.observationId, "failedCount");
21832
+ if (typeof row.elapsedMs !== "number" || !Number.isInteger(row.elapsedMs) || row.elapsedMs < 0) {
21833
+ throw gateRowInvalid(row.observationId, "elapsedMs is invalid");
21834
+ }
21835
+ if (row.stdoutSha256 !== null) assertSha(row.stdoutSha256, row.observationId, "stdoutSha256");
21836
+ if (row.stderrSha256 !== null) assertSha(row.stderrSha256, row.observationId, "stderrSha256");
21837
+ if (row.stdoutComplete !== null && row.stdoutComplete !== 0 && row.stdoutComplete !== 1) {
21838
+ throw gateRowInvalid(row.observationId, "stdoutComplete must be 0, 1, or null");
21839
+ }
21840
+ if (row.stderrComplete !== null && row.stderrComplete !== 0 && row.stderrComplete !== 1) {
21841
+ throw gateRowInvalid(row.observationId, "stderrComplete must be 0, 1, or null");
21842
+ }
21843
+ if (gateRowContentHash("gate_observations", row) !== row.contentHash) {
21844
+ throw gateRowInvalid(row.observationId, "contentHash does not match the canonical row");
21845
+ }
21846
+ }
21847
+ function assertGateEvidenceRow(row) {
21848
+ assertExactRowKeys(row, GATE_EVIDENCE_KEYS, row.evidenceId);
21849
+ assertId(row.evidenceId, "gev_", row.evidenceId, "evidenceId");
21850
+ assertId(row.attemptId, "att_", row.evidenceId, "attemptId");
21851
+ assertId(row.observationId, "obs_", row.evidenceId, "observationId");
21852
+ assertId(row.effectId, "eff_", row.evidenceId, "effectId");
21853
+ assertId(row.settledEventId, "evt_", row.evidenceId, "settledEventId");
21854
+ if (row.kind !== "gate") throw gateRowInvalid(row.evidenceId, "kind must be gate");
21855
+ if (!["passed", "implementation-failure", "repro-missing"].includes(row.outcome)) {
21856
+ throw gateRowInvalid(row.evidenceId, "outcome is invalid");
21857
+ }
21858
+ if (gateRowContentHash("gate_evidence", row) !== row.contentHash) {
21859
+ throw gateRowInvalid(row.evidenceId, "contentHash does not match the canonical row");
21860
+ }
21861
+ }
21862
+ function assertSharedIdentity(row, attempt, id) {
21863
+ if (row.runId !== attempt.runId || row.projectId !== attempt.projectId || row.homeRuntimeId !== attempt.homeRuntimeId || row.stepId !== attempt.stepId || row.stepAttempt !== attempt.stepAttempt || row.assignmentId !== attempt.assignmentId || row.effectId !== attempt.effectId) {
21864
+ throw gateRowInvalid(id, "row identity does not match the gate attempt");
21865
+ }
21866
+ }
21867
+ function gateAttemptFromSql(row) {
21868
+ return {
21869
+ attemptId: row.attempt_id,
21870
+ runId: row.run_id,
21871
+ projectId: row.project_id,
21872
+ homeRuntimeId: row.home_runtime_id,
21873
+ stepId: row.step_id,
21874
+ stepAttempt: row.step_attempt,
21875
+ assignmentId: row.assignment_id,
21876
+ effectId: row.effect_id,
21877
+ gateId: row.gate_id,
21878
+ gateKind: row.gate_kind,
21879
+ expect: row.expect,
21880
+ gateDefinitionHash: row.gate_definition_hash,
21881
+ registryHash: row.registry_hash,
21882
+ runPlanHash: row.run_plan_hash,
21883
+ controlProjectKey: row.control_project_key,
21884
+ producerId: row.producer_id,
21885
+ intentEventId: row.intent_event_id,
21886
+ contentHash: row.content_hash
21887
+ };
21888
+ }
21889
+ function gateObservationFromSql(row) {
21890
+ return {
21891
+ observationId: row.observation_id,
21892
+ attemptId: row.attempt_id,
21893
+ runId: row.run_id,
21894
+ projectId: row.project_id,
21895
+ homeRuntimeId: row.home_runtime_id,
21896
+ stepId: row.step_id,
21897
+ stepAttempt: row.step_attempt,
21898
+ assignmentId: row.assignment_id,
21899
+ effectId: row.effect_id,
21900
+ completeness: row.completeness,
21901
+ spawned: row.spawned === 1 ? 1 : 0,
21902
+ pid: row.pid,
21903
+ exitCode: row.exit_code,
21904
+ signal: row.signal,
21905
+ exitObserved: row.exit_observed === 1 ? 1 : 0,
21906
+ closeObserved: row.close_observed === 1 ? 1 : 0,
21907
+ stopCause: row.stop_cause,
21908
+ signalsAttempted: row.signals_attempted,
21909
+ errorClass: row.error_class,
21910
+ stdoutSha256: row.stdout_sha256,
21911
+ stdoutBytes: row.stdout_bytes,
21912
+ stdoutComplete: row.stdout_complete === null ? null : row.stdout_complete === 1 ? 1 : 0,
21913
+ stderrSha256: row.stderr_sha256,
21914
+ stderrBytes: row.stderr_bytes,
21915
+ stderrComplete: row.stderr_complete === null ? null : row.stderr_complete === 1 ? 1 : 0,
21916
+ checkedCount: row.checked_count,
21917
+ failedCount: row.failed_count,
21918
+ elapsedMs: row.elapsed_ms,
21919
+ startedAt: row.started_at,
21920
+ finishedAt: row.finished_at,
21921
+ recordedEventId: row.recorded_event_id,
21922
+ contentHash: row.content_hash
21923
+ };
21924
+ }
21925
+ function gateEvidenceFromSql(row) {
21926
+ return {
21927
+ evidenceId: row.evidence_id,
21928
+ attemptId: row.attempt_id,
21929
+ observationId: row.observation_id,
21930
+ runId: row.run_id,
21931
+ projectId: row.project_id,
21932
+ homeRuntimeId: row.home_runtime_id,
21933
+ stepId: row.step_id,
21934
+ stepAttempt: row.step_attempt,
21935
+ assignmentId: row.assignment_id,
21936
+ effectId: row.effect_id,
21937
+ evidenceKey: row.evidence_key,
21938
+ kind: row.kind,
21939
+ expect: row.expect,
21940
+ outcome: row.outcome,
21941
+ settledEventId: row.settled_event_id,
21942
+ contentHash: row.content_hash
21943
+ };
21944
+ }
21945
+ function capabilityFromRow(row) {
21946
+ return row ? {
21947
+ attemptId: row.attempt_id,
21948
+ runId: row.run_id,
21949
+ assignmentId: row.assignment_id,
21950
+ stepId: row.step_id,
21951
+ stepAttempt: row.step_attempt,
21952
+ producerId: row.producer_id,
21953
+ capabilityHash: row.capability_hash,
21954
+ state: row.state
21955
+ } : void 0;
21956
+ }
21957
+ var KXM_REGISTRY_SCHEMA_VERSION, REGISTRY_TABLES, REGISTRY_SCHEMA, PROJECT_COLUMNS, KxmRuntimeRegistry, KXM_EVENT_STORE_SCHEMA_VERSION, DRIVE_RECEIPT_MAX_BYTES, EVENT_STORE_TABLES, KXM_EVENT_STORE_TABLE_NAMES, EVENT_STORE_SCHEMA, KxmRunEventStore, SHA2562, OPAQUE, FORBIDDEN_TEXT_KEYS, GATE_ATTEMPT_KEYS, GATE_OBSERVATION_KEYS, GATE_EVIDENCE_KEYS;
21135
21958
  var init_runtime_store = __esm({
21136
21959
  "plugins/kxm/src/runtime-store.ts"() {
21137
21960
  "use strict";
@@ -21141,10 +21964,10 @@ var init_runtime_store = __esm({
21141
21964
  init_sync_transform();
21142
21965
  init_runtime_paths();
21143
21966
  init_database();
21144
- KXM_REGISTRY_SCHEMA_VERSION = 1;
21967
+ KXM_REGISTRY_SCHEMA_VERSION = 2;
21145
21968
  REGISTRY_TABLES = {
21146
21969
  supervisor: ["singleton_id", "runtime_id", "pid", "port", "token_hash", "started_at", "heartbeat_at", "state"],
21147
- projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at"]
21970
+ projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at", "lane_of"]
21148
21971
  };
21149
21972
  REGISTRY_SCHEMA = `
21150
21973
  CREATE TABLE supervisor (
@@ -21158,19 +21981,23 @@ CREATE TABLE supervisor (
21158
21981
  state TEXT NOT NULL
21159
21982
  ) STRICT;
21160
21983
  CREATE TABLE projects (
21161
- project_id TEXT PRIMARY KEY,
21984
+ project_key TEXT PRIMARY KEY,
21985
+ project_id TEXT NOT NULL,
21162
21986
  project_root TEXT NOT NULL,
21163
- project_key TEXT NOT NULL UNIQUE,
21164
21987
  home_runtime_id TEXT NOT NULL,
21165
21988
  config_revision TEXT,
21166
- registered_at TEXT NOT NULL
21989
+ registered_at TEXT NOT NULL,
21990
+ lane_of TEXT
21167
21991
  ) STRICT;
21992
+ CREATE INDEX projects_by_project_id ON projects (project_id);
21168
21993
  `;
21994
+ PROJECT_COLUMNS = "project_id, project_root, project_key, home_runtime_id, config_revision, registered_at, lane_of";
21169
21995
  KxmRuntimeRegistry = class {
21170
21996
  path;
21171
21997
  database;
21172
21998
  constructor(path4) {
21173
21999
  this.path = resolve8(path4);
22000
+ migrateRegistryLanes(this.path);
21174
22001
  this.database = openDatabase(this.path, "runtime registry", {
21175
22002
  schema: REGISTRY_SCHEMA,
21176
22003
  version: KXM_REGISTRY_SCHEMA_VERSION,
@@ -21249,57 +22076,165 @@ CREATE TABLE projects (
21249
22076
  supervisor() {
21250
22077
  return this.readSupervisorRow();
21251
22078
  }
21252
- /** Register or revalidate a project's home binding. Home Runtime is immutable. */
21253
- /** All projects registered to this Runtime, for restart recovery: the
21254
- * supervisor needs to reopen their contexts so pending outbox rows resume
21255
- * syncing and presence keeps beating. */
22079
+ /** All projects registered to this Runtime, including lane roots, for restart
22080
+ * recovery: the supervisor reopens their contexts so pending outbox rows
22081
+ * resume syncing and presence keeps beating. */
21256
22082
  projectsForRuntime(homeRuntimeId) {
21257
22083
  const rows = this.database.prepare(
21258
22084
  "SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
21259
22085
  ).all(homeRuntimeId);
21260
22086
  return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
21261
22087
  }
22088
+ /**
22089
+ * Register or revalidate a control root. The home runtime of a live row is
22090
+ * immutable. A second root whose git common directory matches a live row
22091
+ * for the same project is a lane, unless the registering root is the primary
22092
+ * worktree and the live home row is not: then the primary becomes the home
22093
+ * row (same home runtime id) and every other live row of that repository
22094
+ * becomes its lane. A primary never becomes a lane. A root whose directory
22095
+ * is gone is replaced. A live root that is a different repository is
22096
+ * `project_home_conflict`.
22097
+ */
21262
22098
  registerProject(registration) {
21263
22099
  const projectRoot = resolve8(registration.projectRoot);
21264
22100
  const projectKey = projectRuntimeKey(projectRoot);
21265
- this.database.exec("BEGIN IMMEDIATE");
21266
- try {
21267
- const byId = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_id = ?").get(registration.projectId);
21268
- const byKey = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_key = ?").get(projectKey);
21269
- const existing = byId ?? byKey;
21270
- if (existing) {
21271
- const problems = [];
21272
- if (byId && byId.project_key !== projectKey) problems.push(`project ${registration.projectId} is already bound to a different control root`);
21273
- if (byKey && byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
21274
- if (existing.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
21275
- if (problems.length > 0) {
22101
+ const candidateCommon = gitCommonDirectory(projectRoot);
22102
+ const commonByRoot = /* @__PURE__ */ new Map();
22103
+ for (let attempt = 0; attempt < 8; attempt += 1) {
22104
+ this.database.exec("BEGIN IMMEDIATE");
22105
+ let rootsToMeasure;
22106
+ try {
22107
+ const byKey = this.projectRow("SELECT " + PROJECT_COLUMNS + " FROM projects WHERE project_key = ?", projectKey);
22108
+ if (byKey) {
22109
+ const problems = [];
22110
+ if (byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
22111
+ if (byKey.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
22112
+ if (problems.length > 0) {
22113
+ this.database.exec("ROLLBACK");
22114
+ throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
22115
+ }
22116
+ const result = registrationFromRow(byKey);
22117
+ this.database.exec("COMMIT");
22118
+ return result;
22119
+ }
22120
+ const rows = this.database.prepare(
22121
+ `SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY registered_at`
22122
+ ).all(registration.projectId);
22123
+ const live = rows.filter((row) => controlRootDirectoryExists(row.project_root));
22124
+ const dead = rows.filter((row) => !controlRootDirectoryExists(row.project_root));
22125
+ const unknown = live.filter((row) => !commonByRoot.has(row.project_root));
22126
+ if (unknown.length > 0) {
22127
+ rootsToMeasure = unknown.map((row) => row.project_root);
21276
22128
  this.database.exec("ROLLBACK");
21277
- throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
22129
+ } else if (live.length > 0) {
22130
+ const anchor = live.find((row) => {
22131
+ const dir = commonByRoot.get(row.project_root);
22132
+ return candidateCommon !== void 0 && dir === candidateCommon;
22133
+ });
22134
+ if (!anchor) {
22135
+ this.database.exec("ROLLBACK");
22136
+ throw runtimeError(
22137
+ "project_home_conflict",
22138
+ ".kxm/project.yaml",
22139
+ `project ${registration.projectId} is already bound to a different control root`
22140
+ );
22141
+ }
22142
+ if (live.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
22143
+ this.database.exec("ROLLBACK");
22144
+ throw runtimeError(
22145
+ "project_home_conflict",
22146
+ ".kxm/project.yaml",
22147
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
22148
+ );
22149
+ }
22150
+ const sameRepo = live.filter((row) => commonByRoot.get(row.project_root) === candidateCommon);
22151
+ const home = sameRepo.find((row) => row.lane_of === null);
22152
+ const registeringIsPrimary = isPrimaryWorktree(projectRoot, candidateCommon);
22153
+ const homeIsPrimary = home !== void 0 && isPrimaryWorktree(home.project_root, commonByRoot.get(home.project_root));
22154
+ if (registeringIsPrimary && !homeIsPrimary) {
22155
+ this.insertHome(registration, projectRoot, projectKey);
22156
+ for (const row of sameRepo) {
22157
+ this.database.prepare("UPDATE projects SET lane_of = ? WHERE project_key = ?").run(projectKey, row.project_key);
22158
+ }
22159
+ this.database.exec("COMMIT");
22160
+ registration.logger?.({
22161
+ event: "project_home_promoted",
22162
+ projectId: registration.projectId,
22163
+ projectRoot,
22164
+ homeRuntimeId: registration.homeRuntimeId,
22165
+ previousHomeRoot: home?.project_root ?? null,
22166
+ previousHomeKey: home?.project_key ?? null
22167
+ });
22168
+ return this.insertedRegistration(registration, projectRoot, projectKey);
22169
+ }
22170
+ if (registeringIsPrimary) {
22171
+ this.database.exec("ROLLBACK");
22172
+ throw runtimeError(
22173
+ "project_home_conflict",
22174
+ ".kxm/project.yaml",
22175
+ `project ${registration.projectId} primary checkout is already the home row`
22176
+ );
22177
+ }
22178
+ const laneOf = anchor.lane_of ?? anchor.project_key;
22179
+ this.database.prepare(`
22180
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
22181
+ VALUES (?, ?, ?, ?, ?, ?, ?)
22182
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now, laneOf);
22183
+ const result = this.insertedRegistration(registration, projectRoot, projectKey, laneOf);
22184
+ this.database.exec("COMMIT");
22185
+ return result;
22186
+ } else if (dead.length > 0) {
22187
+ const replaced = dead[0];
22188
+ if (dead.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
22189
+ this.database.exec("ROLLBACK");
22190
+ throw runtimeError(
22191
+ "project_home_conflict",
22192
+ ".kxm/project.yaml",
22193
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
22194
+ );
22195
+ }
22196
+ this.database.prepare("DELETE FROM projects WHERE project_id = ?").run(registration.projectId);
22197
+ this.insertHome(registration, projectRoot, projectKey);
22198
+ this.database.exec("COMMIT");
22199
+ registration.logger?.({
22200
+ event: "project_registration_replaced",
22201
+ projectId: registration.projectId,
22202
+ projectRoot,
22203
+ replacedRoot: replaced.project_root,
22204
+ replacedCount: dead.length,
22205
+ replacedHomeRuntimeId: replaced.home_runtime_id,
22206
+ replacedProjectKey: replaced.project_key
22207
+ });
22208
+ return this.insertedRegistration(registration, projectRoot, projectKey);
22209
+ } else {
22210
+ this.insertHome(registration, projectRoot, projectKey);
22211
+ this.database.exec("COMMIT");
22212
+ return this.insertedRegistration(registration, projectRoot, projectKey);
21278
22213
  }
21279
- const result = {
21280
- projectId: existing.project_id,
21281
- projectRoot: existing.project_root,
21282
- projectKey: existing.project_key,
21283
- homeRuntimeId: existing.home_runtime_id,
21284
- ...existing.config_revision !== null ? { configRevision: existing.config_revision } : {},
21285
- registeredAt: existing.registered_at
21286
- };
21287
- this.database.exec("COMMIT");
21288
- return result;
22214
+ } catch (error) {
22215
+ try {
22216
+ this.database.exec("ROLLBACK");
22217
+ } catch {
22218
+ }
22219
+ throw error;
22220
+ }
22221
+ for (const root of rootsToMeasure ?? []) {
22222
+ commonByRoot.set(root, gitCommonDirectory(root));
21289
22223
  }
21290
- this.database.prepare(`
21291
- INSERT INTO projects (project_id, project_root, project_key, home_runtime_id, config_revision, registered_at)
21292
- VALUES (?, ?, ?, ?, ?, ?)
21293
- `).run(registration.projectId, projectRoot, projectKey, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
22224
+ }
22225
+ throw runtimeError(
22226
+ "runtime_registry_busy",
22227
+ projectRoot,
22228
+ `project ${registration.projectId} registration could not be rechecked`
22229
+ );
22230
+ }
22231
+ /** Remove one control root. Returns false when that root was not registered. */
22232
+ unregisterProject(projectRoot) {
22233
+ this.database.exec("BEGIN IMMEDIATE");
22234
+ try {
22235
+ const removed = this.deleteRegisteredRoot(projectRoot);
21294
22236
  this.database.exec("COMMIT");
21295
- return {
21296
- projectId: registration.projectId,
21297
- projectRoot,
21298
- projectKey,
21299
- homeRuntimeId: registration.homeRuntimeId,
21300
- ...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
21301
- registeredAt: registration.now
21302
- };
22237
+ return removed;
21303
22238
  } catch (error) {
21304
22239
  try {
21305
22240
  this.database.exec("ROLLBACK");
@@ -21308,30 +22243,91 @@ CREATE TABLE projects (
21308
22243
  throw error;
21309
22244
  }
21310
22245
  }
22246
+ /**
22247
+ * Delete the row only when `supervisorIsLive` is false for the supervisor
22248
+ * row read inside this write transaction. A live supervisor keeps the row;
22249
+ * the caller uses the supervisor route instead of editing the file.
22250
+ */
22251
+ unregisterProjectIfIdle(projectRoot, supervisorIsLive) {
22252
+ this.database.exec("BEGIN IMMEDIATE");
22253
+ try {
22254
+ if (supervisorIsLive(this.readSupervisorRow())) {
22255
+ this.database.exec("ROLLBACK");
22256
+ return "supervisor_live";
22257
+ }
22258
+ const removed = this.deleteRegisteredRoot(projectRoot);
22259
+ this.database.exec("COMMIT");
22260
+ return removed ? "removed" : "absent";
22261
+ } catch (error) {
22262
+ try {
22263
+ this.database.exec("ROLLBACK");
22264
+ } catch {
22265
+ }
22266
+ throw error;
22267
+ }
22268
+ }
22269
+ /** How many other rows share this home root's project. Zero when this root is a lane or missing. */
22270
+ homeLaneCount(projectRoot) {
22271
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve8(projectRoot)));
22272
+ if (!row || row.lane_of !== null) return 0;
22273
+ const count = this.database.prepare(
22274
+ "SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
22275
+ ).get(row.project_id, row.project_key);
22276
+ return Number(count?.total ?? 0);
22277
+ }
22278
+ /** Caller holds the write transaction. Does not commit. */
22279
+ deleteRegisteredRoot(projectRoot) {
22280
+ const projectKey = projectRuntimeKey(resolve8(projectRoot));
22281
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
22282
+ if (!row) return false;
22283
+ this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
22284
+ if (row.lane_of === null) {
22285
+ const successor = this.database.prepare(
22286
+ "SELECT project_key FROM projects WHERE project_id = ? ORDER BY registered_at LIMIT 1"
22287
+ ).get(row.project_id);
22288
+ if (successor) {
22289
+ this.database.prepare("UPDATE projects SET lane_of = NULL WHERE project_key = ?").run(successor.project_key);
22290
+ this.database.prepare(
22291
+ "UPDATE projects SET lane_of = ? WHERE project_id = ? AND project_key != ? AND (lane_of IS NULL OR lane_of = ?)"
22292
+ ).run(successor.project_key, row.project_id, successor.project_key, projectKey);
22293
+ }
22294
+ }
22295
+ return true;
22296
+ }
22297
+ /** The home row for a project id, or the earliest row when every row is a lane. */
21311
22298
  project(projectId) {
21312
- const row = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_id = ?").get(projectId);
21313
- return row ? {
21314
- projectId: row.project_id,
21315
- projectRoot: row.project_root,
21316
- projectKey: row.project_key,
21317
- homeRuntimeId: row.home_runtime_id,
21318
- ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
21319
- registeredAt: row.registered_at
21320
- } : void 0;
22299
+ const row = this.projectRow(
22300
+ `SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY CASE WHEN lane_of IS NULL THEN 0 ELSE 1 END, registered_at LIMIT 1`,
22301
+ projectId
22302
+ );
22303
+ return row ? registrationFromRow(row) : void 0;
21321
22304
  }
21322
22305
  projectByRoot(projectRoot) {
21323
- const key = projectRuntimeKey(projectRoot);
21324
- const row = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_key = ?").get(key);
21325
- return row ? {
21326
- projectId: row.project_id,
21327
- projectRoot: row.project_root,
21328
- projectKey: row.project_key,
21329
- homeRuntimeId: row.home_runtime_id,
21330
- ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
21331
- registeredAt: row.registered_at
21332
- } : void 0;
22306
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(projectRoot));
22307
+ return row ? registrationFromRow(row) : void 0;
22308
+ }
22309
+ projectRow(sql, parameter) {
22310
+ return this.database.prepare(sql).get(parameter);
22311
+ }
22312
+ insertHome(registration, projectRoot, projectKey) {
22313
+ this.database.prepare(`
22314
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
22315
+ VALUES (?, ?, ?, ?, ?, ?, NULL)
22316
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
22317
+ }
22318
+ insertedRegistration(registration, projectRoot, projectKey, laneOf) {
22319
+ return {
22320
+ projectId: registration.projectId,
22321
+ projectRoot,
22322
+ projectKey,
22323
+ homeRuntimeId: registration.homeRuntimeId,
22324
+ ...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
22325
+ ...laneOf !== void 0 ? { laneOf } : {},
22326
+ registeredAt: registration.now
22327
+ };
21333
22328
  }
21334
22329
  };
22330
+ KXM_EVENT_STORE_SCHEMA_VERSION = 7;
21335
22331
  DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
21336
22332
  EVENT_STORE_TABLES = {
21337
22333
  runs: ["run_id", "project_id", "home_runtime_id", "workflow_id", "prompt_sha256", "status", "config_revision", "memory_revision", "executor_policy_revision", "tool_policy_revision", "created_at", "updated_at"],
@@ -21449,6 +22445,969 @@ CREATE TABLE projects (
21449
22445
  ]
21450
22446
  };
21451
22447
  KXM_EVENT_STORE_TABLE_NAMES = Object.keys(EVENT_STORE_TABLES).sort();
22448
+ EVENT_STORE_SCHEMA = `
22449
+ CREATE TABLE runs (
22450
+ run_id TEXT PRIMARY KEY,
22451
+ project_id TEXT NOT NULL,
22452
+ home_runtime_id TEXT NOT NULL,
22453
+ workflow_id TEXT NOT NULL,
22454
+ prompt_sha256 TEXT NOT NULL,
22455
+ status TEXT NOT NULL,
22456
+ config_revision TEXT NOT NULL,
22457
+ memory_revision TEXT NOT NULL,
22458
+ executor_policy_revision TEXT NOT NULL,
22459
+ tool_policy_revision TEXT NOT NULL,
22460
+ created_at TEXT NOT NULL,
22461
+ updated_at TEXT NOT NULL
22462
+ ) STRICT;
22463
+ CREATE TABLE events (
22464
+ project_id TEXT NOT NULL,
22465
+ run_id TEXT NOT NULL,
22466
+ sequence INTEGER NOT NULL,
22467
+ event_id TEXT NOT NULL UNIQUE,
22468
+ event_type TEXT NOT NULL,
22469
+ command_id TEXT,
22470
+ occurred_at TEXT NOT NULL,
22471
+ recorded_at TEXT NOT NULL,
22472
+ monotonic_ns TEXT NOT NULL,
22473
+ config_revision TEXT NOT NULL,
22474
+ memory_revision TEXT NOT NULL,
22475
+ executor_policy_revision TEXT NOT NULL,
22476
+ tool_policy_revision TEXT NOT NULL,
22477
+ payload TEXT NOT NULL,
22478
+ schema TEXT NOT NULL,
22479
+ home_runtime_id TEXT NOT NULL,
22480
+ PRIMARY KEY (project_id, run_id, sequence)
22481
+ ) STRICT;
22482
+ CREATE INDEX events_run ON events(run_id, sequence);
22483
+ CREATE TABLE commands (
22484
+ command_id TEXT PRIMARY KEY,
22485
+ run_id TEXT NOT NULL,
22486
+ kind TEXT NOT NULL,
22487
+ result TEXT NOT NULL,
22488
+ recorded_at TEXT NOT NULL
22489
+ ) STRICT;
22490
+ CREATE TABLE run_plans (
22491
+ run_id TEXT PRIMARY KEY REFERENCES runs(run_id),
22492
+ run_plan_hash TEXT NOT NULL,
22493
+ envelope TEXT NOT NULL,
22494
+ pinned_sequence INTEGER NOT NULL
22495
+ ) STRICT;
22496
+ CREATE TABLE run_state (
22497
+ run_id TEXT PRIMARY KEY REFERENCES runs(run_id),
22498
+ last_sequence INTEGER NOT NULL,
22499
+ state TEXT NOT NULL
22500
+ ) STRICT;
22501
+ CREATE TABLE attempt_capabilities (
22502
+ attempt_id TEXT PRIMARY KEY,
22503
+ run_id TEXT NOT NULL,
22504
+ assignment_id TEXT NOT NULL,
22505
+ step_id TEXT NOT NULL,
22506
+ step_attempt INTEGER NOT NULL,
22507
+ producer_id TEXT NOT NULL,
22508
+ capability_hash TEXT NOT NULL UNIQUE,
22509
+ state TEXT NOT NULL CHECK (state IN ('issued','settled','revoked'))
22510
+ ) STRICT;
22511
+ CREATE TABLE gate_attempts (
22512
+ attempt_id TEXT PRIMARY KEY REFERENCES attempt_capabilities(attempt_id),
22513
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
22514
+ project_id TEXT NOT NULL,
22515
+ home_runtime_id TEXT NOT NULL,
22516
+ step_id TEXT NOT NULL,
22517
+ step_attempt INTEGER NOT NULL,
22518
+ assignment_id TEXT NOT NULL,
22519
+ effect_id TEXT NOT NULL UNIQUE,
22520
+ gate_id TEXT NOT NULL,
22521
+ gate_kind TEXT NOT NULL CHECK (gate_kind IN ('command','artifacts-exist')),
22522
+ expect TEXT NOT NULL CHECK (expect IN ('pass','fail')),
22523
+ gate_definition_hash TEXT NOT NULL,
22524
+ registry_hash TEXT NOT NULL,
22525
+ run_plan_hash TEXT NOT NULL,
22526
+ control_project_key TEXT NOT NULL,
22527
+ producer_id TEXT NOT NULL CHECK (producer_id = 'kxm-gate'),
22528
+ intent_event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id),
22529
+ content_hash TEXT NOT NULL
22530
+ ) STRICT;
22531
+ CREATE INDEX gate_attempts_run ON gate_attempts(run_id);
22532
+ CREATE INDEX gate_attempts_project ON gate_attempts(project_id);
22533
+ CREATE TABLE gate_observations (
22534
+ observation_id TEXT PRIMARY KEY,
22535
+ attempt_id TEXT NOT NULL UNIQUE REFERENCES gate_attempts(attempt_id),
22536
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
22537
+ project_id TEXT NOT NULL,
22538
+ home_runtime_id TEXT NOT NULL,
22539
+ step_id TEXT NOT NULL,
22540
+ step_attempt INTEGER NOT NULL,
22541
+ assignment_id TEXT NOT NULL,
22542
+ effect_id TEXT NOT NULL,
22543
+ completeness TEXT NOT NULL CHECK (completeness IN ('complete','incomplete','no-start')),
22544
+ spawned INTEGER NOT NULL CHECK (spawned IN (0, 1)),
22545
+ pid INTEGER,
22546
+ exit_code INTEGER,
22547
+ signal TEXT,
22548
+ exit_observed INTEGER NOT NULL CHECK (exit_observed IN (0, 1)),
22549
+ close_observed INTEGER NOT NULL CHECK (close_observed IN (0, 1)),
22550
+ stop_cause TEXT NOT NULL CHECK (stop_cause IN ('none','timeout','cancel','error')),
22551
+ signals_attempted TEXT NOT NULL CHECK (signals_attempted IN ('none','term','term-kill')),
22552
+ error_class TEXT CHECK (error_class IN ('validation','spawn-error','stream-error','stop-error','recording-error','lost-close')),
22553
+ stdout_sha256 TEXT,
22554
+ stdout_bytes INTEGER,
22555
+ stdout_complete INTEGER CHECK (stdout_complete IN (0, 1)),
22556
+ stderr_sha256 TEXT,
22557
+ stderr_bytes INTEGER,
22558
+ stderr_complete INTEGER CHECK (stderr_complete IN (0, 1)),
22559
+ checked_count INTEGER,
22560
+ failed_count INTEGER,
22561
+ elapsed_ms INTEGER NOT NULL,
22562
+ started_at TEXT NOT NULL,
22563
+ finished_at TEXT,
22564
+ recorded_event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id),
22565
+ content_hash TEXT NOT NULL
22566
+ ) STRICT;
22567
+ CREATE INDEX gate_observations_run ON gate_observations(run_id);
22568
+ CREATE TABLE gate_evidence (
22569
+ evidence_id TEXT PRIMARY KEY,
22570
+ attempt_id TEXT NOT NULL UNIQUE REFERENCES gate_attempts(attempt_id),
22571
+ observation_id TEXT NOT NULL UNIQUE REFERENCES gate_observations(observation_id),
22572
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
22573
+ project_id TEXT NOT NULL,
22574
+ home_runtime_id TEXT NOT NULL,
22575
+ step_id TEXT NOT NULL,
22576
+ step_attempt INTEGER NOT NULL,
22577
+ assignment_id TEXT NOT NULL,
22578
+ effect_id TEXT NOT NULL,
22579
+ evidence_key TEXT,
22580
+ kind TEXT NOT NULL CHECK (kind = 'gate'),
22581
+ expect TEXT NOT NULL CHECK (expect IN ('pass','fail')),
22582
+ outcome TEXT NOT NULL CHECK (outcome IN ('passed','implementation-failure','repro-missing')),
22583
+ settled_event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id),
22584
+ content_hash TEXT NOT NULL
22585
+ ) STRICT;
22586
+ CREATE UNIQUE INDEX gate_evidence_key ON gate_evidence(run_id, step_id, step_attempt, evidence_key) WHERE evidence_key IS NOT NULL;
22587
+ CREATE INDEX gate_evidence_run ON gate_evidence(run_id);
22588
+ CREATE TABLE drive_receipts (
22589
+ drive_id TEXT PRIMARY KEY,
22590
+ run_id TEXT NOT NULL,
22591
+ project_id TEXT NOT NULL,
22592
+ opened_sequence INTEGER NOT NULL,
22593
+ last_sequence INTEGER NOT NULL,
22594
+ closed_at TEXT NOT NULL,
22595
+ schema TEXT NOT NULL,
22596
+ receipt TEXT NOT NULL
22597
+ ) STRICT;
22598
+ CREATE INDEX drive_receipts_run ON drive_receipts(run_id);
22599
+ CREATE TABLE coordinators (
22600
+ coordinator_id TEXT PRIMARY KEY,
22601
+ project_id TEXT NOT NULL,
22602
+ role TEXT NOT NULL,
22603
+ channel TEXT NOT NULL,
22604
+ ceiling_hash TEXT NOT NULL,
22605
+ config_revision TEXT NOT NULL,
22606
+ bound_at TEXT NOT NULL,
22607
+ schema TEXT NOT NULL,
22608
+ record TEXT NOT NULL
22609
+ ) STRICT;
22610
+ CREATE UNIQUE INDEX coordinators_slot ON coordinators(project_id, role, channel);
22611
+ CREATE TABLE intake_messages (
22612
+ message_id TEXT PRIMARY KEY,
22613
+ project_id TEXT NOT NULL,
22614
+ coordinator_id TEXT NOT NULL,
22615
+ idempotency_key TEXT NOT NULL,
22616
+ content_hash TEXT NOT NULL,
22617
+ received_at TEXT NOT NULL,
22618
+ dispatch_state TEXT NOT NULL CHECK (dispatch_state IN ('ready','held_paused','admitted','refused')),
22619
+ schema TEXT NOT NULL,
22620
+ record TEXT NOT NULL
22621
+ ) STRICT;
22622
+ CREATE UNIQUE INDEX intake_idempotency ON intake_messages(project_id, coordinator_id, idempotency_key);
22623
+ CREATE INDEX intake_dispatch ON intake_messages(project_id, dispatch_state, received_at, message_id);
22624
+ CREATE TABLE project_controls (
22625
+ project_id TEXT PRIMARY KEY,
22626
+ paused INTEGER NOT NULL CHECK (paused IN (0, 1)),
22627
+ reason TEXT,
22628
+ updated_at TEXT NOT NULL,
22629
+ actor TEXT NOT NULL,
22630
+ schema TEXT NOT NULL,
22631
+ record TEXT NOT NULL
22632
+ ) STRICT;
22633
+ CREATE TABLE IF NOT EXISTS outbox (
22634
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
22635
+ run_id TEXT NOT NULL,
22636
+ sequence INTEGER NOT NULL,
22637
+ sync_event TEXT NOT NULL,
22638
+ attempted_at TEXT,
22639
+ attempt_count INTEGER NOT NULL DEFAULT 0,
22640
+ acked_at TEXT,
22641
+ refused_code TEXT,
22642
+ refused_at TEXT,
22643
+ UNIQUE (run_id, sequence)
22644
+ ) STRICT;
22645
+ CREATE INDEX outbox_pending ON outbox(seq) WHERE acked_at IS NULL AND refused_code IS NULL;
22646
+ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
22647
+ `;
22648
+ KxmRunEventStore = class {
22649
+ path;
22650
+ /** Secret values registered here are replaced in every sync object this
22651
+ * store derives. In memory only: they are never written anywhere. */
22652
+ syncRedactor = new KxmSyncRedactor();
22653
+ database;
22654
+ constructor(path4) {
22655
+ this.path = resolve8(path4);
22656
+ this.database = openDatabase(this.path, "run event store", {
22657
+ schema: EVENT_STORE_SCHEMA,
22658
+ version: KXM_EVENT_STORE_SCHEMA_VERSION,
22659
+ tables: EVENT_STORE_TABLES
22660
+ });
22661
+ }
22662
+ close() {
22663
+ this.database.close();
22664
+ }
22665
+ /** Persist the accepted prompt outside the event log (hashed in events only). */
22666
+ putRunPrompt(runId, prompt) {
22667
+ const all = this.readRunPrompts();
22668
+ all[runId] = prompt;
22669
+ this.writeRunPrompts(all);
22670
+ }
22671
+ getRunPrompt(runId) {
22672
+ const value = this.readRunPrompts()[runId];
22673
+ return typeof value === "string" ? value : void 0;
22674
+ }
22675
+ runPromptSidecarPath() {
22676
+ return `${this.path}.run-prompts.json`;
22677
+ }
22678
+ readRunPrompts() {
22679
+ const sidecar = this.runPromptSidecarPath();
22680
+ if (!existsSync10(sidecar)) return {};
22681
+ try {
22682
+ const parsed = JSON.parse(readFileSync8(sidecar, "utf8"));
22683
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
22684
+ return Object.fromEntries(
22685
+ Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
22686
+ );
22687
+ } catch {
22688
+ return {};
22689
+ }
22690
+ }
22691
+ writeRunPrompts(all) {
22692
+ writeFileSync6(this.runPromptSidecarPath(), `${JSON.stringify(all)}
22693
+ `, { encoding: "utf8", mode: 384 });
22694
+ }
22695
+ /** Run one immutable transaction, rolling back on any failure. */
22696
+ transaction(work) {
22697
+ return withDatabaseTransaction(this.database, work);
22698
+ }
22699
+ command(commandId) {
22700
+ const row = this.database.prepare("SELECT command_id, run_id, kind, result, recorded_at FROM commands WHERE command_id = ?").get(commandId);
22701
+ return row ? { commandId: row.command_id, runId: row.run_id, kind: row.kind, result: JSON.parse(row.result), recordedAt: row.recorded_at } : void 0;
22702
+ }
22703
+ insertCommand(record) {
22704
+ this.database.prepare("INSERT INTO commands (command_id, run_id, kind, result, recorded_at) VALUES (?, ?, ?, ?, ?)").run(record.commandId, record.runId, record.kind, JSON.stringify(record.result), record.recordedAt);
22705
+ }
22706
+ insertRun(record) {
22707
+ this.database.prepare(`
22708
+ INSERT INTO runs (run_id, project_id, home_runtime_id, workflow_id, prompt_sha256, status, config_revision, memory_revision, executor_policy_revision, tool_policy_revision, created_at, updated_at)
22709
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22710
+ `).run(
22711
+ record.runId,
22712
+ record.projectId,
22713
+ record.homeRuntimeId,
22714
+ record.workflowId,
22715
+ record.promptSha256,
22716
+ record.status,
22717
+ record.configRevision,
22718
+ record.memoryRevision,
22719
+ record.executorPolicyRevision,
22720
+ record.toolPolicyRevision,
22721
+ record.createdAt,
22722
+ record.updatedAt
22723
+ );
22724
+ }
22725
+ updateRunStatus(runId, status, updatedAt) {
22726
+ this.database.prepare("UPDATE runs SET status = ?, updated_at = ? WHERE run_id = ?").run(status, updatedAt, runId);
22727
+ }
22728
+ run(runId) {
22729
+ const row = this.database.prepare(`
22730
+ SELECT run_id, project_id, home_runtime_id, workflow_id, prompt_sha256, status, config_revision, memory_revision, executor_policy_revision, tool_policy_revision, created_at, updated_at
22731
+ FROM runs WHERE run_id = ?
22732
+ `).get(runId);
22733
+ return row ? runFromRow(row) : void 0;
22734
+ }
22735
+ /** All projects registered to this Runtime, for restart recovery: the
22736
+ * supervisor needs to reopen their contexts so pending outbox rows resume
22737
+ * syncing and presence keeps beating. */
22738
+ projectsForRuntime(homeRuntimeId) {
22739
+ const rows = this.database.prepare(
22740
+ "SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
22741
+ ).all(homeRuntimeId);
22742
+ return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
22743
+ }
22744
+ runsForProject(projectId, limit = 50) {
22745
+ const rows = this.database.prepare(`
22746
+ SELECT run_id, project_id, home_runtime_id, workflow_id, prompt_sha256, status, config_revision, memory_revision, executor_policy_revision, tool_policy_revision, created_at, updated_at
22747
+ FROM runs WHERE project_id = ? ORDER BY created_at DESC, run_id DESC LIMIT ?
22748
+ `).all(projectId, limit);
22749
+ return rows.map(runFromRow);
22750
+ }
22751
+ nextSequence(runId) {
22752
+ const row = this.database.prepare("SELECT COALESCE(MAX(sequence), 0) AS max_sequence FROM events WHERE run_id = ?").get(runId);
22753
+ return row.max_sequence + 1;
22754
+ }
22755
+ appendEvent(event) {
22756
+ validateRunEvent(event, "run-event");
22757
+ const run = this.run(event.runId);
22758
+ if (!run) throw runtimeError("run_unknown", event.runId, "run does not exist in this event store");
22759
+ if (event.homeRuntimeId !== run.homeRuntimeId || event.projectId !== run.projectId) {
22760
+ throw runtimeError("run_event_owner_mismatch", event.runId, "event homeRuntimeId or projectId does not match the run");
22761
+ }
22762
+ this.database.prepare(`
22763
+ INSERT INTO events (project_id, run_id, sequence, event_id, event_type, command_id, occurred_at, recorded_at, monotonic_ns, config_revision, memory_revision, executor_policy_revision, tool_policy_revision, payload, schema, home_runtime_id)
22764
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22765
+ `).run(
22766
+ event.projectId,
22767
+ event.runId,
22768
+ event.sequence,
22769
+ event.eventId,
22770
+ event.eventType,
22771
+ event.commandId ?? null,
22772
+ event.occurredAt,
22773
+ event.recordedAt,
22774
+ event.monotonicNs,
22775
+ event.configRevision,
22776
+ event.memoryRevision,
22777
+ event.executorPolicyRevision,
22778
+ event.toolPolicyRevision,
22779
+ JSON.stringify(event.payload),
22780
+ event.schema,
22781
+ event.homeRuntimeId
22782
+ );
22783
+ const syncEvent = deriveKxmSyncEvent(event, { redactor: this.syncRedactor });
22784
+ this.database.prepare("INSERT INTO outbox (run_id, sequence, sync_event) VALUES (?, ?, ?)").run(event.runId, event.sequence, kxmSyncEventBytes(syncEvent));
22785
+ }
22786
+ /**
22787
+ * Retryable outbox rows after `afterSeq`, in outbox order, oldest first.
22788
+ * Acked and durably refused rows are never returned: a refused row is not
22789
+ * waiting for a retry, it is waiting for an operator.
22790
+ */
22791
+ pendingOutbox(limit = 100, afterSeq = 0) {
22792
+ const rows = this.database.prepare(`
22793
+ SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
22794
+ FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL AND seq > ? ORDER BY seq ASC LIMIT ?
22795
+ `).all(afterSeq, limit);
22796
+ return rows.map(outboxFromRow);
22797
+ }
22798
+ /** Every outbox row of one run, in any state, in sequence order. */
22799
+ outboxForRun(runId) {
22800
+ const rows = this.database.prepare(`
22801
+ SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
22802
+ FROM outbox WHERE run_id = ? ORDER BY sequence ASC
22803
+ `).all(runId);
22804
+ return rows.map(outboxFromRow);
22805
+ }
22806
+ /**
22807
+ * Count one attempt against each row. Refused rows are never re-counted: they
22808
+ * are out of the queue, and a tick that reaches them again would mean the
22809
+ * refusal was cleared (which resets the count).
22810
+ */
22811
+ markOutboxAttempted(seqs, at2) {
22812
+ const statement = this.database.prepare(`
22813
+ UPDATE outbox SET attempted_at = ?, attempt_count = attempt_count + 1
22814
+ WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
22815
+ `);
22816
+ this.transaction(() => {
22817
+ for (const seq of seqs) statement.run(at2, seq);
22818
+ });
22819
+ }
22820
+ /** Advance the cursor: the hub holds these rows now. Returns rows newly acked. */
22821
+ ackOutbox(seqs, at2) {
22822
+ const statement = this.database.prepare("UPDATE outbox SET acked_at = ?, refused_code = NULL, refused_at = NULL WHERE seq = ? AND acked_at IS NULL");
22823
+ return this.transaction(() => {
22824
+ let changed = 0;
22825
+ for (const seq of seqs) changed += Number(statement.run(at2, seq).changes);
22826
+ return changed;
22827
+ });
22828
+ }
22829
+ /**
22830
+ * Record a durable hub refusal: the answer will not change if the same bytes
22831
+ * are sent again. Returns rows newly moved out of the pending queue.
22832
+ */
22833
+ refuseOutbox(refusals, at2) {
22834
+ const statement = this.database.prepare(`
22835
+ UPDATE outbox SET refused_code = ?, refused_at = ?
22836
+ WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
22837
+ `);
22838
+ return this.transaction(() => {
22839
+ let changed = 0;
22840
+ for (const refusal of refusals) changed += Number(statement.run(refusal.code, at2, refusal.seq).changes);
22841
+ return changed;
22842
+ });
22843
+ }
22844
+ /**
22845
+ * Operator revive: put refused rows back in the pending queue with a fresh
22846
+ * attempt budget. Used after the hub-side state is corrected — the Runtime
22847
+ * must not decide on its own that a refusal has become retryable.
22848
+ */
22849
+ retryRefusedOutbox() {
22850
+ return Number(this.database.prepare(`
22851
+ UPDATE outbox SET refused_code = NULL, refused_at = NULL, attempted_at = NULL, attempt_count = 0
22852
+ WHERE acked_at IS NULL AND refused_code IS NOT NULL
22853
+ `).run().changes);
22854
+ }
22855
+ /**
22856
+ * The outbox as one read model: what is still retryable, what the hub holds,
22857
+ * and what the hub refused with which codes. This is what makes a stalled
22858
+ * sync answerable without opening SQLite.
22859
+ */
22860
+ outboxStatus() {
22861
+ const counts = this.database.prepare(`
22862
+ SELECT
22863
+ COALESCE(SUM(acked_at IS NULL AND refused_code IS NULL), 0) AS pending,
22864
+ COALESCE(SUM(acked_at IS NOT NULL), 0) AS acked,
22865
+ COALESCE(SUM(acked_at IS NULL AND refused_code IS NOT NULL), 0) AS refused,
22866
+ MAX(attempted_at) AS last_attempt_at
22867
+ FROM outbox
22868
+ `).get();
22869
+ const refusals = this.database.prepare(`
22870
+ SELECT refused_code AS code, COUNT(*) AS count
22871
+ FROM outbox WHERE acked_at IS NULL AND refused_code IS NOT NULL
22872
+ GROUP BY refused_code ORDER BY count DESC, code ASC
22873
+ `).all();
22874
+ const oldest = this.database.prepare(`
22875
+ SELECT seq FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL ORDER BY seq ASC LIMIT 1
22876
+ `).get();
22877
+ return {
22878
+ pending: counts.pending,
22879
+ acked: counts.acked,
22880
+ refused: counts.refused,
22881
+ ...counts.last_attempt_at !== null ? { lastAttemptAt: counts.last_attempt_at } : {},
22882
+ ...oldest ? { oldestPendingSeq: oldest.seq } : {},
22883
+ // Mapped, not spread: node:sqlite rows are null-prototype objects, and this
22884
+ // read model is handed straight to the CLI and the supervisor's JSON answer.
22885
+ refusals: refusals.map((refusal) => ({ code: refusal.code, count: refusal.count }))
22886
+ };
22887
+ }
22888
+ events(runId, afterSequence = 0, limit = 200) {
22889
+ const rows = this.database.prepare(`
22890
+ SELECT project_id, run_id, sequence, event_id, event_type, command_id, occurred_at, recorded_at, monotonic_ns, config_revision, memory_revision, executor_policy_revision, tool_policy_revision, payload, schema, home_runtime_id
22891
+ FROM events WHERE run_id = ? AND sequence > ? ORDER BY sequence ASC LIMIT ?
22892
+ `).all(runId, afterSequence, limit);
22893
+ return rows.map((row) => ({
22894
+ schema: row.schema,
22895
+ eventId: row.event_id,
22896
+ eventType: row.event_type,
22897
+ projectId: row.project_id,
22898
+ runId: row.run_id,
22899
+ homeRuntimeId: row.home_runtime_id,
22900
+ sequence: row.sequence,
22901
+ ...row.command_id !== null ? { commandId: row.command_id } : {},
22902
+ occurredAt: row.occurred_at,
22903
+ recordedAt: row.recorded_at,
22904
+ monotonicNs: row.monotonic_ns,
22905
+ configRevision: row.config_revision,
22906
+ memoryRevision: row.memory_revision,
22907
+ executorPolicyRevision: row.executor_policy_revision,
22908
+ toolPolicyRevision: row.tool_policy_revision,
22909
+ payload: JSON.parse(row.payload)
22910
+ }));
22911
+ }
22912
+ insertRunPlan(row) {
22913
+ this.database.prepare("INSERT INTO run_plans (run_id, run_plan_hash, envelope, pinned_sequence) VALUES (?, ?, ?, ?)").run(row.runId, row.runPlanHash, row.envelope, row.pinnedSequence);
22914
+ }
22915
+ runPlan(runId) {
22916
+ const row = this.database.prepare("SELECT run_id, run_plan_hash, envelope, pinned_sequence FROM run_plans WHERE run_id = ?").get(runId);
22917
+ return row ? { runId: row.run_id, runPlanHash: row.run_plan_hash, envelope: row.envelope, pinnedSequence: row.pinned_sequence } : void 0;
22918
+ }
22919
+ runState(runId) {
22920
+ const row = this.database.prepare("SELECT run_id, last_sequence, state FROM run_state WHERE run_id = ?").get(runId);
22921
+ return row ? { runId: row.run_id, lastSequence: row.last_sequence, state: row.state } : void 0;
22922
+ }
22923
+ upsertRunState(row) {
22924
+ this.database.prepare(`
22925
+ INSERT INTO run_state (run_id, last_sequence, state) VALUES (?, ?, ?)
22926
+ ON CONFLICT(run_id) DO UPDATE SET last_sequence = excluded.last_sequence, state = excluded.state
22927
+ `).run(row.runId, row.lastSequence, row.state);
22928
+ }
22929
+ insertDriveReceipt(receipt) {
22930
+ validateDriveReceipt(receipt, "drive-receipt");
22931
+ const canonical2 = kxmCanonicalJson(receipt);
22932
+ if (Buffer.byteLength(canonical2, "utf8") > DRIVE_RECEIPT_MAX_BYTES) {
22933
+ throw runtimeError("drive_receipt_invalid", receipt.driveId, `drive receipt exceeds ${DRIVE_RECEIPT_MAX_BYTES} bytes`);
22934
+ }
22935
+ const result = this.database.prepare(`
22936
+ INSERT INTO drive_receipts (drive_id, run_id, project_id, opened_sequence, last_sequence, closed_at, schema, receipt)
22937
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
22938
+ ON CONFLICT(drive_id) DO NOTHING
22939
+ `).run(
22940
+ receipt.driveId,
22941
+ receipt.runId,
22942
+ receipt.projectId,
22943
+ receipt.openedSequence,
22944
+ receipt.lastSequence,
22945
+ receipt.closedAt,
22946
+ receipt.schema,
22947
+ canonical2
22948
+ );
22949
+ return { inserted: Number(result.changes) === 1 };
22950
+ }
22951
+ driveReceipt(driveId) {
22952
+ const row = this.database.prepare("SELECT receipt FROM drive_receipts WHERE drive_id = ?").get(driveId);
22953
+ return row ? parseDriveReceipt(row.receipt) : void 0;
22954
+ }
22955
+ driveReceiptsForRun(runId, limit = 20) {
22956
+ const rows = this.database.prepare(`
22957
+ SELECT drive_id, receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
22958
+ `).all(runId, limit);
22959
+ return rows.map((row) => {
22960
+ try {
22961
+ return parseDriveReceipt(row.receipt);
22962
+ } catch {
22963
+ return { driveId: row.drive_id, divergence: "receipt unreadable" };
22964
+ }
22965
+ });
22966
+ }
22967
+ /** Insert a coordinator unless the (project, role, channel) slot is taken. */
22968
+ insertCoordinatorIfAbsent(row) {
22969
+ const result = this.database.prepare(`
22970
+ INSERT OR IGNORE INTO coordinators
22971
+ (coordinator_id, project_id, role, channel, ceiling_hash, config_revision, bound_at, schema, record)
22972
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
22973
+ `).run(
22974
+ row.coordinatorId,
22975
+ row.projectId,
22976
+ row.role,
22977
+ row.channel,
22978
+ row.ceilingHash,
22979
+ row.configRevision,
22980
+ row.boundAt,
22981
+ "kxm.coordinator.v1",
22982
+ row.record
22983
+ );
22984
+ return Number(result.changes) === 1;
22985
+ }
22986
+ coordinatorById(coordinatorId) {
22987
+ const row = this.database.prepare("SELECT * FROM coordinators WHERE coordinator_id = ?").get(coordinatorId);
22988
+ return row ? coordinatorFromRow(row) : void 0;
22989
+ }
22990
+ coordinatorInSlot(projectId, role, channel) {
22991
+ const row = this.database.prepare(`
22992
+ SELECT * FROM coordinators WHERE project_id = ? AND role = ? AND channel = ?
22993
+ `).get(projectId, role, channel);
22994
+ return row ? coordinatorFromRow(row) : void 0;
22995
+ }
22996
+ /**
22997
+ * Replace the coordinator holding a (project, role, channel) slot, guarded by
22998
+ * the identity that is currently there. A rebind therefore cannot clobber a
22999
+ * record that changed underneath it, and cannot leave two live identities for
23000
+ * one slot.
23001
+ */
23002
+ replaceCoordinatorInSlot(expectedCoordinatorId, row) {
23003
+ const result = this.database.prepare(`
23004
+ UPDATE coordinators
23005
+ SET coordinator_id = ?, ceiling_hash = ?, config_revision = ?, bound_at = ?, schema = ?, record = ?
23006
+ WHERE project_id = ? AND role = ? AND channel = ? AND coordinator_id = ?
23007
+ `).run(
23008
+ row.coordinatorId,
23009
+ row.ceilingHash,
23010
+ row.configRevision,
23011
+ row.boundAt,
23012
+ "kxm.coordinator.v1",
23013
+ row.record,
23014
+ row.projectId,
23015
+ row.role,
23016
+ row.channel,
23017
+ expectedCoordinatorId
23018
+ );
23019
+ return Number(result.changes) === 1;
23020
+ }
23021
+ /** Insert an intake message unless its idempotency slot is taken. */
23022
+ insertIntakeMessageIfAbsent(row) {
23023
+ const result = this.database.prepare(`
23024
+ INSERT OR IGNORE INTO intake_messages
23025
+ (message_id, project_id, coordinator_id, idempotency_key, content_hash, received_at, dispatch_state, schema, record)
23026
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
23027
+ `).run(
23028
+ row.messageId,
23029
+ row.projectId,
23030
+ row.coordinatorId,
23031
+ row.idempotencyKey,
23032
+ row.contentHash,
23033
+ row.receivedAt,
23034
+ row.dispatchState,
23035
+ "kxm.intake-message.v1",
23036
+ row.record
23037
+ );
23038
+ return Number(result.changes) === 1;
23039
+ }
23040
+ intakeMessage(messageId) {
23041
+ const row = this.database.prepare("SELECT * FROM intake_messages WHERE message_id = ?").get(messageId);
23042
+ return row ? intakeFromRow(row) : void 0;
23043
+ }
23044
+ intakeBySlot(projectId, coordinatorId, idempotencyKey) {
23045
+ const row = this.database.prepare(`
23046
+ SELECT * FROM intake_messages WHERE project_id = ? AND coordinator_id = ? AND idempotency_key = ?
23047
+ `).get(projectId, coordinatorId, idempotencyKey);
23048
+ return row ? intakeFromRow(row) : void 0;
23049
+ }
23050
+ /**
23051
+ * Intake rows in the given dispatch states, in arrival order (replay-safe).
23052
+ *
23053
+ * `rowid` gives same-store arrival order, which is what queue priority needs
23054
+ * here. It is **not** a durable sequence: this repository backs stores up with
23055
+ * `VACUUM INTO`, and a vacuum may renumber implicit rowids. An explicit
23056
+ * immutable arrival sequence is tracked in the plan's schema-v6 follow-ups.
23057
+ */
23058
+ intakeInStates(projectId, states, limit = 100) {
23059
+ if (states.length === 0) return [];
23060
+ const placeholders = states.map(() => "?").join(", ");
23061
+ const rows = this.database.prepare(`
23062
+ SELECT * FROM intake_messages
23063
+ WHERE project_id = ? AND dispatch_state IN (${placeholders})
23064
+ ORDER BY rowid ASC
23065
+ LIMIT ?
23066
+ `).all(projectId, ...states, limit);
23067
+ return rows.map(intakeFromRow);
23068
+ }
23069
+ /** Rewrite one intake row's dispatch state and record. Returns false when it raced away. */
23070
+ updateIntakeDispatch(messageId, expectState, next) {
23071
+ const result = this.database.prepare(`
23072
+ UPDATE intake_messages SET dispatch_state = ?, record = ?, schema = ?
23073
+ WHERE message_id = ? AND dispatch_state = ?
23074
+ `).run(next.state, next.record, "kxm.intake-message.v1", messageId, expectState);
23075
+ return Number(result.changes) === 1;
23076
+ }
23077
+ projectControl(projectId) {
23078
+ const row = this.database.prepare("SELECT * FROM project_controls WHERE project_id = ?").get(projectId);
23079
+ return row ? controlFromRow(row) : void 0;
23080
+ }
23081
+ putProjectControl(row) {
23082
+ this.database.prepare(`
23083
+ INSERT INTO project_controls (project_id, paused, reason, updated_at, actor, schema, record)
23084
+ VALUES (?, ?, ?, ?, ?, ?, ?)
23085
+ ON CONFLICT(project_id) DO UPDATE SET
23086
+ paused = excluded.paused,
23087
+ reason = excluded.reason,
23088
+ updated_at = excluded.updated_at,
23089
+ actor = excluded.actor,
23090
+ schema = excluded.schema,
23091
+ record = excluded.record
23092
+ `).run(
23093
+ row.projectId,
23094
+ row.paused ? 1 : 0,
23095
+ row.reason ?? null,
23096
+ row.updatedAt,
23097
+ row.actor,
23098
+ "kxm.project-control.v1",
23099
+ row.record
23100
+ );
23101
+ }
23102
+ insertCapability(row) {
23103
+ this.database.prepare(`
23104
+ INSERT INTO attempt_capabilities (attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state)
23105
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
23106
+ `).run(row.attemptId, row.runId, row.assignmentId, row.stepId, row.stepAttempt, row.producerId, row.capabilityHash, row.state);
23107
+ }
23108
+ capabilityByHash(capabilityHash) {
23109
+ return capabilityFromRow(this.database.prepare(`
23110
+ SELECT attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state
23111
+ FROM attempt_capabilities WHERE capability_hash = ?
23112
+ `).get(capabilityHash));
23113
+ }
23114
+ capabilityByAttempt(attemptId) {
23115
+ return capabilityFromRow(this.database.prepare(`
23116
+ SELECT attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state
23117
+ FROM attempt_capabilities WHERE attempt_id = ?
23118
+ `).get(attemptId));
23119
+ }
23120
+ settleCapability(attemptId, state) {
23121
+ const updated = this.database.prepare("UPDATE attempt_capabilities SET state = ? WHERE attempt_id = ?").run(state, attemptId);
23122
+ if (Number(updated.changes) !== 1) {
23123
+ throw runtimeError("capability_unknown", attemptId, "attempt capability does not exist");
23124
+ }
23125
+ }
23126
+ insertGateAttempt(row) {
23127
+ assertGateAttemptRow(row);
23128
+ this.assertEventType(row.runId, row.intentEventId, ["effect.intent_recorded"]);
23129
+ const capability = this.capabilityByAttempt(row.attemptId);
23130
+ if (!capability || capability.runId !== row.runId || capability.assignmentId !== row.assignmentId || capability.stepId !== row.stepId || capability.stepAttempt !== row.stepAttempt) {
23131
+ throw gateRowInvalid(row.attemptId, "gate attempt identity does not match attempt_capabilities");
23132
+ }
23133
+ this.database.prepare(`
23134
+ INSERT INTO gate_attempts (
23135
+ attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23136
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23137
+ producer_id, intent_event_id, content_hash
23138
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
23139
+ `).run(
23140
+ row.attemptId,
23141
+ row.runId,
23142
+ row.projectId,
23143
+ row.homeRuntimeId,
23144
+ row.stepId,
23145
+ row.stepAttempt,
23146
+ row.assignmentId,
23147
+ row.effectId,
23148
+ row.gateId,
23149
+ row.gateKind,
23150
+ row.expect,
23151
+ row.gateDefinitionHash,
23152
+ row.registryHash,
23153
+ row.runPlanHash,
23154
+ row.controlProjectKey,
23155
+ row.producerId,
23156
+ row.intentEventId,
23157
+ row.contentHash
23158
+ );
23159
+ }
23160
+ insertGateObservation(row) {
23161
+ assertGateObservationRow(row);
23162
+ this.assertEventType(row.runId, row.recordedEventId, ["effect.observed", "effect.blocked_uncertain"]);
23163
+ const attempt = this.gateAttempt(row.attemptId);
23164
+ if (!attempt) throw gateRowInvalid(row.attemptId, "gate observation has no matching gate attempt");
23165
+ assertSharedIdentity(row, attempt, row.observationId);
23166
+ assertClosedGateObservation(attempt.gateKind, row, row.observationId);
23167
+ this.database.prepare(`
23168
+ INSERT INTO gate_observations (
23169
+ observation_id, attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23170
+ completeness, spawned, pid, exit_code, signal, exit_observed, close_observed, stop_cause, signals_attempted,
23171
+ error_class, stdout_sha256, stdout_bytes, stdout_complete, stderr_sha256, stderr_bytes, stderr_complete,
23172
+ checked_count, failed_count, elapsed_ms, started_at, finished_at, recorded_event_id, content_hash
23173
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
23174
+ `).run(
23175
+ row.observationId,
23176
+ row.attemptId,
23177
+ row.runId,
23178
+ row.projectId,
23179
+ row.homeRuntimeId,
23180
+ row.stepId,
23181
+ row.stepAttempt,
23182
+ row.assignmentId,
23183
+ row.effectId,
23184
+ row.completeness,
23185
+ row.spawned,
23186
+ row.pid,
23187
+ row.exitCode,
23188
+ row.signal,
23189
+ row.exitObserved,
23190
+ row.closeObserved,
23191
+ row.stopCause,
23192
+ row.signalsAttempted,
23193
+ row.errorClass,
23194
+ row.stdoutSha256,
23195
+ row.stdoutBytes,
23196
+ row.stdoutComplete,
23197
+ row.stderrSha256,
23198
+ row.stderrBytes,
23199
+ row.stderrComplete,
23200
+ row.checkedCount,
23201
+ row.failedCount,
23202
+ row.elapsedMs,
23203
+ row.startedAt,
23204
+ row.finishedAt,
23205
+ row.recordedEventId,
23206
+ row.contentHash
23207
+ );
23208
+ }
23209
+ insertGateEvidence(row) {
23210
+ assertGateEvidenceRow(row);
23211
+ this.assertEventType(row.runId, row.settledEventId, ["effect.settled"]);
23212
+ const attempt = this.gateAttempt(row.attemptId);
23213
+ if (!attempt) throw gateRowInvalid(row.attemptId, "gate evidence has no matching gate attempt");
23214
+ assertSharedIdentity(row, attempt, row.evidenceId);
23215
+ const observation = this.gateObservationForAttempt(row.attemptId);
23216
+ if (!observation || observation.observationId !== row.observationId) {
23217
+ throw gateRowInvalid(row.evidenceId, "gate evidence observation_id does not match the attempt observation");
23218
+ }
23219
+ this.database.prepare(`
23220
+ INSERT INTO gate_evidence (
23221
+ evidence_id, attempt_id, observation_id, run_id, project_id, home_runtime_id, step_id, step_attempt,
23222
+ assignment_id, effect_id, evidence_key, kind, expect, outcome, settled_event_id, content_hash
23223
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
23224
+ `).run(
23225
+ row.evidenceId,
23226
+ row.attemptId,
23227
+ row.observationId,
23228
+ row.runId,
23229
+ row.projectId,
23230
+ row.homeRuntimeId,
23231
+ row.stepId,
23232
+ row.stepAttempt,
23233
+ row.assignmentId,
23234
+ row.effectId,
23235
+ row.evidenceKey,
23236
+ row.kind,
23237
+ row.expect,
23238
+ row.outcome,
23239
+ row.settledEventId,
23240
+ row.contentHash
23241
+ );
23242
+ }
23243
+ gateAttempt(attemptId) {
23244
+ const row = this.database.prepare(`
23245
+ SELECT attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23246
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23247
+ producer_id, intent_event_id, content_hash
23248
+ FROM gate_attempts WHERE attempt_id = ?
23249
+ `).get(attemptId);
23250
+ return row ? gateAttemptFromSql(row) : void 0;
23251
+ }
23252
+ gateAttemptsForRun(runId) {
23253
+ const rows = this.database.prepare(`
23254
+ SELECT attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23255
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23256
+ producer_id, intent_event_id, content_hash
23257
+ FROM gate_attempts WHERE run_id = ? ORDER BY step_attempt ASC, attempt_id ASC
23258
+ `).all(runId);
23259
+ return rows.map(gateAttemptFromSql);
23260
+ }
23261
+ gateAttemptsForProject(projectId) {
23262
+ const rows = this.database.prepare(`
23263
+ SELECT attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23264
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23265
+ producer_id, intent_event_id, content_hash
23266
+ FROM gate_attempts WHERE project_id = ? ORDER BY run_id ASC, step_attempt ASC, attempt_id ASC
23267
+ `).all(projectId);
23268
+ return rows.map(gateAttemptFromSql);
23269
+ }
23270
+ issuedOrRevokedCapabilities() {
23271
+ const rows = this.database.prepare(`
23272
+ SELECT attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state
23273
+ FROM attempt_capabilities
23274
+ WHERE state IN ('issued','revoked')
23275
+ ORDER BY run_id ASC, step_attempt ASC, attempt_id ASC
23276
+ `).all();
23277
+ const mapped = [];
23278
+ for (const row of rows) {
23279
+ const capability = capabilityFromRow(row);
23280
+ if (!capability) {
23281
+ throw runtimeError("gate_recovery_corrupt", "attempt_capabilities", "issued or revoked capability row could not be read");
23282
+ }
23283
+ mapped.push(capability);
23284
+ }
23285
+ return mapped;
23286
+ }
23287
+ gateObservationForAttempt(attemptId) {
23288
+ const row = this.database.prepare(`
23289
+ SELECT observation_id, attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id,
23290
+ effect_id, completeness, spawned, pid, exit_code, signal, exit_observed, close_observed, stop_cause,
23291
+ signals_attempted, error_class, stdout_sha256, stdout_bytes, stdout_complete, stderr_sha256, stderr_bytes,
23292
+ stderr_complete, checked_count, failed_count, elapsed_ms, started_at, finished_at, recorded_event_id, content_hash
23293
+ FROM gate_observations WHERE attempt_id = ?
23294
+ `).get(attemptId);
23295
+ return row ? gateObservationFromSql(row) : void 0;
23296
+ }
23297
+ gateObservationsForRun(runId) {
23298
+ const rows = this.database.prepare(`
23299
+ SELECT observation_id, attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id,
23300
+ effect_id, completeness, spawned, pid, exit_code, signal, exit_observed, close_observed, stop_cause,
23301
+ signals_attempted, error_class, stdout_sha256, stdout_bytes, stdout_complete, stderr_sha256, stderr_bytes,
23302
+ stderr_complete, checked_count, failed_count, elapsed_ms, started_at, finished_at, recorded_event_id, content_hash
23303
+ FROM gate_observations WHERE run_id = ? ORDER BY step_attempt ASC, observation_id ASC
23304
+ `).all(runId);
23305
+ return rows.map(gateObservationFromSql);
23306
+ }
23307
+ gateEvidenceForRun(runId) {
23308
+ const rows = this.database.prepare(`
23309
+ SELECT evidence_id, attempt_id, observation_id, run_id, project_id, home_runtime_id, step_id, step_attempt,
23310
+ assignment_id, effect_id, evidence_key, kind, expect, outcome, settled_event_id, content_hash
23311
+ FROM gate_evidence WHERE run_id = ? ORDER BY step_attempt ASC, evidence_id ASC
23312
+ `).all(runId);
23313
+ return rows.map(gateEvidenceFromSql);
23314
+ }
23315
+ gateEvidenceForAttempt(attemptId) {
23316
+ const row = this.database.prepare(`
23317
+ SELECT evidence_id, attempt_id, observation_id, run_id, project_id, home_runtime_id, step_id, step_attempt,
23318
+ assignment_id, effect_id, evidence_key, kind, expect, outcome, settled_event_id, content_hash
23319
+ FROM gate_evidence WHERE attempt_id = ?
23320
+ `).get(attemptId);
23321
+ return row ? gateEvidenceFromSql(row) : void 0;
23322
+ }
23323
+ gateRowCounts(runId) {
23324
+ const attempts = this.database.prepare("SELECT COUNT(*) AS n FROM gate_attempts WHERE run_id = ?").get(runId);
23325
+ const observations = this.database.prepare("SELECT COUNT(*) AS n FROM gate_observations WHERE run_id = ?").get(runId);
23326
+ const evidence = this.database.prepare("SELECT COUNT(*) AS n FROM gate_evidence WHERE run_id = ?").get(runId);
23327
+ return { attempts: Number(attempts.n), observations: Number(observations.n), evidence: Number(evidence.n) };
23328
+ }
23329
+ assertEventType(runId, eventId, allowed) {
23330
+ const row = this.database.prepare("SELECT run_id, event_type FROM events WHERE event_id = ?").get(eventId);
23331
+ if (!row || row.run_id !== runId || !allowed.includes(row.event_type)) {
23332
+ throw gateRowInvalid(eventId, `referenced event is not ${allowed.join("|")} for this run`);
23333
+ }
23334
+ }
23335
+ };
23336
+ SHA2562 = /^sha256:[a-f0-9]{64}$/;
23337
+ OPAQUE = /^[a-z][a-z0-9]{1,15}_[A-Za-z0-9][A-Za-z0-9_-]{5,127}$/;
23338
+ FORBIDDEN_TEXT_KEYS = /* @__PURE__ */ new Set(["stdout", "stderr", "error", "errorText", "stdoutText", "stderrText", "errorMessage", "message"]);
23339
+ GATE_ATTEMPT_KEYS = [
23340
+ "attemptId",
23341
+ "runId",
23342
+ "projectId",
23343
+ "homeRuntimeId",
23344
+ "stepId",
23345
+ "stepAttempt",
23346
+ "assignmentId",
23347
+ "effectId",
23348
+ "gateId",
23349
+ "gateKind",
23350
+ "expect",
23351
+ "gateDefinitionHash",
23352
+ "registryHash",
23353
+ "runPlanHash",
23354
+ "controlProjectKey",
23355
+ "producerId",
23356
+ "intentEventId",
23357
+ "contentHash"
23358
+ ];
23359
+ GATE_OBSERVATION_KEYS = [
23360
+ "observationId",
23361
+ "attemptId",
23362
+ "runId",
23363
+ "projectId",
23364
+ "homeRuntimeId",
23365
+ "stepId",
23366
+ "stepAttempt",
23367
+ "assignmentId",
23368
+ "effectId",
23369
+ "completeness",
23370
+ "spawned",
23371
+ "pid",
23372
+ "exitCode",
23373
+ "signal",
23374
+ "exitObserved",
23375
+ "closeObserved",
23376
+ "stopCause",
23377
+ "signalsAttempted",
23378
+ "errorClass",
23379
+ "stdoutSha256",
23380
+ "stdoutBytes",
23381
+ "stdoutComplete",
23382
+ "stderrSha256",
23383
+ "stderrBytes",
23384
+ "stderrComplete",
23385
+ "checkedCount",
23386
+ "failedCount",
23387
+ "elapsedMs",
23388
+ "startedAt",
23389
+ "finishedAt",
23390
+ "recordedEventId",
23391
+ "contentHash"
23392
+ ];
23393
+ GATE_EVIDENCE_KEYS = [
23394
+ "evidenceId",
23395
+ "attemptId",
23396
+ "observationId",
23397
+ "runId",
23398
+ "projectId",
23399
+ "homeRuntimeId",
23400
+ "stepId",
23401
+ "stepAttempt",
23402
+ "assignmentId",
23403
+ "effectId",
23404
+ "evidenceKey",
23405
+ "kind",
23406
+ "expect",
23407
+ "outcome",
23408
+ "settledEventId",
23409
+ "contentHash"
23410
+ ];
21452
23411
  }
21453
23412
  });
21454
23413
 
@@ -21970,9 +23929,9 @@ var init_engine_compile = __esm({
21970
23929
  });
21971
23930
 
21972
23931
  // plugins/kxm/src/engine-plan.ts
21973
- import { createHash as createHash12 } from "node:crypto";
23932
+ import { createHash as createHash13 } from "node:crypto";
21974
23933
  function kxmSha256(input) {
21975
- return `sha256:${createHash12("sha256").update(input, "utf8").digest("hex")}`;
23934
+ return `sha256:${createHash13("sha256").update(input, "utf8").digest("hex")}`;
21976
23935
  }
21977
23936
  function parseGateDefinition(raw, gateId, runId) {
21978
23937
  const value = asObject(raw, runId, `gates.definitions.${gateId}`);
@@ -22184,8 +24143,8 @@ var init_price_calc = __esm({
22184
24143
  });
22185
24144
 
22186
24145
  // plugins/kxm/src/prices.ts
22187
- import { createHash as createHash13 } from "node:crypto";
22188
- import { existsSync as existsSync11, readFileSync as readFileSync9, statSync, writeFileSync as writeFileSync7 } from "node:fs";
24146
+ import { createHash as createHash14 } from "node:crypto";
24147
+ import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
22189
24148
  import { join as join11 } from "node:path";
22190
24149
  function hashPriceCatalog(catalog) {
22191
24150
  const canonical2 = {
@@ -22206,7 +24165,7 @@ function hashPriceCatalog(catalog) {
22206
24165
  }))
22207
24166
  })).sort((a, b2) => a.id.localeCompare(b2.id))
22208
24167
  };
22209
- return createHash13("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
24168
+ return createHash14("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
22210
24169
  }
22211
24170
  function parsePriceCatalog(text) {
22212
24171
  const parsed = (0, import_yaml3.parse)(text);
@@ -22297,7 +24256,7 @@ function parsePriceCatalog(text) {
22297
24256
  return catalog;
22298
24257
  }
22299
24258
  function loadPriceCatalog(rootOrPath) {
22300
- const candidatePath = existsSync11(join11(rootOrPath, ".kxm", "prices.yaml")) ? join11(rootOrPath, ".kxm", "prices.yaml") : existsSync11(join11(rootOrPath, "prices.yaml")) ? join11(rootOrPath, "prices.yaml") : existsSync11(rootOrPath) && statSync(rootOrPath).isFile() ? rootOrPath : void 0;
24259
+ const candidatePath = existsSync11(join11(rootOrPath, ".kxm", "prices.yaml")) ? join11(rootOrPath, ".kxm", "prices.yaml") : existsSync11(join11(rootOrPath, "prices.yaml")) ? join11(rootOrPath, "prices.yaml") : existsSync11(rootOrPath) && statSync2(rootOrPath).isFile() ? rootOrPath : void 0;
22301
24260
  if (!candidatePath || !existsSync11(candidatePath)) {
22302
24261
  return void 0;
22303
24262
  }
@@ -22762,7 +24721,7 @@ function loadRoutePolicy(root) {
22762
24721
  if (!existsSync14(path4)) return empty();
22763
24722
  const value = (0, import_yaml6.parse)(readFileSync12(path4, "utf8"));
22764
24723
  if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
22765
- return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x2) => typeof x2 === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x2) => typeof x2 === "string") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v2]) => Array.isArray(v2)).map(([k2, v2]) => [k2, v2.filter((x2) => typeof x2 === "string")])) : {} };
24724
+ return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x2) => typeof x2 === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x2) => typeof x2 === "string") : [] };
22766
24725
  }
22767
24726
  function updateRouteState(root, model, state) {
22768
24727
  return setRouteState(root, model, state);
@@ -22782,21 +24741,7 @@ function setRouteState(root, model, state, role, removeRole2 = false) {
22782
24741
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) roleFile = parsed;
22783
24742
  }
22784
24743
  const roster = Array.isArray(roleFile.roster) ? roleFile.roster.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
22785
- const modelsDir = join14(root, ".kxm", "models");
22786
- let routeId2;
22787
- if (existsSync14(modelsDir)) {
22788
- for (const name of readdirSync4(modelsDir)) {
22789
- if (!name.endsWith(".yaml") || name === "inventory.yaml") continue;
22790
- const parsedModel = (0, import_yaml6.parse)(readFileSync12(join14(modelsDir, name), "utf8"));
22791
- if (parsedModel?.schema !== "kxm.model.v2") continue;
22792
- const vendor = typeof parsedModel.vendor === "string" ? parsedModel.vendor : "";
22793
- const named = typeof parsedModel.model === "string" ? parsedModel.model : "";
22794
- if (named === model || vendor && `${vendor}/${named}` === model) {
22795
- routeId2 = name.slice(0, -5);
22796
- break;
22797
- }
22798
- }
22799
- }
24744
+ const routeId2 = routeIdForInventoryModel(root, model);
22800
24745
  if (!routeId2) throw new Error(`unknown route: no v2 model file matches '${model}'`);
22801
24746
  const existing = roster.findIndex((entry) => entry.route === routeId2 || entry.model === model);
22802
24747
  if (removeRole2) {
@@ -22817,6 +24762,24 @@ function setRouteState(root, model, state, role, removeRole2 = false) {
22817
24762
  writeFileSync10(join14(root, ".kxm", "routes.yaml"), (0, import_yaml6.stringify)(policy), "utf8");
22818
24763
  return policy;
22819
24764
  }
24765
+ function routeIdForInventoryModel(root, model) {
24766
+ const modelsDir = join14(root, ".kxm", "models");
24767
+ if (!existsSync14(modelsDir)) return void 0;
24768
+ for (const name of readdirSync4(modelsDir)) {
24769
+ if (!name.endsWith(".yaml") || name === "inventory.yaml") continue;
24770
+ const parsedModel = (0, import_yaml6.parse)(readFileSync12(join14(modelsDir, name), "utf8"));
24771
+ if (parsedModel?.schema !== "kxm.model.v2") continue;
24772
+ const vendor = typeof parsedModel.vendor === "string" ? parsedModel.vendor : "";
24773
+ const named = typeof parsedModel.model === "string" ? parsedModel.model : "";
24774
+ if (named === model || vendor && `${vendor}/${named}` === model) return name.slice(0, -5);
24775
+ }
24776
+ return void 0;
24777
+ }
24778
+ function rolesForInventoryModel(root, inventoryId) {
24779
+ const routeId2 = routeIdForInventoryModel(root, inventoryId);
24780
+ if (!routeId2) return [];
24781
+ return Object.entries(listRoleBindings(root)).filter(([, ids]) => ids.includes(routeId2)).map(([role]) => role);
24782
+ }
22820
24783
  function listRoleBindings(root) {
22821
24784
  const dir = join14(root, ".kxm", "roles");
22822
24785
  const result = {};
@@ -22828,10 +24791,6 @@ function listRoleBindings(root) {
22828
24791
  }
22829
24792
  return result;
22830
24793
  }
22831
- function isRouteAdmitted(root, modelId2) {
22832
- const policy = loadRoutePolicy(root);
22833
- return policy.admitted.includes(modelId2) && !policy.disabled.includes(modelId2);
22834
- }
22835
24794
  function listInventoryModels(root) {
22836
24795
  const path4 = join14(root, ".kxm", "models", "inventory.yaml");
22837
24796
  if (!existsSync14(path4)) return [];
@@ -22845,7 +24804,7 @@ var init_routes = __esm({
22845
24804
  import_yaml6 = __toESM(require_dist(), 1);
22846
24805
  init_role();
22847
24806
  RETIRED_POLICY = ".kxm/producers.yaml";
22848
- empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
24807
+ empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [] });
22849
24808
  }
22850
24809
  });
22851
24810
 
@@ -22883,7 +24842,7 @@ var init_arbiter = __esm({
22883
24842
  });
22884
24843
 
22885
24844
  // plugins/kxm/src/memory.ts
22886
- import { randomUUID as randomUUID4 } from "node:crypto";
24845
+ import { randomUUID as randomUUID5 } from "node:crypto";
22887
24846
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
22888
24847
  import { extname as extname2, join as join15, resolve as resolve10 } from "node:path";
22889
24848
  function parseFrontmatter(content) {
@@ -23020,7 +24979,7 @@ function createMemoryNote(repoRoot, summary, options = {}) {
23020
24979
  if (!VALID_SCOPES.has(scope)) {
23021
24980
  throw new Error(`invalid memory note scope: ${scope}; must be one of agent, project, run, operator`);
23022
24981
  }
23023
- const hex = randomUUID4().replaceAll("-", "").slice(0, 12);
24982
+ const hex = randomUUID5().replaceAll("-", "").slice(0, 12);
23024
24983
  const id = `cand_${scope}_${hex}`;
23025
24984
  const record = {
23026
24985
  schema: MEMORY_SCHEMA,
@@ -23166,11 +25125,11 @@ var init_memory = __esm({
23166
25125
  });
23167
25126
 
23168
25127
  // plugins/kxm/src/skills.ts
23169
- import { createHash as createHash14 } from "node:crypto";
23170
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync12 } from "node:fs";
25128
+ import { createHash as createHash15 } from "node:crypto";
25129
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync12 } from "node:fs";
23171
25130
  import { dirname as dirname10, join as join16 } from "node:path";
23172
25131
  function skillContentSha256(content) {
23173
- return createHash14("sha256").update(content, "utf8").digest("hex");
25132
+ return createHash15("sha256").update(content, "utf8").digest("hex");
23174
25133
  }
23175
25134
  function skillIdFor(name, contentSha256) {
23176
25135
  const slug = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -23243,7 +25202,7 @@ function boundedList(value, field) {
23243
25202
  return [...new Set(refs)];
23244
25203
  }
23245
25204
  function readdirSorted(dir) {
23246
- return readdirSync6(dir).filter((entry) => statSync2(join16(dir, entry)).isDirectory()).sort();
25205
+ return readdirSync6(dir).filter((entry) => statSync3(join16(dir, entry)).isDirectory()).sort();
23247
25206
  }
23248
25207
  var import_yaml8, SKILL_CANDIDATE_SCHEMA, SKILL_EVALUATION_SCHEMA, SKILL_DECISION_SCHEMA, MAX_SKILL_NAME_CHARS, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_EVIDENCE_REFS, MAX_SKILL_MODELS, PROMOTION_REQUIRED_EVALUATIONS, SkillLifecycleError, SkillLifecycle;
23249
25208
  var init_skills = __esm({
@@ -23587,11 +25546,11 @@ var init_dispatch_context = __esm({
23587
25546
  });
23588
25547
 
23589
25548
  // plugins/kxm/src/artifacts-exist.ts
23590
- import { lstatSync as lstatSync5, realpathSync as realpathSync4, statSync as statSync3 } from "node:fs";
23591
- import { isAbsolute as isAbsolute5, relative as relative4, resolve as resolve11 } from "node:path";
25549
+ import { lstatSync as lstatSync5, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
25550
+ import { isAbsolute as isAbsolute6, relative as relative4, resolve as resolve11 } from "node:path";
23592
25551
  function staysUnder(root, candidate) {
23593
25552
  const child = relative4(root, candidate);
23594
- return child === "" || !isAbsolute5(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
25553
+ return child === "" || !isAbsolute6(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
23595
25554
  }
23596
25555
  function verifyArtifactExists(rootInput, pathInput) {
23597
25556
  const lexicalRoot = resolve11(rootInput);
@@ -23602,12 +25561,12 @@ function verifyArtifactExists(rootInput, pathInput) {
23602
25561
  let realRoot;
23603
25562
  let realPath;
23604
25563
  try {
23605
- realRoot = realpathSync4(lexicalRoot);
25564
+ realRoot = realpathSync5(lexicalRoot);
23606
25565
  } catch {
23607
25566
  return { ok: false, error: "artifact_unreadable", path: lexicalPath };
23608
25567
  }
23609
25568
  try {
23610
- realPath = realpathSync4(lexicalPath);
25569
+ realPath = realpathSync5(lexicalPath);
23611
25570
  } catch {
23612
25571
  return { ok: false, error: "artifact_missing", path: lexicalPath };
23613
25572
  }
@@ -23619,7 +25578,7 @@ function verifyArtifactExists(rootInput, pathInput) {
23619
25578
  if (link.isSymbolicLink()) {
23620
25579
  return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
23621
25580
  }
23622
- const artifact = statSync3(realPath);
25581
+ const artifact = statSync4(realPath);
23623
25582
  if (!artifact.isFile()) return { ok: false, error: "artifact_not_file", path: lexicalPath };
23624
25583
  if (artifact.size < 1) return { ok: false, error: "artifact_empty", path: lexicalPath };
23625
25584
  return { ok: true, path: realPath, bytes: artifact.size };
@@ -24273,103 +26232,132 @@ var init_routing = __esm({
24273
26232
  // plugins/kxm/src/engine.ts
24274
26233
  import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
24275
26234
  import { join as join17 } from "node:path";
24276
- function selectorsNamedByRoutes(projectRoot, routeIds) {
24277
- const selectors = /* @__PURE__ */ new Set();
24278
- for (const routeId2 of routeIds) {
24279
- const file = join17(projectRoot, ".kxm", "models", `${routeId2}.yaml`);
24280
- if (!existsSync17(file)) continue;
24281
- let parsed;
24282
- try {
24283
- parsed = (0, import_yaml9.parse)(readFileSync15(file, "utf8"));
24284
- } catch {
24285
- continue;
24286
- }
24287
- const model = typeof parsed.model === "string" ? parsed.model : "";
24288
- const vendor = typeof parsed.vendor === "string" ? parsed.vendor : "";
24289
- const harness = typeof parsed.harness === "string" ? parsed.harness : "";
24290
- if (model) selectors.add(model);
24291
- if (vendor && model) selectors.add(`${vendor}/${model}`);
24292
- if (harness && model) selectors.add(`${harness}/${model}`);
26235
+ function readYamlFile(file) {
26236
+ return readModelDocument(file);
26237
+ }
26238
+ function readModelDocument(file) {
26239
+ if (!existsSync17(file)) return void 0;
26240
+ try {
26241
+ const parsed = (0, import_yaml9.parse)(readFileSync15(file, "utf8"));
26242
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
26243
+ return parsed;
26244
+ } catch {
26245
+ return void 0;
24293
26246
  }
24294
- return [...selectors];
24295
26247
  }
24296
26248
  function resolveProducerRoute(projectRoot, step, agentId) {
24297
- let agentModel;
24298
- const agentFile = join17(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
24299
- if (existsSync17(agentFile)) {
24300
- try {
24301
- const parsed = (0, import_yaml9.parse)(readFileSync15(agentFile, "utf8"));
24302
- if (typeof parsed?.model === "string") {
24303
- agentModel = parsed.model;
24304
- } else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
24305
- const declared = parsed.model;
24306
- const providerOk = typeof declared.provider === "string" && declared.provider.length > 0 && !declared.provider.includes("/");
24307
- const modelOk = typeof declared.model === "string" && declared.model.length > 0 && !declared.model.startsWith("/") && !declared.model.endsWith("/");
24308
- if (providerOk && modelOk) {
24309
- agentModel = `${declared.provider}/${declared.model}`;
24310
- } else {
24311
- return {
24312
- error: {
24313
- reason: "step_unsupported",
24314
- field: "model",
24315
- detail: "producer_route_unsupported: invalid model declaration"
24316
- }
24317
- };
24318
- }
24319
- }
24320
- } catch {
24321
- }
24322
- }
24323
- let selector = typeof step.model === "string" ? step.model : agentModel;
24324
- if (!selector) {
24325
- if (agentId === "implementer") {
24326
- selector = "xai/grok-4.6";
24327
- }
24328
- }
24329
- if (!selector) {
26249
+ if ((step.kind === "agent" || step.kind === "moa") && step.model !== void 0) {
24330
26250
  return {
24331
26251
  error: {
24332
26252
  reason: "step_unsupported",
24333
26253
  field: "model",
24334
- detail: "producer_route_unsupported: agent or step has no model declared"
26254
+ detail: "producer_route_unsupported: agent step model is not honored"
24335
26255
  }
24336
26256
  };
24337
26257
  }
24338
- const slash = selector.indexOf("/");
24339
- if (slash <= 0 || slash === selector.length - 1) {
26258
+ const agent = readYamlFile(join17(projectRoot, ".kxm", "agents", `${agentId}.yaml`));
26259
+ const role = typeof agent?.role === "string" ? agent.role : "";
26260
+ if (!role) {
24340
26261
  return {
24341
26262
  error: {
24342
26263
  reason: "step_unsupported",
24343
26264
  field: "model",
24344
- detail: `producer_route_unsupported: invalid selector '${selector}'`
26265
+ detail: "producer_route_unsupported: agent has no role"
24345
26266
  }
24346
26267
  };
24347
26268
  }
24348
- if (!isRouteAdmitted(projectRoot, selector)) {
26269
+ const roleDoc = readYamlFile(join17(projectRoot, ".kxm", "roles", `${role}.yaml`));
26270
+ const roster = Array.isArray(roleDoc?.roster) ? roleDoc.roster : void 0;
26271
+ if (!roster) {
24349
26272
  return {
24350
26273
  error: {
24351
26274
  reason: "step_unsupported",
24352
26275
  field: "model",
24353
- detail: `producer_route_unsupported: model '${selector}' is not admitted`
26276
+ detail: `producer_route_unsupported: role '${role}' has no roster`
24354
26277
  }
24355
26278
  };
24356
26279
  }
24357
- const role = agentId === "implementer" ? "writer" : agentId;
24358
- const roleBindings = listRoleBindings(projectRoot);
24359
- const roster = roleBindings[role];
24360
- const rosterSelectors = roster ? selectorsNamedByRoutes(projectRoot, roster) : void 0;
24361
- if (rosterSelectors && !rosterSelectors.includes(selector)) {
26280
+ const policy = loadRoutePolicy(projectRoot);
26281
+ for (const entry of roster) {
26282
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
26283
+ const record = entry;
26284
+ if (typeof record.route !== "string" || record.route.length === 0) continue;
26285
+ const modelPath = join17(projectRoot, ".kxm", "models", `${record.route}.yaml`);
26286
+ const doc = readModelDocument(modelPath);
26287
+ if (!doc) {
26288
+ return {
26289
+ error: {
26290
+ reason: "step_unsupported",
26291
+ field: "model",
26292
+ detail: `producer_route_unsupported: model file '.kxm/models/${record.route}.yaml' is missing or unreadable`
26293
+ }
26294
+ };
26295
+ }
26296
+ if (doc.status !== "admitted") continue;
26297
+ const modelName = typeof doc.model === "string" ? doc.model : "";
26298
+ const vendor = typeof doc.vendor === "string" ? doc.vendor : "";
26299
+ const harness = typeof doc.harness === "string" ? doc.harness : "";
26300
+ if (!modelName || !vendor || !harness) continue;
26301
+ const candidates = [modelName, `${vendor}/${modelName}`, `${harness}/${modelName}`];
26302
+ const admittedSelector = candidates.find((candidate) => policy.admitted.includes(candidate));
26303
+ const disabledSelector = candidates.find((candidate) => policy.disabled.includes(candidate));
26304
+ if (!admittedSelector && disabledSelector) {
26305
+ return {
26306
+ error: {
26307
+ reason: "step_unsupported",
26308
+ field: "model",
26309
+ detail: `producer_route_unsupported: first admitted route '${record.route}' for role '${role}' is disabled`
26310
+ }
26311
+ };
26312
+ }
26313
+ const selector = admittedSelector ?? (modelName.includes("/") ? modelName : `${vendor}/${modelName}`);
26314
+ if (policy.disabled.includes(selector)) {
26315
+ return {
26316
+ error: {
26317
+ reason: "step_unsupported",
26318
+ field: "model",
26319
+ detail: `producer_route_unsupported: first admitted route '${record.route}' for role '${role}' is disabled`
26320
+ }
26321
+ };
26322
+ }
26323
+ if (!policy.admitted.includes(selector)) {
26324
+ return {
26325
+ error: {
26326
+ reason: "step_unsupported",
26327
+ field: "model",
26328
+ detail: `producer_route_unsupported: model '${selector}' is not admitted`
26329
+ }
26330
+ };
26331
+ }
26332
+ const slash = selector.indexOf("/");
26333
+ if (slash <= 0 || slash === selector.length - 1) {
26334
+ return {
26335
+ error: {
26336
+ reason: "step_unsupported",
26337
+ field: "model",
26338
+ detail: `producer_route_unsupported: invalid selector '${selector}'`
26339
+ }
26340
+ };
26341
+ }
26342
+ const permissions = Array.isArray(doc.permissions) ? doc.permissions.filter((item) => typeof item === "string") : [];
24362
26343
  return {
24363
- error: {
24364
- reason: "step_unsupported",
24365
- field: "model",
24366
- detail: `producer_route_unsupported: model '${selector}' not in role '${role}' roster`
24367
- }
26344
+ provider: selector.slice(0, slash),
26345
+ model: selector.slice(slash + 1),
26346
+ selector,
26347
+ harness,
26348
+ routeId: record.route,
26349
+ role,
26350
+ permissions,
26351
+ ...typeof record.effort === "string" ? { effort: record.effort } : {}
24368
26352
  };
24369
26353
  }
24370
- const provider = selector.slice(0, slash);
24371
- const model = selector.slice(slash + 1);
24372
- return { provider, model, selector };
26354
+ return {
26355
+ error: {
26356
+ reason: "step_unsupported",
26357
+ field: "model",
26358
+ detail: `producer_route_unsupported: role '${role}' has no admitted route`
26359
+ }
26360
+ };
24373
26361
  }
24374
26362
  function unsupportedAgentStepTimeout(step, limitMs) {
24375
26363
  if (step.kind !== "agent" && step.kind !== "moa") return void 0;
@@ -24384,9 +26372,9 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
24384
26372
  const workflow = bundle.workflows.get(workflowId);
24385
26373
  if (!workflow) throw runtimeError("run_workflow_unknown", workflowId, `workflow ${workflowId} does not exist in this project`);
24386
26374
  const plan = compileKxmWorkflow({ id: workflowId, value: workflow.value, logicalPath: workflow.logicalPath });
24387
- const envelope = { plan, projectLimits: kxmProjectAdmissionLimits(bundle), gates: pinnedGatesForCompiledPlan(plan, bundle, projectRoot) };
26375
+ const envelope2 = { plan, projectLimits: kxmProjectAdmissionLimits(bundle), gates: pinnedGatesForCompiledPlan(plan, bundle, projectRoot) };
24388
26376
  const prerequisites = [];
24389
- const limit = unsupportedLimit(envelope);
26377
+ const limit = unsupportedLimit(envelope2);
24390
26378
  if (limit) prerequisites.push(limit);
24391
26379
  const visited = /* @__PURE__ */ new Set();
24392
26380
  const pending = [plan.entryStepId];
@@ -24398,8 +26386,8 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
24398
26386
  for (const transition2 of Object.values(step.transitions)) {
24399
26387
  if (transition2.to === "step") pending.push(transition2.target);
24400
26388
  }
24401
- const limitMs = envelope.projectLimits.agentStepTimeoutMs ?? KXM_DEFAULT_AGENT_STEP_TIMEOUT_MS;
24402
- const unsupported = step.kind === "gate" ? unsupportedGateStep(plan, step, envelope, { projectRoot }) ?? starterGatePrerequisite(step, envelope.gates, projectRoot) : unsupportedAgentStepTimeout(step, limitMs) ?? unsupportedStep(plan, step, "oneshot");
26389
+ const limitMs = envelope2.projectLimits.agentStepTimeoutMs ?? KXM_DEFAULT_AGENT_STEP_TIMEOUT_MS;
26390
+ const unsupported = step.kind === "gate" ? unsupportedGateStep(plan, step, envelope2, { projectRoot }) ?? starterGatePrerequisite(step, envelope2.gates, projectRoot) : unsupportedAgentStepTimeout(step, limitMs) ?? unsupportedStep(plan, step, "oneshot");
24403
26391
  if (unsupported) {
24404
26392
  prerequisites.push({ ...unsupported, stepId });
24405
26393
  continue;
@@ -24407,26 +26395,26 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
24407
26395
  if (step.kind !== "agent" && step.kind !== "moa") continue;
24408
26396
  const agentIds = step.assignments.allowedAgents.length > 0 ? step.assignments.allowedAgents : [step.agent];
24409
26397
  for (const agentId of agentIds) {
24410
- const agent = bundle.agents.get(agentId);
24411
- const harness = String(agent?.value.harness ?? bundle.project.value.defaultHarness ?? "pi");
26398
+ const route = resolveProducerRoute(projectRoot, step, agentId);
26399
+ if ("error" in route) {
26400
+ const detail = route.error.detail === "producer_route_unsupported: agent step model is not honored" ? `${agentId}: agent step model is not honored; remove model from the step` : `${agentId}: ${route.error.detail}; set role in .kxm/agents/${agentId}.yaml and admit the route model with kxm routes admit --model <provider/model>`;
26401
+ prerequisites.push({ ...route.error, stepId, detail });
26402
+ continue;
26403
+ }
26404
+ const harness = route.harness;
24412
26405
  const permission = Object.values(step.repositories).includes("write") ? "edit" : "read-only";
24413
26406
  if (!BUILTIN_HARNESSES.some((entry) => entry.id === harness && entry.oneShot) || !oneShotPermissionArgs(harness, permission)) {
24414
26407
  prerequisites.push({ reason: "step_unsupported", stepId, field: "harness", detail: `${agentId}: ${harness} has no audited ${permission} one-shot profile; use a supported workflow or execute this work directly in ${harness}, without substituting another harness` });
24415
26408
  continue;
24416
26409
  }
24417
- const route = resolveProducerRoute(projectRoot, step, agentId);
24418
- if ("error" in route) {
24419
- prerequisites.push({ ...route.error, stepId, detail: `${agentId}: ${route.error.detail}; configure its model in .kxm/agents/${agentId}.yaml and admit the installed model with kxm routes admit --model <provider/model>` });
24420
- continue;
24421
- }
24422
- const writeRefusal = unsupportedLiveWrite(projectRoot, step, agentId, route.selector, envelope.projectLimits.maxConcurrentRuns);
26410
+ const writeRefusal = unsupportedLiveWrite(projectRoot, step, route, envelope2.projectLimits.maxConcurrentRuns);
24423
26411
  if (writeRefusal) {
24424
26412
  prerequisites.push({ ...writeRefusal, stepId });
24425
26413
  continue;
24426
26414
  }
24427
26415
  const compatible = validateHarnessModelPair(harness, route);
24428
26416
  if (!compatible.valid) {
24429
- prerequisites.push({ reason: "step_unsupported", stepId, field: "model", detail: `${agentId}: ${compatible.message ?? `${harness} cannot run ${route.selector}`}; configure a model supported by ${harness} in .kxm/agents/${agentId}.yaml` });
26417
+ prerequisites.push({ reason: "step_unsupported", stepId, field: "model", detail: `${agentId}: ${compatible.message ?? `${harness} cannot run ${route.selector}`}; configure a model supported by ${harness} on the role roster` });
24430
26418
  }
24431
26419
  }
24432
26420
  }
@@ -24449,11 +26437,11 @@ function starterGatePrerequisite(step, gates, projectRoot) {
24449
26437
  }
24450
26438
  return void 0;
24451
26439
  }
24452
- function unsupportedLimit(envelope) {
24453
- if (envelope.plan.limits.maxAgentTimeMs !== void 0) {
26440
+ function unsupportedLimit(envelope2) {
26441
+ if (envelope2.plan.limits.maxAgentTimeMs !== void 0) {
24454
26442
  return { reason: "limit_unsupported", field: "limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
24455
26443
  }
24456
- if (envelope.projectLimits.maxAgentTimeMs !== void 0) {
26444
+ if (envelope2.projectLimits.maxAgentTimeMs !== void 0) {
24457
26445
  return { reason: "limit_unsupported", field: "project.limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
24458
26446
  }
24459
26447
  return void 0;
@@ -24499,25 +26487,7 @@ function unsupportedStep(plan, step, producerId) {
24499
26487
  }
24500
26488
  return void 0;
24501
26489
  }
24502
- function readYamlRecord(path4) {
24503
- if (!existsSync17(path4)) return void 0;
24504
- try {
24505
- const parsed = (0, import_yaml9.parse)(readFileSync15(path4, "utf8"));
24506
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
24507
- } catch {
24508
- return void 0;
24509
- }
24510
- return void 0;
24511
- }
24512
- function agentHarness(projectRoot, agentId) {
24513
- const harness = readYamlRecord(join17(projectRoot, ".kxm", "agents", `${agentId}.yaml`))?.harness;
24514
- return typeof harness === "string" && harness.length > 0 ? harness : void 0;
24515
- }
24516
- function projectDefaultHarness(projectRoot) {
24517
- const harness = readYamlRecord(join17(projectRoot, ".kxm", "project.yaml"))?.defaultHarness;
24518
- return typeof harness === "string" && harness.length > 0 ? harness : "pi";
24519
- }
24520
- function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurrentRuns) {
26490
+ function unsupportedLiveWrite(projectRoot, step, route, maxConcurrentRuns) {
24521
26491
  if (!Object.values(step.repositories).some((access) => access === "write")) return void 0;
24522
26492
  if (step.assignments.maximum !== 1) {
24523
26493
  return {
@@ -24533,52 +26503,33 @@ function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurren
24533
26503
  detail: "live write steps require limits.maxConcurrentRuns of 1; concurrent runs share one checkout"
24534
26504
  };
24535
26505
  }
24536
- const harness = agentHarness(projectRoot, agentId) ?? projectDefaultHarness(projectRoot);
24537
- if (!oneShotWriterArgs(harness)) {
26506
+ if (!oneShotWriterArgs(route.harness)) {
24538
26507
  return {
24539
26508
  reason: "step_unsupported",
24540
26509
  field: "repositories",
24541
- detail: `live write steps require an audited writer profile; ${harness} has none`
24542
- };
24543
- }
24544
- const rosterPath = join17(projectRoot, ".kxm", "roster.yaml");
24545
- if (!existsSync17(rosterPath)) return void 0;
24546
- const roster = readYamlRecord(rosterPath);
24547
- if (!roster || roster.schema !== "kxm.developer-roster.v1") {
24548
- return { reason: "step_unsupported", field: "model", detail: "live write steps require a readable kxm.developer-roster.v1" };
24549
- }
24550
- const routes = roster.routes;
24551
- const lineup = roster.lineup;
24552
- const writerIds = lineup && typeof lineup === "object" && !Array.isArray(lineup) ? lineup.writer : void 0;
24553
- if (!routes || typeof routes !== "object" || Array.isArray(routes) || !Array.isArray(writerIds)) {
24554
- return { reason: "step_unsupported", field: "model", detail: "developer roster has no writer lineup" };
24555
- }
24556
- const allowed = writerIds.some((id) => {
24557
- if (typeof id !== "string") return false;
24558
- const route = routes[id];
24559
- if (!route || typeof route !== "object" || Array.isArray(route)) return false;
24560
- const record = route;
24561
- if (record.harness !== harness || record.status !== "admitted") return false;
24562
- if (!Array.isArray(record.permissions) || !record.permissions.includes("edit")) return false;
24563
- const model = typeof record.model === "string" ? record.model : "";
24564
- const vendor = typeof record.vendor === "string" ? record.vendor : "";
24565
- return model === selector || vendor.length > 0 && `${vendor}/${model}` === selector;
24566
- });
24567
- if (!allowed) {
26510
+ detail: `live write steps require an audited writer profile; ${route.harness} has none`
26511
+ };
26512
+ }
26513
+ const writerIds = listRoleBindings(projectRoot).writer;
26514
+ if (!Array.isArray(writerIds) || writerIds.length === 0) {
26515
+ return { reason: "step_unsupported", field: "model", detail: "writer roster has no admitted edit route" };
26516
+ }
26517
+ const named = writerIds.includes(route.routeId);
26518
+ if (!named || !route.permissions.includes("edit")) {
24568
26519
  return {
24569
26520
  reason: "step_unsupported",
24570
26521
  field: "model",
24571
- detail: `live write route ${selector} on ${harness} is not on the developer roster writer lineup`
26522
+ detail: `live write route ${route.selector} on ${route.harness} is not an admitted edit route on the writer roster`
24572
26523
  };
24573
26524
  }
24574
26525
  return void 0;
24575
26526
  }
24576
- function unsupportedGateStep(plan, step, envelope, context) {
26527
+ function unsupportedGateStep(plan, step, envelope2, context) {
24577
26528
  if (!step.gate) return { reason: "step_unsupported", field: "gate", detail: "gate step is missing gate id" };
24578
- if (envelope.gates.registry === null) {
26529
+ if (envelope2.gates.registry === null) {
24579
26530
  return { reason: "step_unsupported", field: "gate", detail: `step ${step.id} refers to a gate but registry is null` };
24580
26531
  }
24581
- const definition = envelope.gates.definitions[step.gate];
26532
+ const definition = envelope2.gates.definitions[step.gate];
24582
26533
  if (!definition) {
24583
26534
  return { reason: "step_unsupported", field: "gate", detail: `gate definition ${step.gate} not found in registry` };
24584
26535
  }
@@ -24684,7 +26635,7 @@ function unsupportedGateStep(plan, step, envelope, context) {
24684
26635
  return { reason: "gate_outcome_undeclared", field: "outcomes", detail: "step expects fail but missing required failure outcome; declare implementation-failure or failed" };
24685
26636
  }
24686
26637
  }
24687
- if (context && projectRuntimeKey(context.projectRoot) !== envelope.gates.controlRoot.projectKey) {
26638
+ if (context && projectRuntimeKey(context.projectRoot) !== envelope2.gates.controlRoot.projectKey) {
24688
26639
  throw runtimeError("run_owner_mismatch", plan.workflowId, `gate control root does not match project`);
24689
26640
  }
24690
26641
  if (definition.kind === "artifacts-exist" && step.expect === "fail") {
@@ -24754,21 +26705,21 @@ var init_oneshot_producer = __esm({
24754
26705
  // plugins/kxm/src/hub-binding.ts
24755
26706
  import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync16, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "node:fs";
24756
26707
  import { homedir as homedir5 } from "node:os";
24757
- import { dirname as dirname11, isAbsolute as isAbsolute6, join as join18, resolve as resolve12 } from "node:path";
26708
+ import { dirname as dirname11, isAbsolute as isAbsolute7, join as join18, resolve as resolve12 } from "node:path";
24758
26709
  function resolveUserStateRoot2(env) {
24759
26710
  const explicit = env.KXM_STATE_HOME?.trim();
24760
26711
  if (explicit) {
24761
- if (!isAbsolute6(explicit)) throw new HubBindingError("local_state_root_not_absolute");
26712
+ if (!isAbsolute7(explicit)) throw new HubBindingError("local_state_root_not_absolute");
24762
26713
  return resolve12(explicit);
24763
26714
  }
24764
26715
  if (process.platform === "win32") {
24765
26716
  const localAppData = env.LOCALAPPDATA?.trim();
24766
- const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join18(homedir5(), "AppData", "Local");
26717
+ const base2 = localAppData && isAbsolute7(localAppData) ? localAppData : join18(homedir5(), "AppData", "Local");
24767
26718
  return resolve12(base2, "KXM");
24768
26719
  }
24769
26720
  if (process.platform === "darwin") return resolve12(homedir5(), "Library", "Application Support", "KXM");
24770
26721
  const xdgState = env.XDG_STATE_HOME?.trim();
24771
- const base = xdgState && isAbsolute6(xdgState) ? xdgState : join18(homedir5(), ".local", "state");
26722
+ const base = xdgState && isAbsolute7(xdgState) ? xdgState : join18(homedir5(), ".local", "state");
24772
26723
  return resolve12(base, "kxm");
24773
26724
  }
24774
26725
  function hubBindingFile(env = process.env) {
@@ -24904,9 +26855,9 @@ var init_logger = __esm({
24904
26855
 
24905
26856
  // plugins/kxm/src/runtime-supervisor.ts
24906
26857
  import { spawn as spawn2 } from "node:child_process";
24907
- import { createHash as createHash15, createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
26858
+ import { createHash as createHash16, createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
24908
26859
  import { chmodSync as chmodSync4, existsSync as existsSync19, lstatSync as lstatSync6, mkdirSync as mkdirSync13, readFileSync as readFileSync17, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync14 } from "node:fs";
24909
- import { dirname as dirname12, isAbsolute as isAbsolute7, join as join19 } from "node:path";
26860
+ import { dirname as dirname12, isAbsolute as isAbsolute8, join as join19 } from "node:path";
24910
26861
  function kxmSupervisorTokenFile(paths) {
24911
26862
  return join19(paths.runtimeDir, "supervisor.token");
24912
26863
  }
@@ -24947,6 +26898,9 @@ function readRecentSupervisorError(paths) {
24947
26898
  return void 0;
24948
26899
  }
24949
26900
  }
26901
+ function kxmSupervisorRecordIsLive(record) {
26902
+ return supervisorStatusOf(record).running;
26903
+ }
24950
26904
  function supervisorStatusOf(record) {
24951
26905
  if (!record) return { running: false };
24952
26906
  const heartbeatAgeMs = Date.now() - Date.parse(record.heartbeatAt);
@@ -25098,7 +27052,6 @@ var init_runtime_supervisor = __esm({
25098
27052
  init_sqlite();
25099
27053
  init_runtime_service();
25100
27054
  init_oneshot_producer();
25101
- init_routes();
25102
27055
  init_engine();
25103
27056
  init_runtime_owner();
25104
27057
  init_client();
@@ -25284,7 +27237,7 @@ function refuseDryRun(io, jsonMode, command, reason) {
25284
27237
  }
25285
27238
  function printWorker(runtime, worker, payload, text, outcome) {
25286
27239
  const sessionId = runtime.env.KXM_SESSION_ID?.trim();
25287
- const envelope = workerResult(worker, {
27240
+ const envelope2 = workerResult(worker, {
25288
27241
  ...payload,
25289
27242
  summary: text,
25290
27243
  ...outcome ? { outcome } : {},
@@ -25292,7 +27245,7 @@ function printWorker(runtime, worker, payload, text, outcome) {
25292
27245
  });
25293
27246
  if (!runtime.dryRun) {
25294
27247
  try {
25295
- const safeEnvelope = redactCliValue(envelope);
27248
+ const safeEnvelope = redactCliValue(envelope2);
25296
27249
  appendTelemetry(telemetryPath(runtime.dirs.logs), makeTelemetryEvent({
25297
27250
  envelope: safeEnvelope,
25298
27251
  ...sessionId ? { sessionId } : {},
@@ -25305,7 +27258,7 @@ function printWorker(runtime, worker, payload, text, outcome) {
25305
27258
  } catch {
25306
27259
  }
25307
27260
  }
25308
- print(runtime.io, runtime.json, envelope, text);
27261
+ print(runtime.io, runtime.json, envelope2, text);
25309
27262
  }
25310
27263
  function hostMode(runtime) {
25311
27264
  try {
@@ -25561,8 +27514,8 @@ var init_model_inventory = __esm({
25561
27514
  });
25562
27515
 
25563
27516
  // plugins/kxm/src/permission.ts
25564
- import { spawnSync as spawnSync4 } from "node:child_process";
25565
- import { createHash as createHash16 } from "node:crypto";
27517
+ import { spawnSync as spawnSync5 } from "node:child_process";
27518
+ import { createHash as createHash17 } from "node:crypto";
25566
27519
  import { existsSync as existsSync24, mkdtempSync, mkdirSync as mkdirSync20, readFileSync as readFileSync24, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "node:fs";
25567
27520
  import { tmpdir } from "node:os";
25568
27521
  import { dirname as dirname14, join as join28, resolve as resolve19, sep as sep4 } from "node:path";
@@ -25766,7 +27719,7 @@ function kxmProseEntries(resource) {
25766
27719
  return entries;
25767
27720
  }
25768
27721
  function valueHash(canonicalValue) {
25769
- return `sha256:${createHash16("sha256").update(canonicalValue, "utf8").digest("hex")}`;
27722
+ return `sha256:${createHash17("sha256").update(canonicalValue, "utf8").digest("hex")}`;
25770
27723
  }
25771
27724
  function rankDirection(rank, baseValue, candidateValue) {
25772
27725
  const base = JSON.parse(baseValue);
@@ -26035,7 +27988,7 @@ function gitEnvironment2() {
26035
27988
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
26036
27989
  }
26037
27990
  function git(root, args) {
26038
- const result = spawnSync4("git", ["-C", root, ...args], {
27991
+ const result = spawnSync5("git", ["-C", root, ...args], {
26039
27992
  encoding: "utf8",
26040
27993
  env: gitEnvironment2(),
26041
27994
  timeout: 15e3,
@@ -26053,7 +28006,7 @@ function git(root, args) {
26053
28006
  return result.stdout;
26054
28007
  }
26055
28008
  function gitBuffer(root, args) {
26056
- const result = spawnSync4("git", ["-C", root, ...args], {
28009
+ const result = spawnSync5("git", ["-C", root, ...args], {
26057
28010
  env: gitEnvironment2(),
26058
28011
  timeout: 15e3,
26059
28012
  windowsHide: true,
@@ -26086,7 +28039,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
26086
28039
  const paths = listing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
26087
28040
  const shadow = mkdtempSync(join28(tmpdir(), "kxm-permission-base-"));
26088
28041
  const shadowResolved = resolve19(shadow);
26089
- const shadowMembersDir = `.kxm-shadow-members-${createHash16("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
28042
+ const shadowMembersDir = `.kxm-shadow-members-${createHash17("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
26090
28043
  try {
26091
28044
  for (const path4 of paths) {
26092
28045
  const segments = path4.split("/");
@@ -26251,7 +28204,7 @@ function loadBaseProjectDeclarations(projectFile) {
26251
28204
  return members;
26252
28205
  }
26253
28206
  function initShadowGitRoot(directory) {
26254
- const result = spawnSync4("git", ["-c", "init.defaultBranch=main", "init", "--quiet", directory], {
28207
+ const result = spawnSync5("git", ["-c", "init.defaultBranch=main", "init", "--quiet", directory], {
26255
28208
  encoding: "utf8",
26256
28209
  env: gitEnvironment2(),
26257
28210
  timeout: 1e4,
@@ -26306,7 +28259,7 @@ var init_permission = __esm({
26306
28259
  });
26307
28260
 
26308
28261
  // plugins/kxm/src/repair.ts
26309
- import { randomUUID as randomUUID10 } from "node:crypto";
28262
+ import { randomUUID as randomUUID11 } from "node:crypto";
26310
28263
  import {
26311
28264
  chmodSync as chmodSync5,
26312
28265
  closeSync as closeSync3,
@@ -26322,7 +28275,7 @@ import {
26322
28275
  rmSync as rmSync9,
26323
28276
  writeFileSync as writeFileSync20
26324
28277
  } from "node:fs";
26325
- import { dirname as dirname15, isAbsolute as isAbsolute8, join as join29, relative as relative5, resolve as resolve20 } from "node:path";
28278
+ import { dirname as dirname15, isAbsolute as isAbsolute9, join as join29, relative as relative5, resolve as resolve20 } from "node:path";
26326
28279
  function resourceKindForTemplatePath(path4) {
26327
28280
  if (path4 === ".kxm/project.yaml") return "project";
26328
28281
  if (path4 === ".kxm/gates.yaml") return "gate-registry";
@@ -26409,7 +28362,7 @@ function backupFile(projectRoot, portablePath2) {
26409
28362
  return join29(transactionRoot(projectRoot), "backups", ...portablePath2.split("/"));
26410
28363
  }
26411
28364
  function portableManagedPath(path4) {
26412
- return path4.startsWith(".kxm/") && path4 !== KXM_TEMPLATE_PROVENANCE_PATH && path4.length <= 1024 && !path4.includes("\\") && !isAbsolute8(path4) && PORTABLE_PATH.test(path4) && !/[<>:"|?*]/.test(path4);
28365
+ return path4.startsWith(".kxm/") && path4 !== KXM_TEMPLATE_PROVENANCE_PATH && path4.length <= 1024 && !path4.includes("\\") && !isAbsolute9(path4) && PORTABLE_PATH.test(path4) && !/[<>:"|?*]/.test(path4);
26413
28366
  }
26414
28367
  function readRegularBounded(file, label) {
26415
28368
  const stat = lstatSync7(file);
@@ -26596,7 +28549,7 @@ function operationPlanSha(operation) {
26596
28549
  function writeOperation(projectRoot, operation) {
26597
28550
  const root = transactionRoot(projectRoot);
26598
28551
  const file = operationFile(projectRoot);
26599
- const temporary = join29(root, `.operation-${operation.operationId}-${randomUUID10()}.tmp`);
28552
+ const temporary = join29(root, `.operation-${operation.operationId}-${randomUUID11()}.tmp`);
26600
28553
  try {
26601
28554
  writeDurableNew(temporary, Buffer.from(`${JSON.stringify(operation, null, 2)}
26602
28555
  `, "utf8"), 384);
@@ -26640,7 +28593,7 @@ function readOperation(projectRoot, schemasDir) {
26640
28593
  seen.add(folded);
26641
28594
  prior = entry.path;
26642
28595
  }
26643
- if (!isAbsolute8(operation.projectRoot)) fail3("init_transaction_project_root_invalid", TRANSACTION_NAME, "operation project root must be absolute", "path");
28596
+ if (!isAbsolute9(operation.projectRoot)) fail3("init_transaction_project_root_invalid", TRANSACTION_NAME, "operation project root must be absolute", "path");
26644
28597
  validateOperationIntent(projectRoot, operation);
26645
28598
  return operation;
26646
28599
  }
@@ -26788,7 +28741,7 @@ function createOperation(projectRoot, kind, rendered, files, sourceTemplateRevis
26788
28741
  const root = resolve20(projectRoot);
26789
28742
  const operationWithoutHash = {
26790
28743
  schema: "kxm.init-operation.v1",
26791
- operationId: `op_${randomUUID10().replaceAll("-", "")}`,
28744
+ operationId: `op_${randomUUID11().replaceAll("-", "")}`,
26792
28745
  kind,
26793
28746
  projectRoot: root,
26794
28747
  projectId: rendered.projectId,
@@ -26852,7 +28805,7 @@ function destinationSha(projectRoot, path4) {
26852
28805
  return kxmContentSha256(readRegularBounded(file, path4));
26853
28806
  }
26854
28807
  function requireConditionalLinkSupport(directory, operationId, path4) {
26855
- const nonce = randomUUID10();
28808
+ const nonce = randomUUID11();
26856
28809
  const source = join29(directory, `.kxm-repair-${operationId}-${nonce}.link-source`);
26857
28810
  const target = join29(directory, `.kxm-repair-${operationId}-${nonce}.link-target`);
26858
28811
  try {
@@ -26906,7 +28859,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
26906
28859
  requireConditionalLinkSupport(directory, operation.operationId, entry.path);
26907
28860
  const bytes = readTarget(projectRoot, entry.path);
26908
28861
  if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
26909
- const temporary = join29(directory, `${temporaryPrefix}${randomUUID10()}.tmp`);
28862
+ const temporary = join29(directory, `${temporaryPrefix}${randomUUID11()}.tmp`);
26910
28863
  const mode = existsSync25(destination) ? lstatSync7(destination).mode & 511 : 420;
26911
28864
  try {
26912
28865
  writeDurableNew(temporary, bytes, mode);
@@ -27182,7 +29135,7 @@ function commitKxmInitTransaction(projectRoot, schemasDir) {
27182
29135
  fail3("init_transaction_not_verified", TRANSACTION_NAME, "initialization transaction cannot commit before installed state is verified");
27183
29136
  }
27184
29137
  const transaction = transactionRoot(projectRoot);
27185
- const retired = join29(projectRoot, `${TRANSACTION_NAME}-cleanup-${operation.operationId}-${randomUUID10()}`);
29138
+ const retired = join29(projectRoot, `${TRANSACTION_NAME}-cleanup-${operation.operationId}-${randomUUID11()}`);
27186
29139
  renameSync7(transaction, retired);
27187
29140
  syncDirectory2(projectRoot);
27188
29141
  rmSync9(retired, { recursive: true, force: true });
@@ -27213,7 +29166,7 @@ var init_repair = __esm({
27213
29166
  });
27214
29167
 
27215
29168
  // plugins/kxm/src/init.ts
27216
- import { randomUUID as randomUUID11 } from "node:crypto";
29169
+ import { randomUUID as randomUUID12 } from "node:crypto";
27217
29170
  import { existsSync as existsSync26 } from "node:fs";
27218
29171
  import { basename as basename6, join as join30 } from "node:path";
27219
29172
  function initIssue(code, file, message) {
@@ -27227,7 +29180,7 @@ function normalizedProjectName(root, requested) {
27227
29180
  return name;
27228
29181
  }
27229
29182
  function generatedProjectId(requested) {
27230
- const id = requested?.trim() || `prj_${randomUUID11().replaceAll("-", "")}`;
29183
+ const id = requested?.trim() || `prj_${randomUUID12().replaceAll("-", "")}`;
27231
29184
  if (!PROJECT_ID.test(id) || id.length > 144) {
27232
29185
  throw new KxmConfigError([initIssue("project_id_invalid", ".kxm/project.yaml", "project ID must satisfy the kxm.project.v1 opaque ID grammar and use the prj_ prefix")]);
27233
29186
  }
@@ -28059,29 +30012,29 @@ function runEngineNotice(runId, defaultHarness, prerequisites) {
28059
30012
  "These are local Runtime runs, not webhook workflows; use kxm runs, not kxm workflow get."
28060
30013
  ].join("\n");
28061
30014
  }
28062
- function resolveKxmRunTarget(runtime, workflow, forTask = false, envelope = {}, allowDefaultWorkflow = false) {
30015
+ function resolveKxmRunTarget(runtime, workflow, forTask = false, envelope2 = {}, allowDefaultWorkflow = false) {
28063
30016
  if (runtime.workspaceFlag !== void 0) {
28064
30017
  print(runtime.io, runtime.json, {
28065
30018
  ok: false,
28066
30019
  command: "run",
28067
30020
  error: "workspace_option_unsupported",
28068
- ...envelope
30021
+ ...envelope2
28069
30022
  }, "kxm run discovers the authoritative project from the current directory; --workspace is not supported");
28070
30023
  return 2;
28071
30024
  }
28072
30025
  if (!workflow && !forTask && !allowDefaultWorkflow) {
28073
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required", ...envelope }, "usage: kxm run <workflow> [prompt]");
30026
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required", ...envelope2 }, "usage: kxm run <workflow> [prompt]");
28074
30027
  return 2;
28075
30028
  }
28076
30029
  const projectRoot = discoverKxmProjectRoot(runtime.cwd);
28077
30030
  if (!projectRoot) {
28078
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "project_required", ...envelope }, "kxm run requires a KXM project (run kxm init first)");
30031
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "project_required", ...envelope2 }, "kxm run requires a KXM project (run kxm init first)");
28079
30032
  return 1;
28080
30033
  }
28081
30034
  const bundle = loadKxmProject(projectRoot, {});
28082
30035
  const workflowId = workflow ?? String(bundle.project.value.defaultWorkflow ?? "default");
28083
30036
  if (!bundle.workflows.has(workflowId)) {
28084
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow: workflowId, ...envelope }, `workflow ${workflowId} does not exist in this project`);
30037
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow: workflowId, ...envelope2 }, `workflow ${workflowId} does not exist in this project`);
28085
30038
  return 1;
28086
30039
  }
28087
30040
  const defaultHarness = String(bundle.project.value.defaultHarness ?? "pi");
@@ -28101,9 +30054,9 @@ ${prerequisites.map((item) => `${item.stepId ?? workflowId}: ${item.detail}`).jo
28101
30054
  return { projectRoot, workflowId, configRevision: bundle.configRevision, defaultHarness, prerequisites };
28102
30055
  }
28103
30056
  async function cmdKxmRun(runtime, workflow, promptParts, forTask = false, call) {
28104
- const envelope = call?.brief !== void 0 ? { brief: call.brief } : {};
30057
+ const envelope2 = call?.brief !== void 0 ? { brief: call.brief } : {};
28105
30058
  try {
28106
- const target = resolveKxmRunTarget(runtime, workflow, forTask, envelope, call?.allowDefaultWorkflow === true);
30059
+ const target = resolveKxmRunTarget(runtime, workflow, forTask, envelope2, call?.allowDefaultWorkflow === true);
28107
30060
  if (typeof target === "number") return target;
28108
30061
  const { projectRoot } = target;
28109
30062
  if (runtime.dryRun) {
@@ -28111,7 +30064,7 @@ async function cmdKxmRun(runtime, workflow, promptParts, forTask = false, call)
28111
30064
  ok: true,
28112
30065
  command: "run",
28113
30066
  dryRun: true,
28114
- ...envelope,
30067
+ ...envelope2,
28115
30068
  ...target
28116
30069
  }, `run plan: workflow ${target.workflowId} at ${target.configRevision.slice(0, 19)}\u2026 (no run created)`);
28117
30070
  return 0;
@@ -28128,7 +30081,7 @@ async function cmdKxmRun(runtime, workflow, promptParts, forTask = false, call)
28128
30081
  print(runtime.io, runtime.json, {
28129
30082
  ok: true,
28130
30083
  command: "run",
28131
- ...envelope,
30084
+ ...envelope2,
28132
30085
  execution: {
28133
30086
  status: "not_started",
28134
30087
  mode: "live",
@@ -28150,10 +30103,10 @@ ${runEngineNotice(run.runId, target.defaultHarness, target.prerequisites)}`);
28150
30103
  return 0;
28151
30104
  } catch (error) {
28152
30105
  if (error instanceof KxmConfigError) {
28153
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_failed", issues: error.issues, ...envelope }, `run failed: ${error.message}`);
30106
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_failed", issues: error.issues, ...envelope2 }, `run failed: ${error.message}`);
28154
30107
  return 1;
28155
30108
  }
28156
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_io_failed", ...envelope }, "run failed because a local operation did not complete");
30109
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_io_failed", ...envelope2 }, "run failed because a local operation did not complete");
28157
30110
  return 1;
28158
30111
  }
28159
30112
  }
@@ -28210,8 +30163,9 @@ async function cmdKxmRunStatus(runtime, runId) {
28210
30163
  print(
28211
30164
  runtime.io,
28212
30165
  runtime.json,
28213
- { ok: true, command: "runs status", run, ...drive !== void 0 ? { drive } : {} },
28214
- `${formatRunStatusLine(run, drive)}${driveLine ? `
30166
+ { ok: true, command: "runs status", projectRoot, run, ...drive !== void 0 ? { drive } : {} },
30167
+ `${formatRunStatusLine(run, drive)}
30168
+ root ${projectRoot}${driveLine ? `
28215
30169
  ${driveLine}` : ""}`
28216
30170
  );
28217
30171
  return 0;
@@ -28479,8 +30433,7 @@ async function cmdModelsScreen(runtime) {
28479
30433
  try {
28480
30434
  while (true) {
28481
30435
  const policy = loadRoutePolicy(runtime.dirs.workdir);
28482
- const roleBindings = listRoleBindings(runtime.dirs.workdir);
28483
- runtime.io.stdout(models.map((m2, i) => `${i + 1}. ${m2} [${policy.admitted.includes(m2) ? "admitted" : policy.disabled.includes(m2) ? "disabled" : "unset"}] roles:${Object.entries(roleBindings).filter(([, xs]) => xs.includes(m2)).map(([r]) => r).join(",") || "-"}`).join("\n") + "\n");
30436
+ runtime.io.stdout(models.map((m2, i) => `${i + 1}. ${m2} [${policy.admitted.includes(m2) ? "admitted" : policy.disabled.includes(m2) ? "disabled" : "unset"}] roles:${rolesForInventoryModel(runtime.dirs.workdir, m2).join(",") || "-"}`).join("\n") + "\n");
28484
30437
  const command = (await ask("[a]dmit [d]isable [r]ole-add [x]ole-remove [q]uit: ")).trim().toLowerCase();
28485
30438
  if (command === "q" || command === "quit") return 0;
28486
30439
  const index = Number.parseInt((await ask("model number: ")).trim(), 10) - 1;
@@ -28502,7 +30455,8 @@ async function cmdModelsScreen(runtime) {
28502
30455
  }
28503
30456
  async function cmdRouteList(runtime) {
28504
30457
  const policy = loadRoutePolicy(runtime.dirs.workdir);
28505
- print(runtime.io, runtime.json, { ok: true, command: "routes list", policy }, [...policy.admitted.map((x2) => `admitted ${x2}`), ...policy.disabled.map((x2) => `disabled ${x2}`)].join("\n") || "no route decisions");
30458
+ const membership = Object.entries(listRoleBindings(runtime.dirs.workdir)).flatMap(([role, ids]) => ids.map((id) => `${role} ${id}`));
30459
+ print(runtime.io, runtime.json, { ok: true, command: "routes list", policy, membership }, [...policy.admitted.map((x2) => `admitted ${x2}`), ...policy.disabled.map((x2) => `disabled ${x2}`), ...membership].join("\n") || "no route decisions");
28506
30460
  return 0;
28507
30461
  }
28508
30462
  async function cmdRouteCount(runtime) {
@@ -33215,7 +35169,7 @@ init_runtime_supervisor();
33215
35169
  init_runtime_store();
33216
35170
  init_workflow();
33217
35171
  init_types();
33218
- import { randomUUID as randomUUID5 } from "node:crypto";
35172
+ import { randomUUID as randomUUID6 } from "node:crypto";
33219
35173
  import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
33220
35174
  import { join as join22, resolve as resolve14 } from "node:path";
33221
35175
  import { createInterface } from "node:readline";
@@ -33649,7 +35603,7 @@ async function cmdRoleResume(runtime, runId, ruling) {
33649
35603
  JSON.stringify(resumeResult.run),
33650
35604
  runId
33651
35605
  );
33652
- const journalId = randomUUID5();
35606
+ const journalId = randomUUID6();
33653
35607
  const journalPayload = {
33654
35608
  id: journalId,
33655
35609
  runId,
@@ -33695,7 +35649,7 @@ async function cmdRoleResume(runtime, runId, ruling) {
33695
35649
  // plugins/kxm/src/cli/workflows.ts
33696
35650
  var import_yaml12 = __toESM(require_dist(), 1);
33697
35651
  init_sqlite();
33698
- import { randomUUID as randomUUID7 } from "node:crypto";
35652
+ import { randomUUID as randomUUID8 } from "node:crypto";
33699
35653
  import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
33700
35654
  import { join as join24, resolve as resolve16 } from "node:path";
33701
35655
 
@@ -33943,7 +35897,7 @@ init_redact();
33943
35897
  // plugins/kxm/src/github-watch.ts
33944
35898
  init_redact();
33945
35899
  init_workflow();
33946
- import { randomUUID as randomUUID6 } from "node:crypto";
35900
+ import { randomUUID as randomUUID7 } from "node:crypto";
33947
35901
  async function fetchWithTimeout(fetchImpl, input, init, timeoutMs) {
33948
35902
  const controller = new AbortController();
33949
35903
  const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));
@@ -34045,7 +35999,7 @@ async function watchGithubChecks(input) {
34045
35999
  "workflow.signal": input.signalKey
34046
36000
  };
34047
36001
  const explicitDeliveryId = input.deliveryId?.trim();
34048
- const deliveryGeneration = randomUUID6();
36002
+ const deliveryGeneration = randomUUID7();
34049
36003
  let deliveryId = explicitDeliveryId || `github-watch:${deliveryGeneration}:pr-${input.pr}`;
34050
36004
  const deliver = async (status, summary, evidence) => {
34051
36005
  const boundedEvidence = Object.fromEntries(Object.entries({ ...contextEvidence, ...evidence }).slice(0, 64));
@@ -34786,7 +36740,7 @@ async function cmdWorkflowModify(runtime, workflowId, options) {
34786
36740
  }
34787
36741
  async function cmdWorkflowStart(runtime, definitionIdArg, options) {
34788
36742
  const definitionId = definitionIdArg || runtime.env.KXM_WORKFLOW_ID?.trim();
34789
- const deliveryId = String(options.deliveryId || `cli-${randomUUID7()}`);
36743
+ const deliveryId = String(options.deliveryId || `cli-${randomUUID8()}`);
34790
36744
  const event = options.event;
34791
36745
  const payloadFlag = options.payload ?? "{}";
34792
36746
  if (!definitionId) {
@@ -34917,7 +36871,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
34917
36871
  printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signal to KXM run");
34918
36872
  return 0;
34919
36873
  }
34920
- const deliveryId2 = String(deliveryIdFlag || `cli-signal:${randomUUID7()}`);
36874
+ const deliveryId2 = String(deliveryIdFlag || `cli-signal:${randomUUID8()}`);
34921
36875
  try {
34922
36876
  const supervisor = await ensureKxmSupervisor({ env: runtime.env });
34923
36877
  const posted = await kxmRuntimeRequest(
@@ -34953,7 +36907,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
34953
36907
  printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signed signal");
34954
36908
  return 0;
34955
36909
  }
34956
- const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID7()}`);
36910
+ const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID8()}`);
34957
36911
  try {
34958
36912
  const posted = await postWorkflowSignal({
34959
36913
  serverUrl: runtime.serverUrl,
@@ -35213,10 +37167,10 @@ function suggestWorkflowAndRoles(prompt, options = {}) {
35213
37167
  supported: true,
35214
37168
  prerequisites: [
35215
37169
  "Run kxm init in the target repository if needed, then install the exact template with the suggested command. If that workflow ID already exists, stop and review it; the commands below apply only to a newly installed template, not an existing definition with potentially different agents or permissions.",
35216
- ...roles.map((role) => `Set harness: ${role.harness} and model: <your authenticated compatible model selector> in .kxm/agents/${role.agent}.yaml. Admit that exact selector in .kxm/routes.yaml and ensure it is not disabled; this recommendation does not choose a model or change routing.`),
37170
+ ...roles.map((role) => `Set role on .kxm/agents/${role.agent}.yaml to a role whose roster names an admitted route for harness ${role.harness}. Admit that route's selector in .kxm/routes.yaml and ensure it is not disabled; this recommendation does not choose a model or change routing.`),
35217
37171
  ...writeStep ? [
35218
37172
  `Keep each writer step at assignments.maximum: 1 and set limits.maxConcurrentRuns: 1 in .kxm/project.yaml; the live ${selected.id} writer profile requires a lone writer in the checkout.`,
35219
- `If .kxm/roster.yaml exists, it must be readable kxm.developer-roster.v1 with a lineup.writer route matching harness: ${selected.id} and the selected writer model (model or vendor/model), status: admitted, and permissions containing edit.`,
37173
+ `The writer role roster must name a route whose harness is ${selected.id}, whose status is admitted, and whose permissions contain edit.`,
35220
37174
  "Configure the test gate in .kxm/gates.yaml with kind: command and argv for this repository's actual verification command. A scaffold/example command or simulation is not evidence that the implementation works."
35221
37175
  ] : [],
35222
37176
  `Validate the installed workflow with kxm gate validate --file .kxm/workflows/${bestPattern.id}.yaml and the project configuration with kxm init --dry-run before creating a run.`
@@ -35232,7 +37186,7 @@ function suggestWorkflowAndRoles(prompt, options = {}) {
35232
37186
 
35233
37187
  // plugins/kxm/src/task-manager.ts
35234
37188
  var import_yaml13 = __toESM(require_dist(), 1);
35235
- import { randomUUID as randomUUID8 } from "node:crypto";
37189
+ import { randomUUID as randomUUID9 } from "node:crypto";
35236
37190
  import { existsSync as existsSync23, mkdirSync as mkdirSync17, readFileSync as readFileSync22, readdirSync as readdirSync8, writeFileSync as writeFileSync17 } from "node:fs";
35237
37191
  import { join as join25, resolve as resolve17 } from "node:path";
35238
37192
  var GOAL_SCHEMA = "kxm.goal.v1";
@@ -35250,7 +37204,7 @@ function taskFilePath(repoRoot, taskId) {
35250
37204
  return join25(tasksDirectory(repoRoot), `${taskId}.yaml`);
35251
37205
  }
35252
37206
  function createGoal(repoRoot, input, options = {}) {
35253
- const id = `goal_${randomUUID8().replaceAll("-", "").slice(0, 12)}`;
37207
+ const id = `goal_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
35254
37208
  const now = (/* @__PURE__ */ new Date()).toISOString();
35255
37209
  const record = {
35256
37210
  schema: GOAL_SCHEMA,
@@ -35286,7 +37240,7 @@ function listGoals(repoRoot) {
35286
37240
  return goals.sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
35287
37241
  }
35288
37242
  function createTask(repoRoot, input, options = {}) {
35289
- const id = `task_${randomUUID8().replaceAll("-", "").slice(0, 12)}`;
37243
+ const id = `task_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
35290
37244
  const now = (/* @__PURE__ */ new Date()).toISOString();
35291
37245
  const record = {
35292
37246
  schema: TASK_SCHEMA,
@@ -35367,7 +37321,7 @@ init_engine_compile();
35367
37321
 
35368
37322
  // plugins/kxm/src/studio-layout.ts
35369
37323
  import { createServer } from "node:http";
35370
- import { randomUUID as randomUUID9 } from "node:crypto";
37324
+ import { randomUUID as randomUUID10 } from "node:crypto";
35371
37325
  var STUDIO_LAYOUT_SCHEMA = "kxm.studio-layout.v1";
35372
37326
  function generateStudioLayout(plan, state) {
35373
37327
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -35630,7 +37584,7 @@ function createStudioServer(options = {}) {
35630
37584
  }));
35631
37585
  return;
35632
37586
  }
35633
- const mutationId = `mut_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
37587
+ const mutationId = `mut_${randomUUID10().replaceAll("-", "").slice(0, 12)}`;
35634
37588
  if (options.onMutation) {
35635
37589
  try {
35636
37590
  const outcome = await options.onMutation(command, args);
@@ -36904,7 +38858,7 @@ init_project();
36904
38858
  init_repo_root();
36905
38859
  init_harness();
36906
38860
  init_types();
36907
- import { spawnSync as spawnSync5 } from "node:child_process";
38861
+ import { spawnSync as spawnSync6 } from "node:child_process";
36908
38862
  var PLUGIN_SUPPORTED_HARNESSES = Object.freeze(["pi", "omp", "claude"]);
36909
38863
  async function cmdPluginInstall(runtime, options) {
36910
38864
  const repoRoot = findKxmRepoRoot(import.meta.url);
@@ -36943,7 +38897,7 @@ async function cmdPluginInstall(runtime, options) {
36943
38897
  detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`
36944
38898
  });
36945
38899
  } else {
36946
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync5(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
38900
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync6(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
36947
38901
  if (run.error || run.status !== 0 && run.status !== null) {
36948
38902
  results.push({
36949
38903
  harness: "pi",
@@ -36974,7 +38928,7 @@ async function cmdPluginInstall(runtime, options) {
36974
38928
  detail: `would install kxm plugin into omp: ${command} ${args.join(" ")}`
36975
38929
  });
36976
38930
  } else {
36977
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync5(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
38931
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync6(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
36978
38932
  if (run.error || run.status !== 0 && run.status !== null) {
36979
38933
  results.push({
36980
38934
  harness: "omp",
@@ -37009,9 +38963,9 @@ async function cmdPluginInstall(runtime, options) {
37009
38963
  if (runtime.io.spawnSync) {
37010
38964
  runtime.io.spawnSync(command, marketplaceArgs);
37011
38965
  } else {
37012
- spawnSync5(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
38966
+ spawnSync6(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
37013
38967
  }
37014
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync5(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
38968
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync6(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
37015
38969
  if (run.error || run.status !== 0 && run.status !== null) {
37016
38970
  results.push({
37017
38971
  harness: "claude",
@@ -37132,25 +39086,27 @@ async function cmdDocsServe(runtime, options = {}) {
37132
39086
 
37133
39087
  // plugins/kxm/src/cli/lanes.ts
37134
39088
  init_project_config();
39089
+ init_runtime_store();
37135
39090
  init_runtime_supervisor();
37136
39091
  init_types();
37137
- import { spawnSync as spawnSync6 } from "node:child_process";
39092
+ import { spawnSync as spawnSync7 } from "node:child_process";
37138
39093
  import { chmodSync as chmodSync6, existsSync as existsSync30, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "node:fs";
37139
39094
  import { basename as basename8, dirname as dirname17, join as join36, resolve as resolve24 } from "node:path";
37140
39095
  var LANE_SCHEMA = "kxm.lanes.v1";
37141
39096
  var SHA_RE = /^[a-f0-9]{40,64}$/;
37142
39097
  var TIMESTAMP_RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?Z$/;
37143
39098
  var SETTLED_RUN = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
39099
+ var LANE_RUN_LIST_LIMIT = 1e4;
37144
39100
  function lanesFile(runtime) {
37145
39101
  return join36(runtime.dirs.state, "lanes.json");
37146
39102
  }
37147
- function gitEnv() {
39103
+ function gitEnv2() {
37148
39104
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
37149
39105
  }
37150
39106
  function git2(cwd, args) {
37151
- const result = spawnSync6("git", ["-C", cwd, ...args], {
39107
+ const result = spawnSync7("git", ["-C", cwd, ...args], {
37152
39108
  encoding: "utf8",
37153
- env: gitEnv(),
39109
+ env: gitEnv2(),
37154
39110
  timeout: 3e4,
37155
39111
  windowsHide: true
37156
39112
  });
@@ -37373,6 +39329,61 @@ async function projectedRunStatus(runtime, projectRoot, runId, allowStart) {
37373
39329
  function isSettled(status) {
37374
39330
  return SETTLED_RUN.has(status);
37375
39331
  }
39332
+ async function laneStoreRuns(runtime, lanePath) {
39333
+ const project = await Promise.resolve().then(() => (init_project(), project_exports));
39334
+ const request = project.kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest;
39335
+ const handle = await attachKxmSupervisor({ env: runtime.env });
39336
+ const projectId = () => String(loadKxmProject(lanePath, {}).project.value.id);
39337
+ if (handle) {
39338
+ try {
39339
+ const result = await request(
39340
+ handle,
39341
+ "GET",
39342
+ `/v1/projects/${encodeURIComponent(projectId())}/runs?projectRoot=${encodeURIComponent(lanePath)}&limit=${LANE_RUN_LIST_LIMIT}`
39343
+ );
39344
+ const runs = Array.isArray(result.runs) ? result.runs : [];
39345
+ return runs.flatMap((run) => {
39346
+ if (!run || typeof run !== "object" || Array.isArray(run)) return [];
39347
+ const record = run;
39348
+ if (typeof record.runId !== "string" || record.runId.length === 0) return [];
39349
+ const status = typeof record.status === "string" && record.status.length > 0 ? record.status : "unknown";
39350
+ return [{ runId: record.runId, status }];
39351
+ });
39352
+ } catch {
39353
+ }
39354
+ }
39355
+ const eventsPath = kxmProjectRunEventsPath(lanePath, runtime.env);
39356
+ if (!existsSync30(eventsPath)) return void 0;
39357
+ const store = new KxmRunEventStore(eventsPath);
39358
+ try {
39359
+ return store.runsForProject(projectId(), LANE_RUN_LIST_LIMIT).map((run) => ({ runId: run.runId, status: run.status }));
39360
+ } finally {
39361
+ store.close();
39362
+ }
39363
+ }
39364
+ async function refuseIfLaneStoreRunsOpen(runtime, unit, command) {
39365
+ const loaded = loadLanes(runtime, command);
39366
+ if (typeof loaded === "number") return loaded;
39367
+ const lane = loaded.lanes[unit];
39368
+ if (!lane) return void 0;
39369
+ let runs;
39370
+ try {
39371
+ runs = await laneStoreRuns(runtime, lane.path);
39372
+ } catch (error) {
39373
+ const detail = error instanceof Error ? error.message : "run list failed";
39374
+ return refuse(runtime, command, "lane_run_open", `lane ${unit} runs could not be read: ${detail.slice(0, 200)}`, { unit });
39375
+ }
39376
+ if (!runs) return void 0;
39377
+ const unsettled = runs.filter((run) => !isSettled(run.status)).map((run) => run.runId);
39378
+ if (unsettled.length === 0) return void 0;
39379
+ return refuse(
39380
+ runtime,
39381
+ command,
39382
+ "lane_run_open",
39383
+ `lane ${unit} run ${unsettled.join(", ")} is unsettled`,
39384
+ { unit, runIds: unsettled }
39385
+ );
39386
+ }
37376
39387
  async function refuseIfLaneRunOpen(runtime, unit, command) {
37377
39388
  const loaded = loadLanes(runtime, command);
37378
39389
  if (typeof loaded === "number") return loaded;
@@ -37483,11 +39494,43 @@ async function cmdLaneStatus(runtime, unit) {
37483
39494
  print(
37484
39495
  runtime.io,
37485
39496
  runtime.json,
37486
- { ok: true, command, lane: { ...view, status: runStatus } },
37487
- `${formatLaneLine(view)} status=${runStatus}`
39497
+ { ok: true, command, root: record.path, lane: { ...view, status: runStatus } },
39498
+ `${formatLaneLine(view)} status=${runStatus} root=${record.path}`
37488
39499
  );
37489
39500
  return 0;
37490
39501
  }
39502
+ async function unregisterLaneRoot(runtime, projectRoot, force) {
39503
+ const body = force ? { projectRoot, force: true } : { projectRoot };
39504
+ try {
39505
+ const project = await Promise.resolve().then(() => (init_project(), project_exports));
39506
+ const request = project.kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest;
39507
+ const post = async (handle2) => {
39508
+ await request(handle2, "POST", "/v1/projects/unregister", body);
39509
+ };
39510
+ const handle = await attachKxmSupervisor({ env: runtime.env });
39511
+ if (handle) {
39512
+ await post(handle);
39513
+ return void 0;
39514
+ }
39515
+ const paths = kxmRuntimePaths({ env: runtime.env });
39516
+ if (!existsSync30(paths.registryDb)) return void 0;
39517
+ const registry = new KxmRuntimeRegistry(paths.registryDb);
39518
+ let outcome;
39519
+ try {
39520
+ outcome = registry.unregisterProjectIfIdle(projectRoot, kxmSupervisorRecordIsLive);
39521
+ } finally {
39522
+ registry.close();
39523
+ }
39524
+ if (outcome === "supervisor_live") {
39525
+ const live = await attachKxmSupervisor({ env: runtime.env });
39526
+ if (!live) return "runtime supervisor is live but could not be reached";
39527
+ await post(live);
39528
+ }
39529
+ return void 0;
39530
+ } catch (error) {
39531
+ return error instanceof Error ? error.message : "lane root could not be unregistered";
39532
+ }
39533
+ }
37491
39534
  async function cmdLaneDrop(runtime, unit, options = {}) {
37492
39535
  const command = "lane drop";
37493
39536
  const invalid = refuseUnit(runtime, command, unit);
@@ -37509,16 +39552,23 @@ async function cmdLaneDrop(runtime, unit, options = {}) {
37509
39552
  return refuse(runtime, command, "lane_dirty", `lane ${unit} has ${dirty} uncommitted change(s)`, { unit, dirty });
37510
39553
  }
37511
39554
  }
39555
+ const listed = await refuseIfLaneStoreRunsOpen(runtime, unit, command);
39556
+ if (listed !== void 0) return listed;
37512
39557
  const open = await refuseIfLaneRunOpen(runtime, unit, command);
37513
39558
  if (open !== void 0) return open;
37514
39559
  }
37515
39560
  if (runtime.dryRun) {
37516
39561
  printPlan(runtime, { command, unit, branchKept: record.branch }, [
39562
+ { action: "delete", target: `runtime registry ${record.path}` },
37517
39563
  { action: "delete", target: record.path },
37518
39564
  { action: "write", target: lanesFile(runtime) }
37519
39565
  ], `drop lane ${unit}; branch ${record.branch} is not deleted`);
37520
39566
  return 0;
37521
39567
  }
39568
+ const unregistered = await unregisterLaneRoot(runtime, record.path, force);
39569
+ if (unregistered !== void 0) {
39570
+ return refuse(runtime, command, "lane_unregister_failed", `lane ${unit} could not be unregistered: ${unregistered.slice(0, 300)}`, { unit });
39571
+ }
37522
39572
  if (existsSync30(record.path)) {
37523
39573
  const removed = git2(root, ["worktree", "remove", ...force ? ["--force"] : [], record.path]);
37524
39574
  if (removed.status !== 0) {
@@ -37629,7 +39679,7 @@ function cmdLand(runtime, options = {}) {
37629
39679
  // plugins/kxm/src/cli/assign.ts
37630
39680
  init_project_config();
37631
39681
  init_types();
37632
- import { spawnSync as spawnSync7 } from "node:child_process";
39682
+ import { spawnSync as spawnSync8 } from "node:child_process";
37633
39683
  import { existsSync as existsSync31 } from "node:fs";
37634
39684
  import { join as join38 } from "node:path";
37635
39685
  var kxmAssignCliSeams = {};
@@ -37715,7 +39765,7 @@ async function cmdAssign(runtime, subcommand, args) {
37715
39765
  return 0;
37716
39766
  }
37717
39767
  const spawn6 = kxmAssignCliSeams.spawn ?? ((commandName, commandArgs, options) => {
37718
- const result2 = spawnSync7(commandName, commandArgs, {
39768
+ const result2 = spawnSync8(commandName, commandArgs, {
37719
39769
  cwd: options.cwd,
37720
39770
  stdio: options.stdio,
37721
39771
  env: options.env,
@@ -37733,7 +39783,7 @@ async function cmdAssign(runtime, subcommand, args) {
37733
39783
 
37734
39784
  // plugins/kxm/src/cli/hub.ts
37735
39785
  init_redact();
37736
- import { randomUUID as randomUUID13 } from "node:crypto";
39786
+ import { randomUUID as randomUUID14 } from "node:crypto";
37737
39787
  import { existsSync as existsSync37, mkdirSync as mkdirSync28, readFileSync as readFileSync35, readdirSync as readdirSync12, rmSync as rmSync11, writeFileSync as writeFileSync25 } from "node:fs";
37738
39788
  import { join as join48, resolve as resolve27 } from "node:path";
37739
39789
  init_hub_env();
@@ -46177,7 +48227,7 @@ var MAX_RENDER_WRITE_CHARS = 1024 * 1024;
46177
48227
  // plugins/kxm/src/tui.ts
46178
48228
  import { mkdirSync as mkdirSync25 } from "node:fs";
46179
48229
  import { join as join43, resolve as resolve26, dirname as dirname19 } from "node:path";
46180
- import { spawnSync as spawnSync8 } from "node:child_process";
48230
+ import { spawnSync as spawnSync9 } from "node:child_process";
46181
48231
 
46182
48232
  // packages/core/tui/src/types/surface.ts
46183
48233
  var KXM_TUI_LIMITS = Object.freeze({
@@ -46322,7 +48372,7 @@ init_sqlite();
46322
48372
  init_telemetry();
46323
48373
  import { existsSync as existsSync32, readdirSync as readdirSync11, readFileSync as readFileSync30 } from "node:fs";
46324
48374
  import { homedir as homedir6 } from "node:os";
46325
- import { isAbsolute as isAbsolute9, join as join42, resolve as resolve25 } from "node:path";
48375
+ import { isAbsolute as isAbsolute10, join as join42, resolve as resolve25 } from "node:path";
46326
48376
  var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
46327
48377
  var RUNTIME_PROJECT_KEY = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
46328
48378
  function processExists2(pid) {
@@ -46461,19 +48511,19 @@ function resolveKxmStateRoot(stateDir, options) {
46461
48511
  }
46462
48512
  const env = options?.env ?? process.env;
46463
48513
  const explicit = env.KXM_STATE_HOME?.trim() || env.KXM_USER_STATE_DIR?.trim() || env.KXM_STATE_ROOT?.trim();
46464
- if (explicit && isAbsolute9(explicit) && existsSync32(resolve25(explicit))) {
48514
+ if (explicit && isAbsolute10(explicit) && existsSync32(resolve25(explicit))) {
46465
48515
  return resolve25(explicit);
46466
48516
  }
46467
48517
  let base;
46468
48518
  if (process.platform === "win32") {
46469
48519
  const localAppData = env.LOCALAPPDATA?.trim();
46470
- base = localAppData && isAbsolute9(localAppData) ? localAppData : join42(homedir6(), "AppData", "Local");
48520
+ base = localAppData && isAbsolute10(localAppData) ? localAppData : join42(homedir6(), "AppData", "Local");
46471
48521
  base = resolve25(base, "KXM");
46472
48522
  } else if (process.platform === "darwin") {
46473
48523
  base = resolve25(homedir6(), "Library", "Application Support", "KXM");
46474
48524
  } else {
46475
48525
  const xdgState = env.XDG_STATE_HOME?.trim();
46476
- base = xdgState && isAbsolute9(xdgState) ? xdgState : join42(homedir6(), ".local", "state");
48526
+ base = xdgState && isAbsolute10(xdgState) ? xdgState : join42(homedir6(), ".local", "state");
46477
48527
  base = resolve25(base, "kxm");
46478
48528
  }
46479
48529
  if (existsSync32(base)) return base;
@@ -46734,16 +48784,16 @@ function applyMeshTuiKey(view, key, itemCount = 0) {
46734
48784
  function copyToClipboard(text) {
46735
48785
  try {
46736
48786
  if (process.platform === "darwin") {
46737
- const proc = spawnSync8("pbcopy", { input: text, encoding: "utf8", windowsHide: true });
48787
+ const proc = spawnSync9("pbcopy", { input: text, encoding: "utf8", windowsHide: true });
46738
48788
  return proc.status === 0;
46739
48789
  }
46740
48790
  if (process.platform === "win32") {
46741
- const proc = spawnSync8("clip", { input: text, encoding: "utf8", windowsHide: true });
48791
+ const proc = spawnSync9("clip", { input: text, encoding: "utf8", windowsHide: true });
46742
48792
  return proc.status === 0;
46743
48793
  }
46744
- const wl = spawnSync8("wl-copy", [text], { encoding: "utf8", windowsHide: true });
48794
+ const wl = spawnSync9("wl-copy", [text], { encoding: "utf8", windowsHide: true });
46745
48795
  if (wl.status === 0) return true;
46746
- const xclip = spawnSync8("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true });
48796
+ const xclip = spawnSync9("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true });
46747
48797
  return xclip.status === 0;
46748
48798
  } catch {
46749
48799
  return false;
@@ -46751,7 +48801,7 @@ function copyToClipboard(text) {
46751
48801
  }
46752
48802
  function spawnDegradeWorktree(repoRoot, runId, options) {
46753
48803
  const runner = options?.execFn ?? ((cmd, args) => {
46754
- const res = spawnSync8(cmd, args, {
48804
+ const res = spawnSync9(cmd, args, {
46755
48805
  cwd: repoRoot,
46756
48806
  encoding: "utf8",
46757
48807
  windowsHide: true,
@@ -47535,8 +49585,8 @@ async function runMeshTui(input) {
47535
49585
  }
47536
49586
 
47537
49587
  // plugins/kxm/src/session-work.ts
47538
- import { spawnSync as spawnSync9 } from "node:child_process";
47539
- import { randomUUID as randomUUID12 } from "node:crypto";
49588
+ import { spawnSync as spawnSync10 } from "node:child_process";
49589
+ import { randomUUID as randomUUID13 } from "node:crypto";
47540
49590
  import { existsSync as existsSync33, mkdirSync as mkdirSync26, readFileSync as readFileSync31, renameSync as renameSync8, writeFileSync as writeFileSync23 } from "node:fs";
47541
49591
  import { join as join44 } from "node:path";
47542
49592
  init_hub_binding();
@@ -47596,10 +49646,10 @@ function formatShipLine(ship) {
47596
49646
  }
47597
49647
  function readGitShip(cwd) {
47598
49648
  try {
47599
- const dirty = spawnSync9("git", ["-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
49649
+ const dirty = spawnSync10("git", ["-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
47600
49650
  if (dirty.status !== 0) return void 0;
47601
49651
  const isDirty = dirty.stdout.trim().length > 0;
47602
- const upstream = spawnSync9("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
49652
+ const upstream = spawnSync10("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
47603
49653
  if (upstream.status === 0) {
47604
49654
  return {
47605
49655
  dirty: isDirty,
@@ -47607,9 +49657,9 @@ function readGitShip(cwd) {
47607
49657
  };
47608
49658
  }
47609
49659
  for (const baseRef of ["origin/HEAD", "main", "origin/main", "master", "origin/master"]) {
47610
- const mb = spawnSync9("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
49660
+ const mb = spawnSync10("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
47611
49661
  if (mb.status === 0 && mb.stdout.trim()) {
47612
- const count = spawnSync9("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
49662
+ const count = spawnSync10("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
47613
49663
  if (count.status === 0) {
47614
49664
  return {
47615
49665
  dirty: isDirty,
@@ -47717,7 +49767,7 @@ function writeCachedSessionBrief(stateDir, brief) {
47717
49767
  try {
47718
49768
  mkdirSync26(stateDir, { recursive: true, mode: 448 });
47719
49769
  const file = join44(stateDir, "session-brief.json");
47720
- const tmp = `${file}.tmp.${randomUUID12().slice(0, 8)}`;
49770
+ const tmp = `${file}.tmp.${randomUUID13().slice(0, 8)}`;
47721
49771
  writeFileSync23(tmp, JSON.stringify(brief, null, 2), { encoding: "utf8", mode: 384 });
47722
49772
  renameSync8(tmp, file);
47723
49773
  } catch {
@@ -48588,16 +50638,16 @@ async function cmdSessionStatus(runtime) {
48588
50638
  const recoveries = [];
48589
50639
  for (const file of names3.filter((name) => name.startsWith("worker-recovery-") && name.endsWith(".json"))) {
48590
50640
  try {
48591
- const envelope = JSON.parse(readFileSync35(join48(stateDir, file), "utf8"));
50641
+ const envelope2 = JSON.parse(readFileSync35(join48(stateDir, file), "utf8"));
48592
50642
  recoveries.push({
48593
50643
  file,
48594
- reason: envelope.reason,
48595
- agentName: envelope.agentName,
48596
- project: envelope.project,
48597
- createdAt: envelope.createdAt,
48598
- runId: envelope.runId ?? void 0,
48599
- stageId: envelope.stageId ?? void 0,
48600
- freshSession: envelope.freshSession === true
50644
+ reason: envelope2.reason,
50645
+ agentName: envelope2.agentName,
50646
+ project: envelope2.project,
50647
+ createdAt: envelope2.createdAt,
50648
+ runId: envelope2.runId ?? void 0,
50649
+ stageId: envelope2.stageId ?? void 0,
50650
+ freshSession: envelope2.freshSession === true
48601
50651
  });
48602
50652
  } catch {
48603
50653
  recoveries.push({ file, error: "invalid_recovery_envelope" });
@@ -48752,7 +50802,7 @@ Session token: ${sessionToken}
48752
50802
  return 0;
48753
50803
  }
48754
50804
  async function cmdSessionStart(runtime, options) {
48755
- const id = options.id?.trim() || `session_${randomUUID13().replaceAll("-", "").slice(0, 12)}`;
50805
+ const id = options.id?.trim() || `session_${randomUUID14().replaceAll("-", "").slice(0, 12)}`;
48756
50806
  const workflowId = options.workflow?.trim();
48757
50807
  const mix = options.mix?.trim();
48758
50808
  if (workflowId && mix) {
@@ -48811,9 +50861,9 @@ init_redact();
48811
50861
  init_telemetry();
48812
50862
  init_routing();
48813
50863
  init_prices();
48814
- import { spawnSync as spawnSync11 } from "node:child_process";
50864
+ import { spawnSync as spawnSync12 } from "node:child_process";
48815
50865
  import { createHash as createHash19 } from "node:crypto";
48816
- import { existsSync as existsSync44, mkdtempSync as mkdtempSync3, readFileSync as readFileSync41, rmSync as rmSync13 } from "node:fs";
50866
+ import { existsSync as existsSync44, mkdtempSync as mkdtempSync3, readFileSync as readFileSync40, rmSync as rmSync13 } from "node:fs";
48817
50867
  import { tmpdir as tmpdir2 } from "node:os";
48818
50868
  import { basename as basename11, extname as extname3, join as join54, resolve as resolve32 } from "node:path";
48819
50869
  import { createInterface as createInterface3 } from "node:readline";
@@ -48822,7 +50872,7 @@ import { createInterface as createInterface3 } from "node:readline";
48822
50872
  init_routing();
48823
50873
  init_protocol();
48824
50874
  init_relevance();
48825
- import { createHash as createHash17 } from "node:crypto";
50875
+ import { createHash as createHash18 } from "node:crypto";
48826
50876
  import { existsSync as existsSync38, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
48827
50877
  import { join as join49, relative as relative7, resolve as resolve28 } from "node:path";
48828
50878
  var CANDIDATE_SCHEMA = "kxm.candidate.v1";
@@ -49039,7 +51089,7 @@ function groupRoutingRecords(records, options = {}) {
49039
51089
  }
49040
51090
  const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : void 0;
49041
51091
  const safeSlug = group.stepId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 16) || "step";
49042
- const keyHash = createHash17("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
51092
+ const keyHash = createHash18("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
49043
51093
  const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : void 0;
49044
51094
  keyed.push({
49045
51095
  key: group.key,
@@ -49668,8 +51718,8 @@ ${divider}
49668
51718
 
49669
51719
  // plugins/kxm/src/ssh-remote.ts
49670
51720
  init_safety_integrity();
49671
- import { spawnSync as spawnSync10 } from "node:child_process";
49672
- import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync13, rmSync as rmSync12, statSync as statSync5 } from "node:fs";
51721
+ import { spawnSync as spawnSync11 } from "node:child_process";
51722
+ import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync13, rmSync as rmSync12, statSync as statSync6 } from "node:fs";
49673
51723
  import { homedir as homedir8 } from "node:os";
49674
51724
  import { join as join51, resolve as resolve30 } from "node:path";
49675
51725
  var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
@@ -49744,7 +51794,7 @@ function parseSshConfig(configPath) {
49744
51794
  return [];
49745
51795
  }
49746
51796
  }
49747
- function resolveSshHostG(host, execFn = spawnSync10) {
51797
+ function resolveSshHostG(host, execFn = spawnSync11) {
49748
51798
  try {
49749
51799
  const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
49750
51800
  if (result.status !== 0 || !result.stdout) {
@@ -49810,7 +51860,7 @@ function buildSshArgs(options) {
49810
51860
  args.push(options.host);
49811
51861
  return args;
49812
51862
  }
49813
- function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync10) {
51863
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync11) {
49814
51864
  const resolvedDir = ensureSocketDir(socketDir);
49815
51865
  const controlPath = join51(resolvedDir, "%C");
49816
51866
  try {
@@ -49822,7 +51872,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
49822
51872
  return false;
49823
51873
  }
49824
51874
  }
49825
- function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync10) {
51875
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync11) {
49826
51876
  const resolvedDir = ensureSocketDir(socketDir);
49827
51877
  const controlPath = join51(resolvedDir, "%C");
49828
51878
  try {
@@ -49836,7 +51886,7 @@ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
49836
51886
  }
49837
51887
  function executeSshRun(params) {
49838
51888
  const startTime = Date.now();
49839
- const execSyncFn = params.execFn ?? spawnSync10;
51889
+ const execSyncFn = params.execFn ?? spawnSync11;
49840
51890
  if (params.action === "info") {
49841
51891
  if (params.host) {
49842
51892
  const hostInfo = resolveSshHostG(params.host, execSyncFn);
@@ -50277,7 +52327,7 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
50277
52327
  // plugins/kxm/src/completion-install.ts
50278
52328
  import { existsSync as existsSync42, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync27 } from "node:fs";
50279
52329
  import { homedir as homedir9 } from "node:os";
50280
- import { basename as basename10, delimiter, dirname as dirname23, isAbsolute as isAbsolute10, join as join52, resolve as resolve31 } from "node:path";
52330
+ import { basename as basename10, delimiter, dirname as dirname23, isAbsolute as isAbsolute11, join as join52, resolve as resolve31 } from "node:path";
50281
52331
  var COMPLETION_MARKER = "# kxm completion";
50282
52332
  var PATH_MARKER = "# kxm path";
50283
52333
  function detectShell(env = process.env, platform = process.platform) {
@@ -50403,7 +52453,7 @@ ${line}
50403
52453
  }
50404
52454
  function kxmBinDir(env = process.env) {
50405
52455
  const argv1 = env.KXM_ENTRY ?? process.argv[1];
50406
- if (argv1 && isAbsolute10(argv1)) {
52456
+ if (argv1 && isAbsolute11(argv1)) {
50407
52457
  const dir = dirname23(argv1);
50408
52458
  if (existsSync42(join52(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
50409
52459
  }
@@ -50444,8 +52494,7 @@ ${line}
50444
52494
  // plugins/kxm/src/init-guide-setup.ts
50445
52495
  var import_yaml18 = __toESM(require_dist(), 1);
50446
52496
  init_routes();
50447
- import { createHash as createHash18 } from "node:crypto";
50448
- import { existsSync as existsSync43, mkdirSync as mkdirSync32, readFileSync as readFileSync40, writeFileSync as writeFileSync28 } from "node:fs";
52497
+ import { existsSync as existsSync43, mkdirSync as mkdirSync32, writeFileSync as writeFileSync28 } from "node:fs";
50449
52498
  import { dirname as dirname24, join as join53 } from "node:path";
50450
52499
  var ADMITTED_GUIDE_BINDINGS = Object.freeze({
50451
52500
  "anthropic/claude-fable-5.1": { harness: "claude", provider: "anthropic", model: "fable" },
@@ -50790,13 +52839,12 @@ var WRITER_ROLE_PATTERN = /(writer|implementer|scaffold|backend|frontend|sdk-int
50790
52839
  function isWriterRole(roleSlug) {
50791
52840
  return WRITER_ROLE_PATTERN.test(roleSlug);
50792
52841
  }
50793
- function agentDocument(roleSlug, stage, binding) {
52842
+ function agentDocument(roleSlug, stage) {
50794
52843
  const writer = isWriterRole(roleSlug);
50795
52844
  return {
50796
52845
  schema: "kxm.agent.v1",
50797
52846
  purpose: `${stage.domain} (workflow-guide ${stage.title}; candidate verification is the operator's responsibility)`,
50798
- harness: binding.harness,
50799
- model: { provider: binding.provider, model: binding.model },
52847
+ role: roleSlug,
50800
52848
  tools: { preset: writer ? "workspace-writer" : "read-only" },
50801
52849
  defaultRepositoryAccess: writer ? "none" : "read",
50802
52850
  repositories: { control: writer ? "write" : "read" },
@@ -50827,19 +52875,62 @@ function workflowDocument(workflow) {
50827
52875
  steps
50828
52876
  };
50829
52877
  }
52878
+ var GUIDE_PI_EVIDENCE_SOURCE = "plans/evidence/route-guide-qwen-pi.md";
52879
+ var GUIDE_PI_EVIDENCE_SHA256 = "109f39728b251dd0565e275ae689a6e1d9b889a488d996b157d500c538115759";
52880
+ var GUIDE_PI_NOTE = {
52881
+ harness: "pi",
52882
+ vendor: "openrouter",
52883
+ model: "qwen/qwen3-coder-plus",
52884
+ reason: "Qwen has no supported native harness on this runner, and the operator admitted that exact model for guided setup.",
52885
+ provingCommand: "pi auth check --provider openrouter"
52886
+ };
52887
+ var GUIDE_PI_EVIDENCE_NOTE = `---
52888
+ schema: "kxm.doc.v1"
52889
+ id: "RES-GUIDE-QWEN-PI"
52890
+ type: "research"
52891
+ title: "Research: guided setup Pi Qwen origin"
52892
+ project: "kxm"
52893
+ status: "approved"
52894
+ owner: "@operator"
52895
+ created: "2026-09-26"
52896
+ updated: "2026-09-26"
52897
+ authority: "evidence"
52898
+ confidence: "verified"
52899
+ summary: "Guided setup pins every Pi model origin to this note so a later project.yaml edit cannot change the hash."
52900
+ tags: ["routing", "qwen", "pi", "init-guide"]
52901
+ related: []
52902
+ details:
52903
+ research_status: "complete"
52904
+ target_decision_date: "2026-09-26"
52905
+ ---
52906
+
52907
+ # route-guide-qwen-pi
52908
+
52909
+ Dated 2026-09-26. Route ids are the guided role slugs. Harness \`${GUIDE_PI_NOTE.harness}\`. Model id \`${GUIDE_PI_NOTE.model}\`. Vendor \`${GUIDE_PI_NOTE.vendor}\`. Admitted because ${GUIDE_PI_NOTE.reason} Auth was proved with \`${GUIDE_PI_NOTE.provingCommand}\`.
52910
+ `;
52911
+ function guidePiNoteMatches(binding) {
52912
+ return binding.harness === GUIDE_PI_NOTE.harness && binding.provider === GUIDE_PI_NOTE.vendor && binding.model === GUIDE_PI_NOTE.model;
52913
+ }
50830
52914
  function renderGuideSetupFiles(projectRoot, plan) {
50831
- const projectYaml = join53(projectRoot, ".kxm", "project.yaml");
50832
- const projectOrigin = existsSync43(projectYaml) ? { source: ".kxm/project.yaml", sha256: createHash18("sha256").update(readFileSync40(projectYaml)).digest("hex") } : void 0;
50833
52915
  const files = [];
50834
52916
  const roleStages = /* @__PURE__ */ new Map();
50835
52917
  for (const workflow of plan.workflows) {
50836
52918
  for (const stage of workflow.stages) roleStages.set(stage.role, stage);
50837
52919
  }
52920
+ let evidenceQueued = false;
50838
52921
  for (const [role, binding] of [...plan.agents.entries()].sort(([a], [b2]) => a.localeCompare(b2))) {
50839
52922
  const stage = roleStages.get(role);
50840
52923
  if (!stage) continue;
50841
52924
  const routeId2 = role.replaceAll("_", "-");
50842
52925
  const writer = isWriterRole(role);
52926
+ const piMatch = binding.harness === "pi" && guidePiNoteMatches(binding);
52927
+ if (binding.harness === "pi" && !piMatch) {
52928
+ files.push({
52929
+ path: join53(projectRoot, ".kxm", "agents", `${role}.yaml`),
52930
+ content: (0, import_yaml18.stringify)(agentDocument(role, stage))
52931
+ });
52932
+ continue;
52933
+ }
50843
52934
  const modelDocument = {
50844
52935
  schema: "kxm.model.v2",
50845
52936
  id: routeId2,
@@ -50849,10 +52940,19 @@ function renderGuideSetupFiles(projectRoot, plan) {
50849
52940
  status: "admitted",
50850
52941
  permissions: [writer ? "edit" : "read-only"]
50851
52942
  };
50852
- if (binding.harness === "pi" && projectOrigin) modelDocument.origin = projectOrigin;
52943
+ if (piMatch) {
52944
+ modelDocument.origin = { source: GUIDE_PI_EVIDENCE_SOURCE, sha256: GUIDE_PI_EVIDENCE_SHA256 };
52945
+ if (!evidenceQueued) {
52946
+ files.push({
52947
+ path: join53(projectRoot, GUIDE_PI_EVIDENCE_SOURCE),
52948
+ content: GUIDE_PI_EVIDENCE_NOTE
52949
+ });
52950
+ evidenceQueued = true;
52951
+ }
52952
+ }
50853
52953
  files.push({
50854
52954
  path: join53(projectRoot, ".kxm", "agents", `${role}.yaml`),
50855
- content: (0, import_yaml18.stringify)(agentDocument(role, stage, binding))
52955
+ content: (0, import_yaml18.stringify)(agentDocument(role, stage))
50856
52956
  });
50857
52957
  files.push({
50858
52958
  path: join53(projectRoot, ".kxm", "models", `${routeId2}.yaml`),
@@ -50931,7 +53031,7 @@ init_types();
50931
53031
  init_repo_root();
50932
53032
  function cliSpawn(runtime, command, args, extra) {
50933
53033
  if (runtime.io.spawnSync) return runtime.io.spawnSync(command, args);
50934
- const result = spawnSync11(command, [...args], {
53034
+ const result = spawnSync12(command, [...args], {
50935
53035
  encoding: "utf8",
50936
53036
  windowsHide: true,
50937
53037
  shell: process.platform === "win32",
@@ -50978,7 +53078,7 @@ function applyKxmPackageUpdate(runtime, notice) {
50978
53078
  for (const step of planned) {
50979
53079
  if (step.kind === "verify") {
50980
53080
  if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
50981
- const actual = existsSync44(step.path) ? createHash19("sha256").update(readFileSync41(step.path)).digest("hex") : "missing";
53081
+ const actual = existsSync44(step.path) ? createHash19("sha256").update(readFileSync40(step.path)).digest("hex") : "missing";
50982
53082
  return {
50983
53083
  ok: false,
50984
53084
  error: "release_digest_mismatch",
@@ -51261,7 +53361,7 @@ async function cmdValidate(runtime, fileFlag) {
51261
53361
  return 1;
51262
53362
  }
51263
53363
  try {
51264
- const raw = file ? readFileSync41(file, "utf8") : inline;
53364
+ const raw = file ? readFileSync40(file, "utf8") : inline;
51265
53365
  if (explicitFile && file && !raw.trimStart().startsWith("[")) {
51266
53366
  const value = parseRestrictedYaml2(raw, file);
51267
53367
  const issues = new KxmSchemaRegistry().validate("workflow", value, file);
@@ -51511,7 +53611,7 @@ async function maybeOfferCompletionInstall(runtime) {
51511
53611
  const { rcFile } = completionRcTarget(shell, { env: runtime.env });
51512
53612
  if (rcFile && existsSync44(rcFile)) {
51513
53613
  try {
51514
- if (readFileSync41(rcFile, "utf8").includes(scriptPath)) return;
53614
+ if (readFileSync40(rcFile, "utf8").includes(scriptPath)) return;
51515
53615
  } catch {
51516
53616
  }
51517
53617
  }