@kontextmind/kxm 0.7.127 → 0.7.130

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.
@@ -18708,6 +18708,69 @@ function readJsonObject(file) {
18708
18708
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${file} is not a JSON object`);
18709
18709
  return parsed;
18710
18710
  }
18711
+ function validateRunEvent(value, file) {
18712
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18713
+ if (!registry.runEventValidator(value)) {
18714
+ throw new KxmConfigError([issue3(
18715
+ "schema",
18716
+ "run_event_invalid",
18717
+ file,
18718
+ registry.ajv.errorsText(registry.runEventValidator.errors, { separator: "; " })
18719
+ )]);
18720
+ }
18721
+ }
18722
+ function syncEventSchemaErrors(value) {
18723
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18724
+ if (registry.syncEventValidator(value)) return void 0;
18725
+ return registry.ajv.errorsText(registry.syncEventValidator.errors, { separator: "; " });
18726
+ }
18727
+ function validateDriveReceipt(value, file) {
18728
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18729
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.drive-receipt.v1") {
18730
+ throw new KxmConfigError([issue3(
18731
+ "schema",
18732
+ "drive_receipt_invalid",
18733
+ file,
18734
+ "expected kxm.drive-receipt.v1"
18735
+ )]);
18736
+ }
18737
+ if (!registry.driveReceiptValidator(value)) {
18738
+ throw new KxmConfigError([issue3(
18739
+ "schema",
18740
+ "drive_receipt_invalid",
18741
+ file,
18742
+ registry.ajv.errorsText(registry.driveReceiptValidator.errors, { separator: "; " })
18743
+ )]);
18744
+ }
18745
+ }
18746
+ function validateCoordinator(value, file) {
18747
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18748
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.coordinator.v1") {
18749
+ throw new KxmConfigError([issue3("schema", "coordinator_invalid", file, "expected kxm.coordinator.v1")]);
18750
+ }
18751
+ if (!registry.coordinatorValidator(value)) {
18752
+ throw new KxmConfigError([issue3(
18753
+ "schema",
18754
+ "coordinator_invalid",
18755
+ file,
18756
+ registry.ajv.errorsText(registry.coordinatorValidator.errors, { separator: "; " })
18757
+ )]);
18758
+ }
18759
+ }
18760
+ function validateIntakeMessage(value, file) {
18761
+ const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
18762
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.intake-message.v1") {
18763
+ throw new KxmConfigError([issue3("schema", "intake_message_invalid", file, "expected kxm.intake-message.v1")]);
18764
+ }
18765
+ if (!registry.intakeMessageValidator(value)) {
18766
+ throw new KxmConfigError([issue3(
18767
+ "schema",
18768
+ "intake_message_invalid",
18769
+ file,
18770
+ registry.ajv.errorsText(registry.intakeMessageValidator.errors, { separator: "; " })
18771
+ )]);
18772
+ }
18773
+ }
18711
18774
  function schemaIssue(file, error) {
18712
18775
  const location = error.instancePath || "/";
18713
18776
  const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
@@ -19853,7 +19916,7 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
19853
19916
  }
19854
19917
  return { mode: "create", inspectedFrom, projectRoot: candidateRoot, changesRequired: true, issues: [], legacyInputs: [] };
19855
19918
  }
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;
19919
+ 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
19920
  var init_project_config = __esm({
19858
19921
  "plugins/kxm/src/project-config.ts"() {
19859
19922
  "use strict";
@@ -20418,12 +20481,169 @@ var init_runtime_paths = __esm({
20418
20481
 
20419
20482
  // plugins/kxm/src/sync-transform.ts
20420
20483
  import { createHash as createHash10 } from "node:crypto";
20421
- var KXM_DEFAULT_SYNC_POLICY, KXM_DEFAULT_SYNC_POLICY_REVISION, SCALAR_FIELDS, CARRIED_FIELDS;
20484
+ function isRecord(value) {
20485
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
20486
+ }
20487
+ function pickScalars(source, keys, redactor) {
20488
+ if (!isRecord(source)) return void 0;
20489
+ const result = {};
20490
+ for (const key of keys) {
20491
+ const value = source[key];
20492
+ if (typeof value === "number" || typeof value === "boolean") {
20493
+ result[key] = value;
20494
+ } else if (typeof value === "string") {
20495
+ if (redactor) {
20496
+ const scrubbed = redactor.scrub(value);
20497
+ if (scrubbed.text.length > 0) result[key] = scrubbed.text.slice(0, 200);
20498
+ } else {
20499
+ result[key] = value;
20500
+ }
20501
+ }
20502
+ }
20503
+ return Object.keys(result).length > 0 ? result : void 0;
20504
+ }
20505
+ function buildPayload(source, builder) {
20506
+ for (const key of SCALAR_FIELDS) {
20507
+ const value = source[key];
20508
+ if (typeof value === "string" || typeof value === "number") builder.set(key, value);
20509
+ }
20510
+ builder.set("displayTitle", builder.text(source.displayTitle, 200));
20511
+ builder.set("summary", builder.text(source.summary, 1e3));
20512
+ if (source.reason !== void 0) {
20513
+ builder.set("reason", builder.text(source.reason, 1e3) ?? REDACTED);
20514
+ }
20515
+ builder.set("executor", builder.record(source.executor, ["id", "kind", "harness"], {
20516
+ executorVersion: 128,
20517
+ harnessVersion: 128,
20518
+ helperGeneration: 256
20519
+ }));
20520
+ const model = builder.record(source.model, ["profile", "provider"], { model: 200, thinking: 64 });
20521
+ if (model && isRecord(source.model) && Array.isArray(source.model.tags)) {
20522
+ model.tags = source.model.tags.filter((tag) => typeof tag === "string").slice(0, 32);
20523
+ }
20524
+ builder.set("model", model);
20525
+ builder.set("timing", builder.record(source.timing, ["startedAt", "finishedAt", "durationMs", "queueMs", "agentMs"]));
20526
+ builder.set("usage", builder.record(source.usage, [
20527
+ "inputTokens",
20528
+ "outputTokens",
20529
+ "cacheReadTokens",
20530
+ "cacheWriteTokens",
20531
+ "cost",
20532
+ "currency",
20533
+ "costKind"
20534
+ ]));
20535
+ const refs = (value, keys) => Array.isArray(value) ? value.slice(0, MAX_REFS).map((entry) => pickScalars(entry, keys)).filter((entry) => entry !== void 0) : void 0;
20536
+ builder.set("evidenceRefs", refs(source.evidenceRefs, REF_KEYS));
20537
+ builder.set("suppliedRefs", refs(source.suppliedRefs, REF_KEYS));
20538
+ builder.set("artifacts", refs(source.artifacts, ARTIFACT_KEYS));
20539
+ if (isRecord(source.receipt)) {
20540
+ const receipt = builder.record(source.receipt, ["provider", "kind", "hash", "observedAt"], { id: 512 });
20541
+ if (receipt) builder.set("receipts", [receipt]);
20542
+ if (source.receipt.url !== void 0) builder.omitted.add("receipt.url");
20543
+ }
20544
+ builder.set("error", builder.record(source.error, ["class", "retryable"], { component: 128 }));
20545
+ if (isRecord(source.effect)) {
20546
+ const effect = pickScalars(source.effect, ["id", "idempotencyKeyHash"], builder.redactor) ?? {};
20547
+ const policy = pickScalars(source.effect.policy, ["class", "sharedMutable", "idempotencyKey", "receiptQuery"], builder.redactor);
20548
+ if (policy) effect.policy = policy;
20549
+ builder.set("effect", effect);
20550
+ }
20551
+ builder.set("lease", pickScalars(source.lease, LEASE_KEYS, builder.redactor));
20552
+ builder.set("actor", builder.record(source.actor, ["kind"], { id: 200 }));
20553
+ builder.set("resolvedBy", builder.record(source.resolvedBy, ["kind"], { id: 200 }));
20554
+ if (isRecord(source.counts)) {
20555
+ const counts = Object.fromEntries(Object.entries(source.counts).filter((entry) => Number.isInteger(entry[1]) && entry[1] >= 0).slice(0, 32));
20556
+ builder.set("counts", counts);
20557
+ }
20558
+ }
20559
+ function envelope(event, policyRevision, payload, fieldsOmitted, valuesReplaced) {
20560
+ return {
20561
+ schema: KXM_SYNC_EVENT_SCHEMA,
20562
+ sourceEventId: event.eventId,
20563
+ eventType: event.eventType,
20564
+ projectId: event.projectId,
20565
+ runId: event.runId,
20566
+ homeRuntimeId: event.homeRuntimeId,
20567
+ sequence: event.sequence,
20568
+ occurredAt: event.occurredAt,
20569
+ configRevision: event.configRevision,
20570
+ memoryRevision: event.memoryRevision,
20571
+ executorPolicyRevision: event.executorPolicyRevision,
20572
+ toolPolicyRevision: event.toolPolicyRevision,
20573
+ syncPolicyRevision: policyRevision,
20574
+ redaction: {
20575
+ version: KXM_SYNC_REDACTOR_VERSION,
20576
+ fieldsOmitted: [...new Set(fieldsOmitted)].sort().slice(0, 128),
20577
+ valuesReplaced: Math.min(valuesReplaced, 1e5)
20578
+ },
20579
+ payload
20580
+ };
20581
+ }
20582
+ function deriveKxmSyncEvent(event, options = {}) {
20583
+ const redactor = options.redactor ?? new KxmSyncRedactor();
20584
+ const policyRevision = options.policyRevision ?? KXM_DEFAULT_SYNC_POLICY_REVISION;
20585
+ const source = isRecord(event.payload) ? event.payload : {};
20586
+ const builder = new SyncPayloadBuilder(redactor);
20587
+ buildPayload(source, builder);
20588
+ const omitted = new Set(builder.omitted);
20589
+ for (const key of Object.keys(source)) if (!CARRIED_FIELDS.has(key)) omitted.add(key);
20590
+ if (event.protectedPayload !== void 0) omitted.add("protectedPayload");
20591
+ const payload = Object.keys(builder.payload).length > 0 ? builder.payload : { counts: {} };
20592
+ const full = envelope(event, policyRevision, payload, omitted, builder.replaced);
20593
+ if (syncEventSchemaErrors(full) === void 0) return full;
20594
+ const degradedPayload = { degraded: true };
20595
+ for (const key of CONTROL_FIELDS) {
20596
+ const value = builder.payload[key];
20597
+ const restoreNested = (built, orig) => {
20598
+ if (!isRecord(orig)) return built !== void 0 ? built : "[redacted]";
20599
+ const result = {};
20600
+ const builtRecord = isRecord(built) ? built : {};
20601
+ for (const [k2, v2] of Object.entries(orig)) {
20602
+ const builtValue = builtRecord[k2];
20603
+ if (builtValue === void 0 || typeof builtValue === "string" && builtValue.trim().length === 0) {
20604
+ result[k2] = isRecord(v2) ? restoreNested(void 0, v2) : "[redacted]";
20605
+ } else {
20606
+ result[k2] = builtValue;
20607
+ }
20608
+ }
20609
+ return result;
20610
+ };
20611
+ if (value === void 0) {
20612
+ const sourceValue = source[key];
20613
+ if (sourceValue !== void 0) {
20614
+ degradedPayload[key] = restoreNested(void 0, sourceValue);
20615
+ }
20616
+ } else if (typeof value === "string" && value.trim().length === 0) {
20617
+ degradedPayload[key] = "[redacted]";
20618
+ } else if (isRecord(value)) {
20619
+ degradedPayload[key] = restoreNested(value, source[key]);
20620
+ } else {
20621
+ degradedPayload[key] = value;
20622
+ }
20623
+ }
20624
+ const degradedOmitted = new Set(omitted);
20625
+ for (const key of Object.keys(builder.payload)) if (!(key in degradedPayload)) degradedOmitted.add(key);
20626
+ const degraded = envelope(event, policyRevision, degradedPayload, degradedOmitted, builder.replaced);
20627
+ const errors = syncEventSchemaErrors(degraded);
20628
+ if (errors === void 0) return degraded;
20629
+ throw new KxmConfigError([{
20630
+ phase: "schema",
20631
+ code: "sync_event_invalid",
20632
+ file: `${event.runId}#${event.sequence}`,
20633
+ message: `event cannot be derived into kxm.sync-event.v1: ${errors}`
20634
+ }]);
20635
+ }
20636
+ function kxmSyncEventBytes(event) {
20637
+ return kxmCanonicalJson(event);
20638
+ }
20639
+ 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
20640
  var init_sync_transform = __esm({
20423
20641
  "plugins/kxm/src/sync-transform.ts"() {
20424
20642
  "use strict";
20425
20643
  init_project_config();
20426
20644
  init_redact();
20645
+ KXM_SYNC_EVENT_SCHEMA = "kxm.sync-event.v1";
20646
+ KXM_SYNC_REDACTOR_VERSION = "redactor-v1";
20427
20647
  KXM_DEFAULT_SYNC_POLICY = Object.freeze({
20428
20648
  prompts: "title-only",
20429
20649
  results: "bounded-summary",
@@ -20435,6 +20655,98 @@ var init_sync_transform = __esm({
20435
20655
  environmentValues: false
20436
20656
  });
20437
20657
  KXM_DEFAULT_SYNC_POLICY_REVISION = `sha256:${createHash10("sha256").update(kxmCanonicalJson(KXM_DEFAULT_SYNC_POLICY), "utf8").digest("hex")}`;
20658
+ MIN_REGISTERED_SECRET_CHARS = 4;
20659
+ REDACTED = "[redacted]";
20660
+ PATH_REDACTED = "[path]";
20661
+ CREDENTIAL_SHAPES = [
20662
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g,
20663
+ /\bssh-(?:rsa|ed25519|dss|ecdsa-[a-z0-9-]+) AAAA[0-9A-Za-z+/=]+/g,
20664
+ /\bAKIA[0-9A-Z]{16}\b/g,
20665
+ /\bgh[opsu]_[A-Za-z0-9]{20,}\b/g,
20666
+ /\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*\S+/gi
20667
+ ];
20668
+ URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi;
20669
+ ABSOLUTE_PATHS = [
20670
+ /\\\\[^\s\\"'`]+\\[^\s"'`]*/g,
20671
+ // UNC
20672
+ /\b[A-Za-z]:[\\/][^\s"'`]*/g,
20673
+ // Windows drive
20674
+ /(?<![\w.~:/-])~[\\/][^\s"'`]*/g,
20675
+ // home
20676
+ /(?<![\w.~:/-])\/(?:[\w.@%+-]+\/)*[\w.@%+-]+\/?/g
20677
+ // POSIX
20678
+ ];
20679
+ CONTROL_CHARACTERS = /[\u0000-\u001F\u007F]/g;
20680
+ KxmSyncRedactor = class {
20681
+ values = /* @__PURE__ */ new Set();
20682
+ register(value) {
20683
+ const trimmed = value.trim();
20684
+ if (trimmed.length >= MIN_REGISTERED_SECRET_CHARS) this.values.add(trimmed);
20685
+ }
20686
+ get size() {
20687
+ return this.values.size;
20688
+ }
20689
+ /** Scrub one free-text value; `replaced` counts every substitution. */
20690
+ scrub(input) {
20691
+ let replaced = 0;
20692
+ let text = input;
20693
+ for (const secret of [...this.values].sort((left, right) => right.length - left.length)) {
20694
+ const parts = text.split(secret);
20695
+ if (parts.length > 1) {
20696
+ replaced += parts.length - 1;
20697
+ text = parts.join(REDACTED);
20698
+ }
20699
+ }
20700
+ const substitute = (pattern, replacement) => {
20701
+ text = text.replace(pattern, (...args) => {
20702
+ replaced += 1;
20703
+ return typeof replacement === "string" ? replacement : replacement(...args);
20704
+ });
20705
+ };
20706
+ for (const pattern of CREDENTIAL_SHAPES) substitute(pattern, REDACTED);
20707
+ substitute(URL_USERINFO, (_match, scheme) => `${scheme}${REDACTED}@`);
20708
+ const shaped = redactSecrets(text);
20709
+ if (shaped !== text) {
20710
+ replaced += shaped.split(REDACTED).length - text.split(REDACTED).length;
20711
+ text = shaped;
20712
+ }
20713
+ for (const pattern of ABSOLUTE_PATHS) substitute(pattern, PATH_REDACTED);
20714
+ text = text.replace(CONTROL_CHARACTERS, " ").replace(/\s+/g, " ").trim();
20715
+ return { text, replaced };
20716
+ }
20717
+ };
20718
+ SyncPayloadBuilder = class {
20719
+ payload = {};
20720
+ omitted = /* @__PURE__ */ new Set();
20721
+ replaced = 0;
20722
+ redactor;
20723
+ constructor(redactor) {
20724
+ this.redactor = redactor;
20725
+ }
20726
+ /** Bounded, scrubbed free text; empty after scrubbing means absent. */
20727
+ text(value, max) {
20728
+ if (typeof value !== "string") return void 0;
20729
+ const { text, replaced } = this.redactor.scrub(value);
20730
+ this.replaced += replaced;
20731
+ if (text.length === 0) return void 0;
20732
+ return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
20733
+ }
20734
+ /** A nested record rebuilt key by key, with the named keys scrubbed as text. */
20735
+ record(source, keys, textKeys = {}) {
20736
+ const scalars = pickScalars(source, keys.filter((key) => !(key in textKeys)));
20737
+ const result = { ...scalars ?? {} };
20738
+ if (isRecord(source)) {
20739
+ for (const [key, max] of Object.entries(textKeys)) {
20740
+ const value = this.text(source[key], max);
20741
+ if (value !== void 0) result[key] = value;
20742
+ }
20743
+ }
20744
+ return Object.keys(result).length > 0 ? result : void 0;
20745
+ }
20746
+ set(key, value) {
20747
+ if (value !== void 0) this.payload[key] = value;
20748
+ }
20749
+ };
20438
20750
  SCALAR_FIELDS = [
20439
20751
  "workflowId",
20440
20752
  "promptHash",
@@ -20452,6 +20764,10 @@ var init_sync_transform = __esm({
20452
20764
  "scopeEpoch",
20453
20765
  "resolutionAction"
20454
20766
  ];
20767
+ REF_KEYS = ["id", "kind", "hash", "status", "producerId"];
20768
+ ARTIFACT_KEYS = ["id", "kind", "hash", "size", "classification", "availability"];
20769
+ LEASE_KEYS = ["id", "fencingToken", "acquiredAt", "expiresAt", "operation"];
20770
+ MAX_REFS = 64;
20455
20771
  CARRIED_FIELDS = /* @__PURE__ */ new Set([
20456
20772
  ...SCALAR_FIELDS,
20457
20773
  "displayTitle",
@@ -20472,6 +20788,7 @@ var init_sync_transform = __esm({
20472
20788
  "suppliedRefs",
20473
20789
  "counts"
20474
20790
  ]);
20791
+ CONTROL_FIELDS = ["effect", "lease", "actor", "resolvedBy", "resolutionAction", "reason", "suppliedRefs", "outcome"];
20475
20792
  }
20476
20793
  });
20477
20794
 
@@ -20551,22 +20868,25 @@ function ensureWalJournalMode(database, file, description, timeoutMs = 5e3) {
20551
20868
  Atomics.wait(sleeper, 0, 0, 10);
20552
20869
  }
20553
20870
  }
20871
+ function assertDatabaseFile(file, description) {
20872
+ checkedParent(file, description);
20873
+ const stat = lstatSync3(file, { throwIfNoEntry: false });
20874
+ if (stat) {
20875
+ if (stat.isSymbolicLink() || !stat.isFile()) {
20876
+ throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
20877
+ }
20878
+ }
20879
+ for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
20880
+ const info = lstatSync3(sidecar, { throwIfNoEntry: false });
20881
+ if (info?.isSymbolicLink()) {
20882
+ throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
20883
+ }
20884
+ }
20885
+ }
20554
20886
  function openDatabase(file, description, spec) {
20555
20887
  const isMemory = file === ":memory:";
20556
20888
  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
- }
20889
+ assertDatabaseFile(file, description);
20570
20890
  }
20571
20891
  const database = new DatabaseSync(file);
20572
20892
  let transaction = false;
@@ -20595,7 +20915,7 @@ function openDatabase(file, description, spec) {
20595
20915
  throw databaseError(
20596
20916
  "runtime_schema_outdated",
20597
20917
  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`
20918
+ `${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
20919
  );
20600
20920
  }
20601
20921
  if (spec.tables) {
@@ -20615,6 +20935,84 @@ function openDatabase(file, description, spec) {
20615
20935
  throw error;
20616
20936
  }
20617
20937
  }
20938
+ function monotonicNowMs() {
20939
+ return Number(process.hrtime.bigint() / 1000000n);
20940
+ }
20941
+ function finiteNow(clock, label) {
20942
+ const now = clock();
20943
+ if (typeof now !== "number" || !Number.isFinite(now)) {
20944
+ throw databaseError(
20945
+ "runtime_transaction_clock_invalid",
20946
+ "transaction",
20947
+ `${label} must return a finite monotonic number; got ${String(now)}`
20948
+ );
20949
+ }
20950
+ return now;
20951
+ }
20952
+ function isTransactionContention(error) {
20953
+ const carrier = error;
20954
+ for (const value of [carrier?.errcode, carrier?.errCode, carrier?.errno]) {
20955
+ if (typeof value === "number" && Number.isInteger(value)) return CONTENTION_PRIMARY_CODES.includes(value & 255);
20956
+ }
20957
+ for (const value of [carrier?.code, carrier?.name]) {
20958
+ if (typeof value === "string" && SQLITE_RESULT_NAMES.test(value)) {
20959
+ return CONTENTION_SYMBOLIC_NAMES.test(value);
20960
+ }
20961
+ }
20962
+ return CONTENTION_MESSAGES.test(error instanceof Error ? error.message : String(error));
20963
+ }
20964
+ function withDatabaseTransaction(database, work, mode = "IMMEDIATE", clock = monotonicNowMs) {
20965
+ if (activeTransactions.has(database)) {
20966
+ throw databaseError("runtime_transaction_nested", "transaction", "nested transactions are not allowed");
20967
+ }
20968
+ if (mode !== "DEFERRED") {
20969
+ const deadlines = transactionThrottles.get(database);
20970
+ const until = deadlines?.get(clock);
20971
+ if (until !== void 0) {
20972
+ const remaining = until - finiteNow(clock, "the transaction clock");
20973
+ if (remaining > 0) {
20974
+ throw databaseError(
20975
+ "runtime_transaction_busy",
20976
+ "transaction",
20977
+ `a previous BEGIN was blocked on this database; retry deferred ${String(remaining)}ms`
20978
+ );
20979
+ }
20980
+ deadlines?.delete(clock);
20981
+ }
20982
+ }
20983
+ try {
20984
+ database.exec(`BEGIN ${mode}`);
20985
+ } catch (error) {
20986
+ if (!isTransactionContention(error)) throw error;
20987
+ const now = finiteNow(clock, "the transaction clock");
20988
+ let deadlines = transactionThrottles.get(database);
20989
+ if (deadlines === void 0) {
20990
+ deadlines = /* @__PURE__ */ new WeakMap();
20991
+ transactionThrottles.set(database, deadlines);
20992
+ }
20993
+ deadlines.set(clock, now + TRANSACTION_BUSY_BACKOFF_MS);
20994
+ throw databaseError(
20995
+ "runtime_transaction_busy",
20996
+ "transaction",
20997
+ `BEGIN ${mode} blocked by another transaction: ${error instanceof Error ? error.message : String(error)}`
20998
+ );
20999
+ }
21000
+ activeTransactions.add(database);
21001
+ if (mode !== "DEFERRED") transactionThrottles.get(database)?.delete(clock);
21002
+ try {
21003
+ const result = work();
21004
+ database.exec("COMMIT");
21005
+ return result;
21006
+ } catch (error) {
21007
+ try {
21008
+ database.exec("ROLLBACK");
21009
+ } catch {
21010
+ }
21011
+ throw error;
21012
+ } finally {
21013
+ activeTransactions.delete(database);
21014
+ }
21015
+ }
20618
21016
  function checkpointWal(database, mode = "TRUNCATE") {
20619
21017
  const row = database.prepare(`PRAGMA wal_checkpoint(${mode})`).get();
20620
21018
  return {
@@ -21083,16 +21481,23 @@ function applyRestorePlan(plan) {
21083
21481
  restoredStores
21084
21482
  };
21085
21483
  }
21086
- var KXM_BACKUP_CEILINGS;
21484
+ var activeTransactions, TRANSACTION_BUSY_BACKOFF_MS, transactionThrottles, CONTENTION_PRIMARY_CODES, CONTENTION_SYMBOLIC_NAMES, SQLITE_RESULT_NAMES, CONTENTION_MESSAGES, KXM_BACKUP_CEILINGS;
21087
21485
  var init_database = __esm({
21088
21486
  "plugins/kxm/src/database.ts"() {
21089
21487
  "use strict";
21090
21488
  init_sqlite();
21091
21489
  init_project_config();
21092
21490
  init_runtime_paths();
21491
+ activeTransactions = /* @__PURE__ */ new WeakSet();
21492
+ TRANSACTION_BUSY_BACKOFF_MS = 1e3;
21493
+ transactionThrottles = /* @__PURE__ */ new WeakMap();
21494
+ CONTENTION_PRIMARY_CODES = [5, 6, 15];
21495
+ CONTENTION_SYMBOLIC_NAMES = /^SQLITE_(?:BUSY|LOCKED|PROTOCOL)(?:_[A-Z0-9]+)?$/;
21496
+ SQLITE_RESULT_NAMES = /^SQLITE_[A-Z][A-Z0-9_]*$/;
21497
+ CONTENTION_MESSAGES = /^(?:database is locked|database table is locked|locking protocol|SQLITE_BUSY|SQLITE_LOCKED|SQLITE_PROTOCOL)(?:$|[\s.:])/i;
21093
21498
  KXM_BACKUP_CEILINGS = {
21094
21499
  "hub-store": 5,
21095
- registry: 1,
21500
+ registry: 2,
21096
21501
  "binding-store": 1,
21097
21502
  events: 7
21098
21503
  };
@@ -21100,8 +21505,10 @@ var init_database = __esm({
21100
21505
  });
21101
21506
 
21102
21507
  // 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";
21508
+ import { spawnSync as spawnSync4 } from "node:child_process";
21509
+ import { createHash as createHash12, randomUUID as randomUUID4 } from "node:crypto";
21510
+ import { existsSync as existsSync10, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync4, statSync, writeFileSync as writeFileSync6 } from "node:fs";
21511
+ import { dirname as dirname7, isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
21105
21512
  function runtimeIssue(phase, code, file, message) {
21106
21513
  return { phase, code, file, message };
21107
21514
  }
@@ -21119,6 +21526,106 @@ function projectRuntimeOwnsRun(projectRoot, runId, env) {
21119
21526
  database.close();
21120
21527
  }
21121
21528
  }
21529
+ function registrationFromRow(row) {
21530
+ return {
21531
+ projectId: row.project_id,
21532
+ projectRoot: row.project_root,
21533
+ projectKey: row.project_key,
21534
+ homeRuntimeId: row.home_runtime_id,
21535
+ ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
21536
+ ...row.lane_of !== null ? { laneOf: row.lane_of } : {},
21537
+ registeredAt: row.registered_at
21538
+ };
21539
+ }
21540
+ function controlRootDirectoryExists(projectRoot) {
21541
+ const stat = statSync(projectRoot, { throwIfNoEntry: false });
21542
+ return Boolean(stat?.isDirectory());
21543
+ }
21544
+ function gitEnv() {
21545
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
21546
+ }
21547
+ function gitCommonDirectory(projectRoot) {
21548
+ const result = spawnSync4("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
21549
+ encoding: "utf8",
21550
+ env: gitEnv(),
21551
+ timeout: 3e4,
21552
+ windowsHide: true
21553
+ });
21554
+ if (result.status !== 0) return void 0;
21555
+ const text = (result.stdout ?? "").trim();
21556
+ if (!text) return void 0;
21557
+ const absolute = isAbsolute5(text) ? text : resolve8(projectRoot, text);
21558
+ let canonical2;
21559
+ try {
21560
+ canonical2 = realpathSync4.native(absolute);
21561
+ } catch {
21562
+ canonical2 = resolve8(absolute);
21563
+ }
21564
+ return process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
21565
+ }
21566
+ function isPrimaryWorktree(projectRoot, commonDir) {
21567
+ if (commonDir === void 0) return false;
21568
+ const dotGit = resolve8(projectRoot, ".git");
21569
+ let canonical2;
21570
+ try {
21571
+ canonical2 = realpathSync4.native(dotGit);
21572
+ } catch {
21573
+ canonical2 = resolve8(dotGit);
21574
+ }
21575
+ if (process.platform === "win32") canonical2 = canonical2.toLocaleLowerCase("en-US");
21576
+ return canonical2 === commonDir;
21577
+ }
21578
+ function migrateRegistryLanes(file) {
21579
+ const stat = lstatSync4(file, { throwIfNoEntry: false });
21580
+ if (!stat) return;
21581
+ assertDatabaseFile(file, "runtime registry");
21582
+ const database = new DatabaseSync(file);
21583
+ let transaction = false;
21584
+ try {
21585
+ database.exec("PRAGMA busy_timeout = 5000");
21586
+ const current = database.prepare("PRAGMA user_version").get();
21587
+ if ((current?.user_version ?? 0) !== 1) return;
21588
+ database.exec("BEGIN IMMEDIATE");
21589
+ transaction = true;
21590
+ const locked = database.prepare("PRAGMA user_version").get();
21591
+ if ((locked?.user_version ?? 0) !== 1) {
21592
+ database.exec("ROLLBACK");
21593
+ transaction = false;
21594
+ return;
21595
+ }
21596
+ if (!tableColumns(database, "projects").includes("lane_of")) {
21597
+ database.exec(`
21598
+ CREATE TABLE projects_v2 (
21599
+ project_key TEXT PRIMARY KEY,
21600
+ project_id TEXT NOT NULL,
21601
+ project_root TEXT NOT NULL,
21602
+ home_runtime_id TEXT NOT NULL,
21603
+ config_revision TEXT,
21604
+ registered_at TEXT NOT NULL,
21605
+ lane_of TEXT
21606
+ ) STRICT;
21607
+ INSERT INTO projects_v2 (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
21608
+ SELECT project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, NULL FROM projects;
21609
+ DROP TABLE projects;
21610
+ ALTER TABLE projects_v2 RENAME TO projects;
21611
+ CREATE INDEX projects_by_project_id ON projects (project_id);
21612
+ `);
21613
+ }
21614
+ database.exec("PRAGMA user_version = 2");
21615
+ database.exec("COMMIT");
21616
+ transaction = false;
21617
+ } catch (error) {
21618
+ if (transaction) {
21619
+ try {
21620
+ database.exec("ROLLBACK");
21621
+ } catch {
21622
+ }
21623
+ }
21624
+ throw error;
21625
+ } finally {
21626
+ database.close();
21627
+ }
21628
+ }
21122
21629
  function readKxmSupervisorRecord(database) {
21123
21630
  const row = database.prepare("SELECT runtime_id, pid, port, token_hash, started_at, heartbeat_at, state FROM supervisor WHERE singleton_id = 1").get();
21124
21631
  return row ? {
@@ -21131,7 +21638,326 @@ function readKxmSupervisorRecord(database) {
21131
21638
  state: row.state
21132
21639
  } : void 0;
21133
21640
  }
21134
- var KXM_REGISTRY_SCHEMA_VERSION, REGISTRY_TABLES, REGISTRY_SCHEMA, KxmRuntimeRegistry, DRIVE_RECEIPT_MAX_BYTES, EVENT_STORE_TABLES, KXM_EVENT_STORE_TABLE_NAMES;
21641
+ function coordinatorFromRow(row) {
21642
+ const parsed = JSON.parse(row.record);
21643
+ if (row.schema !== "kxm.coordinator.v1") {
21644
+ throw runtimeError("coordinator_record_divergent", row.coordinator_id, `unexpected coordinator schema ${row.schema}`);
21645
+ }
21646
+ validateCoordinator(parsed, row.coordinator_id);
21647
+ 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) {
21648
+ throw runtimeError("coordinator_record_divergent", row.coordinator_id, "coordinator columns do not match the persisted record");
21649
+ }
21650
+ return {
21651
+ coordinatorId: row.coordinator_id,
21652
+ projectId: row.project_id,
21653
+ role: row.role,
21654
+ channel: row.channel,
21655
+ ceilingHash: row.ceiling_hash,
21656
+ configRevision: row.config_revision,
21657
+ boundAt: row.bound_at,
21658
+ record: row.record
21659
+ };
21660
+ }
21661
+ function intakeFromRow(row) {
21662
+ const parsed = JSON.parse(row.record);
21663
+ if (row.schema !== "kxm.intake-message.v1") {
21664
+ throw runtimeError("intake_record_divergent", row.message_id, `unexpected intake schema ${row.schema}`);
21665
+ }
21666
+ validateIntakeMessage(parsed, row.message_id);
21667
+ 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) {
21668
+ throw runtimeError("intake_record_divergent", row.message_id, "intake columns do not match the persisted record");
21669
+ }
21670
+ return {
21671
+ messageId: row.message_id,
21672
+ projectId: row.project_id,
21673
+ coordinatorId: row.coordinator_id,
21674
+ idempotencyKey: row.idempotency_key,
21675
+ contentHash: row.content_hash,
21676
+ receivedAt: row.received_at,
21677
+ dispatchState: row.dispatch_state,
21678
+ record: row.record
21679
+ };
21680
+ }
21681
+ function controlFromRow(row) {
21682
+ const paused = row.paused === 1;
21683
+ const expected = kxmCanonicalJson({
21684
+ schema: "kxm.project-control.v1",
21685
+ projectId: row.project_id,
21686
+ paused,
21687
+ ...row.reason !== null ? { reason: row.reason } : {},
21688
+ updatedAt: row.updated_at,
21689
+ actor: row.actor
21690
+ });
21691
+ if (row.record !== expected) {
21692
+ throw runtimeError("project_control_divergent", row.project_id, "project control record does not match its columns");
21693
+ }
21694
+ return {
21695
+ projectId: row.project_id,
21696
+ paused,
21697
+ ...row.reason !== null ? { reason: row.reason } : {},
21698
+ updatedAt: row.updated_at,
21699
+ actor: row.actor,
21700
+ record: row.record
21701
+ };
21702
+ }
21703
+ function parseDriveReceipt(raw) {
21704
+ const parsed = JSON.parse(raw);
21705
+ validateDriveReceipt(parsed, "drive-receipt");
21706
+ return parsed;
21707
+ }
21708
+ function outboxFromRow(row) {
21709
+ return {
21710
+ seq: row.seq,
21711
+ runId: row.run_id,
21712
+ sequence: row.sequence,
21713
+ syncEvent: row.sync_event,
21714
+ attemptCount: row.attempt_count,
21715
+ ...row.attempted_at !== null ? { attemptedAt: row.attempted_at } : {},
21716
+ ...row.acked_at !== null ? { ackedAt: row.acked_at } : {},
21717
+ ...row.refused_code !== null ? { refusedCode: row.refused_code } : {},
21718
+ ...row.refused_at !== null ? { refusedAt: row.refused_at } : {}
21719
+ };
21720
+ }
21721
+ function runFromRow(row) {
21722
+ if (typeof row.memory_revision !== "string" || row.memory_revision.length === 0) {
21723
+ throw runtimeError("runtime_schema_shape_invalid", row.run_id, "memory_revision is required");
21724
+ }
21725
+ return {
21726
+ runId: row.run_id,
21727
+ projectId: row.project_id,
21728
+ homeRuntimeId: row.home_runtime_id,
21729
+ workflowId: row.workflow_id,
21730
+ promptSha256: row.prompt_sha256,
21731
+ status: row.status,
21732
+ configRevision: row.config_revision,
21733
+ memoryRevision: row.memory_revision,
21734
+ executorPolicyRevision: row.executor_policy_revision,
21735
+ toolPolicyRevision: row.tool_policy_revision,
21736
+ createdAt: row.created_at,
21737
+ updatedAt: row.updated_at
21738
+ };
21739
+ }
21740
+ function gateRowContentHash(table, row) {
21741
+ const copy = { ...row };
21742
+ delete copy.contentHash;
21743
+ void table;
21744
+ return `sha256:${createHash12("sha256").update(kxmCanonicalJson(copy), "utf8").digest("hex")}`;
21745
+ }
21746
+ function assertClosedGateObservation(kind, row, id = "observation") {
21747
+ if (row.completeness === "incomplete") return;
21748
+ if (row.completeness === "no-start") {
21749
+ const legitimateStartFailure = (row.errorClass === "validation" || row.errorClass === "spawn-error") && row.stopCause === "none";
21750
+ const requestedStop = row.stopCause === "cancel" && row.errorClass === null;
21751
+ 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) {
21752
+ throw gateRowInvalid(id, "no-start observation forbids PID/exit/close/stream/check facts and requires a legitimate no-start cause");
21753
+ }
21754
+ return;
21755
+ }
21756
+ if (kind === "command") {
21757
+ 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) {
21758
+ throw gateRowInvalid(id, "complete command observation requires spawn, numeric exit, no signal, exit/close, complete streams, and stopCause none");
21759
+ }
21760
+ return;
21761
+ }
21762
+ 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) {
21763
+ throw gateRowInvalid(id, "complete artifacts observation requires consistent counts and no command facts");
21764
+ }
21765
+ }
21766
+ function gateRowInvalid(file, message) {
21767
+ return runtimeError("gate_row_invalid", file, message);
21768
+ }
21769
+ function assertExactRowKeys(row, keys, id) {
21770
+ const actual = Object.keys(row);
21771
+ if (actual.length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(row, key))) {
21772
+ throw gateRowInvalid(id, "gate row does not have the exact key set");
21773
+ }
21774
+ for (const key of actual) {
21775
+ if (FORBIDDEN_TEXT_KEYS.has(key)) throw gateRowInvalid(id, "gate row must not carry raw stdout/stderr/error text");
21776
+ }
21777
+ }
21778
+ function assertId(value, prefix, id, label) {
21779
+ if (typeof value !== "string" || !value.startsWith(prefix) || !OPAQUE.test(value)) {
21780
+ throw gateRowInvalid(id, `${label} is not a valid ${prefix} opaque id`);
21781
+ }
21782
+ }
21783
+ function assertSha(value, id, label) {
21784
+ if (typeof value !== "string" || !SHA2562.test(value)) throw gateRowInvalid(id, `${label} is not a sha256 hash`);
21785
+ }
21786
+ function assertFlag(value, id, label) {
21787
+ if (value !== 0 && value !== 1) throw gateRowInvalid(id, `${label} must be 0 or 1`);
21788
+ }
21789
+ function assertNullInt(value, id, label) {
21790
+ if (value !== null && (typeof value !== "number" || !Number.isInteger(value))) {
21791
+ throw gateRowInvalid(id, `${label} must be an integer or null`);
21792
+ }
21793
+ }
21794
+ function assertGateAttemptRow(row) {
21795
+ assertExactRowKeys(row, GATE_ATTEMPT_KEYS, row.attemptId);
21796
+ assertId(row.attemptId, "att_", row.attemptId, "attemptId");
21797
+ assertId(row.effectId, "eff_", row.attemptId, "effectId");
21798
+ assertId(row.intentEventId, "evt_", row.attemptId, "intentEventId");
21799
+ assertSha(row.gateDefinitionHash, row.attemptId, "gateDefinitionHash");
21800
+ assertSha(row.registryHash, row.attemptId, "registryHash");
21801
+ assertSha(row.runPlanHash, row.attemptId, "runPlanHash");
21802
+ if (row.producerId !== "kxm-gate") throw gateRowInvalid(row.attemptId, "producerId must be kxm-gate");
21803
+ if (row.gateKind !== "command" && row.gateKind !== "artifacts-exist") {
21804
+ throw gateRowInvalid(row.attemptId, "gateKind is invalid");
21805
+ }
21806
+ if (row.expect !== "pass" && row.expect !== "fail") throw gateRowInvalid(row.attemptId, "expect is invalid");
21807
+ if (row.gateKind === "artifacts-exist" && row.expect === "fail") {
21808
+ throw gateRowInvalid(row.attemptId, "artifacts-exist cannot expect fail");
21809
+ }
21810
+ if (typeof row.controlProjectKey !== "string" || row.controlProjectKey.length === 0) {
21811
+ throw gateRowInvalid(row.attemptId, "controlProjectKey is required");
21812
+ }
21813
+ if (gateRowContentHash("gate_attempts", row) !== row.contentHash) {
21814
+ throw gateRowInvalid(row.attemptId, "contentHash does not match the canonical row");
21815
+ }
21816
+ }
21817
+ function assertGateObservationRow(row) {
21818
+ assertExactRowKeys(row, GATE_OBSERVATION_KEYS, row.observationId);
21819
+ assertId(row.observationId, "obs_", row.observationId, "observationId");
21820
+ assertId(row.attemptId, "att_", row.observationId, "attemptId");
21821
+ assertId(row.effectId, "eff_", row.observationId, "effectId");
21822
+ assertId(row.recordedEventId, "evt_", row.observationId, "recordedEventId");
21823
+ if (!["complete", "incomplete", "no-start"].includes(row.completeness)) {
21824
+ throw gateRowInvalid(row.observationId, "completeness is invalid");
21825
+ }
21826
+ assertFlag(row.spawned, row.observationId, "spawned");
21827
+ assertFlag(row.exitObserved, row.observationId, "exitObserved");
21828
+ assertFlag(row.closeObserved, row.observationId, "closeObserved");
21829
+ assertNullInt(row.pid, row.observationId, "pid");
21830
+ assertNullInt(row.exitCode, row.observationId, "exitCode");
21831
+ assertNullInt(row.stdoutBytes, row.observationId, "stdoutBytes");
21832
+ assertNullInt(row.stderrBytes, row.observationId, "stderrBytes");
21833
+ assertNullInt(row.checkedCount, row.observationId, "checkedCount");
21834
+ assertNullInt(row.failedCount, row.observationId, "failedCount");
21835
+ if (typeof row.elapsedMs !== "number" || !Number.isInteger(row.elapsedMs) || row.elapsedMs < 0) {
21836
+ throw gateRowInvalid(row.observationId, "elapsedMs is invalid");
21837
+ }
21838
+ if (row.stdoutSha256 !== null) assertSha(row.stdoutSha256, row.observationId, "stdoutSha256");
21839
+ if (row.stderrSha256 !== null) assertSha(row.stderrSha256, row.observationId, "stderrSha256");
21840
+ if (row.stdoutComplete !== null && row.stdoutComplete !== 0 && row.stdoutComplete !== 1) {
21841
+ throw gateRowInvalid(row.observationId, "stdoutComplete must be 0, 1, or null");
21842
+ }
21843
+ if (row.stderrComplete !== null && row.stderrComplete !== 0 && row.stderrComplete !== 1) {
21844
+ throw gateRowInvalid(row.observationId, "stderrComplete must be 0, 1, or null");
21845
+ }
21846
+ if (gateRowContentHash("gate_observations", row) !== row.contentHash) {
21847
+ throw gateRowInvalid(row.observationId, "contentHash does not match the canonical row");
21848
+ }
21849
+ }
21850
+ function assertGateEvidenceRow(row) {
21851
+ assertExactRowKeys(row, GATE_EVIDENCE_KEYS, row.evidenceId);
21852
+ assertId(row.evidenceId, "gev_", row.evidenceId, "evidenceId");
21853
+ assertId(row.attemptId, "att_", row.evidenceId, "attemptId");
21854
+ assertId(row.observationId, "obs_", row.evidenceId, "observationId");
21855
+ assertId(row.effectId, "eff_", row.evidenceId, "effectId");
21856
+ assertId(row.settledEventId, "evt_", row.evidenceId, "settledEventId");
21857
+ if (row.kind !== "gate") throw gateRowInvalid(row.evidenceId, "kind must be gate");
21858
+ if (!["passed", "implementation-failure", "repro-missing"].includes(row.outcome)) {
21859
+ throw gateRowInvalid(row.evidenceId, "outcome is invalid");
21860
+ }
21861
+ if (gateRowContentHash("gate_evidence", row) !== row.contentHash) {
21862
+ throw gateRowInvalid(row.evidenceId, "contentHash does not match the canonical row");
21863
+ }
21864
+ }
21865
+ function assertSharedIdentity(row, attempt, id) {
21866
+ 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) {
21867
+ throw gateRowInvalid(id, "row identity does not match the gate attempt");
21868
+ }
21869
+ }
21870
+ function gateAttemptFromSql(row) {
21871
+ return {
21872
+ attemptId: row.attempt_id,
21873
+ runId: row.run_id,
21874
+ projectId: row.project_id,
21875
+ homeRuntimeId: row.home_runtime_id,
21876
+ stepId: row.step_id,
21877
+ stepAttempt: row.step_attempt,
21878
+ assignmentId: row.assignment_id,
21879
+ effectId: row.effect_id,
21880
+ gateId: row.gate_id,
21881
+ gateKind: row.gate_kind,
21882
+ expect: row.expect,
21883
+ gateDefinitionHash: row.gate_definition_hash,
21884
+ registryHash: row.registry_hash,
21885
+ runPlanHash: row.run_plan_hash,
21886
+ controlProjectKey: row.control_project_key,
21887
+ producerId: row.producer_id,
21888
+ intentEventId: row.intent_event_id,
21889
+ contentHash: row.content_hash
21890
+ };
21891
+ }
21892
+ function gateObservationFromSql(row) {
21893
+ return {
21894
+ observationId: row.observation_id,
21895
+ attemptId: row.attempt_id,
21896
+ runId: row.run_id,
21897
+ projectId: row.project_id,
21898
+ homeRuntimeId: row.home_runtime_id,
21899
+ stepId: row.step_id,
21900
+ stepAttempt: row.step_attempt,
21901
+ assignmentId: row.assignment_id,
21902
+ effectId: row.effect_id,
21903
+ completeness: row.completeness,
21904
+ spawned: row.spawned === 1 ? 1 : 0,
21905
+ pid: row.pid,
21906
+ exitCode: row.exit_code,
21907
+ signal: row.signal,
21908
+ exitObserved: row.exit_observed === 1 ? 1 : 0,
21909
+ closeObserved: row.close_observed === 1 ? 1 : 0,
21910
+ stopCause: row.stop_cause,
21911
+ signalsAttempted: row.signals_attempted,
21912
+ errorClass: row.error_class,
21913
+ stdoutSha256: row.stdout_sha256,
21914
+ stdoutBytes: row.stdout_bytes,
21915
+ stdoutComplete: row.stdout_complete === null ? null : row.stdout_complete === 1 ? 1 : 0,
21916
+ stderrSha256: row.stderr_sha256,
21917
+ stderrBytes: row.stderr_bytes,
21918
+ stderrComplete: row.stderr_complete === null ? null : row.stderr_complete === 1 ? 1 : 0,
21919
+ checkedCount: row.checked_count,
21920
+ failedCount: row.failed_count,
21921
+ elapsedMs: row.elapsed_ms,
21922
+ startedAt: row.started_at,
21923
+ finishedAt: row.finished_at,
21924
+ recordedEventId: row.recorded_event_id,
21925
+ contentHash: row.content_hash
21926
+ };
21927
+ }
21928
+ function gateEvidenceFromSql(row) {
21929
+ return {
21930
+ evidenceId: row.evidence_id,
21931
+ attemptId: row.attempt_id,
21932
+ observationId: row.observation_id,
21933
+ runId: row.run_id,
21934
+ projectId: row.project_id,
21935
+ homeRuntimeId: row.home_runtime_id,
21936
+ stepId: row.step_id,
21937
+ stepAttempt: row.step_attempt,
21938
+ assignmentId: row.assignment_id,
21939
+ effectId: row.effect_id,
21940
+ evidenceKey: row.evidence_key,
21941
+ kind: row.kind,
21942
+ expect: row.expect,
21943
+ outcome: row.outcome,
21944
+ settledEventId: row.settled_event_id,
21945
+ contentHash: row.content_hash
21946
+ };
21947
+ }
21948
+ function capabilityFromRow(row) {
21949
+ return row ? {
21950
+ attemptId: row.attempt_id,
21951
+ runId: row.run_id,
21952
+ assignmentId: row.assignment_id,
21953
+ stepId: row.step_id,
21954
+ stepAttempt: row.step_attempt,
21955
+ producerId: row.producer_id,
21956
+ capabilityHash: row.capability_hash,
21957
+ state: row.state
21958
+ } : void 0;
21959
+ }
21960
+ 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
21961
  var init_runtime_store = __esm({
21136
21962
  "plugins/kxm/src/runtime-store.ts"() {
21137
21963
  "use strict";
@@ -21141,10 +21967,10 @@ var init_runtime_store = __esm({
21141
21967
  init_sync_transform();
21142
21968
  init_runtime_paths();
21143
21969
  init_database();
21144
- KXM_REGISTRY_SCHEMA_VERSION = 1;
21970
+ KXM_REGISTRY_SCHEMA_VERSION = 2;
21145
21971
  REGISTRY_TABLES = {
21146
21972
  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"]
21973
+ projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at", "lane_of"]
21148
21974
  };
21149
21975
  REGISTRY_SCHEMA = `
21150
21976
  CREATE TABLE supervisor (
@@ -21158,19 +21984,23 @@ CREATE TABLE supervisor (
21158
21984
  state TEXT NOT NULL
21159
21985
  ) STRICT;
21160
21986
  CREATE TABLE projects (
21161
- project_id TEXT PRIMARY KEY,
21987
+ project_key TEXT PRIMARY KEY,
21988
+ project_id TEXT NOT NULL,
21162
21989
  project_root TEXT NOT NULL,
21163
- project_key TEXT NOT NULL UNIQUE,
21164
21990
  home_runtime_id TEXT NOT NULL,
21165
21991
  config_revision TEXT,
21166
- registered_at TEXT NOT NULL
21992
+ registered_at TEXT NOT NULL,
21993
+ lane_of TEXT
21167
21994
  ) STRICT;
21995
+ CREATE INDEX projects_by_project_id ON projects (project_id);
21168
21996
  `;
21997
+ PROJECT_COLUMNS = "project_id, project_root, project_key, home_runtime_id, config_revision, registered_at, lane_of";
21169
21998
  KxmRuntimeRegistry = class {
21170
21999
  path;
21171
22000
  database;
21172
22001
  constructor(path4) {
21173
22002
  this.path = resolve8(path4);
22003
+ migrateRegistryLanes(this.path);
21174
22004
  this.database = openDatabase(this.path, "runtime registry", {
21175
22005
  schema: REGISTRY_SCHEMA,
21176
22006
  version: KXM_REGISTRY_SCHEMA_VERSION,
@@ -21249,57 +22079,188 @@ CREATE TABLE projects (
21249
22079
  supervisor() {
21250
22080
  return this.readSupervisorRow();
21251
22081
  }
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. */
22082
+ /** All projects registered to this Runtime, including lane roots, for restart
22083
+ * recovery: the supervisor reopens their contexts so pending outbox rows
22084
+ * resume syncing and presence keeps beating. */
21256
22085
  projectsForRuntime(homeRuntimeId) {
21257
22086
  const rows = this.database.prepare(
21258
22087
  "SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
21259
22088
  ).all(homeRuntimeId);
21260
22089
  return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
21261
22090
  }
22091
+ /**
22092
+ * Register or revalidate a control root. The home runtime of a live row is
22093
+ * immutable. A second root whose git common directory matches a live row
22094
+ * for the same project is a lane, unless the registering root is the primary
22095
+ * worktree and the live home row is not: then the primary becomes the home
22096
+ * row (same home runtime id) and every other live row of that repository
22097
+ * becomes its lane. A primary never becomes a lane. A root whose directory
22098
+ * is gone is replaced. A live root that is a different repository is
22099
+ * `project_home_conflict`.
22100
+ */
21262
22101
  registerProject(registration) {
21263
22102
  const projectRoot = resolve8(registration.projectRoot);
21264
22103
  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) {
22104
+ const candidateCommon = gitCommonDirectory(projectRoot);
22105
+ const commonByRoot = /* @__PURE__ */ new Map();
22106
+ for (let attempt = 0; attempt < 8; attempt += 1) {
22107
+ this.database.exec("BEGIN IMMEDIATE");
22108
+ let rootsToMeasure;
22109
+ try {
22110
+ const byKey = this.projectRow("SELECT " + PROJECT_COLUMNS + " FROM projects WHERE project_key = ?", projectKey);
22111
+ if (byKey) {
22112
+ const problems = [];
22113
+ if (byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
22114
+ if (byKey.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
22115
+ if (problems.length > 0) {
22116
+ this.database.exec("ROLLBACK");
22117
+ throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
22118
+ }
22119
+ const result = registrationFromRow(byKey);
22120
+ this.database.exec("COMMIT");
22121
+ return result;
22122
+ }
22123
+ const rows = this.database.prepare(
22124
+ `SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY registered_at`
22125
+ ).all(registration.projectId);
22126
+ const live = rows.filter((row) => controlRootDirectoryExists(row.project_root));
22127
+ const dead = rows.filter((row) => !controlRootDirectoryExists(row.project_root));
22128
+ const unknown = live.filter((row) => !commonByRoot.has(row.project_root));
22129
+ if (unknown.length > 0) {
22130
+ rootsToMeasure = unknown.map((row) => row.project_root);
21276
22131
  this.database.exec("ROLLBACK");
21277
- throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
22132
+ } else if (live.length > 0) {
22133
+ const anchor = live.find((row) => {
22134
+ const dir = commonByRoot.get(row.project_root);
22135
+ return candidateCommon !== void 0 && dir === candidateCommon;
22136
+ });
22137
+ if (!anchor) {
22138
+ this.database.exec("ROLLBACK");
22139
+ throw runtimeError(
22140
+ "project_home_conflict",
22141
+ ".kxm/project.yaml",
22142
+ `project ${registration.projectId} is already bound to a different control root`
22143
+ );
22144
+ }
22145
+ if (live.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
22146
+ this.database.exec("ROLLBACK");
22147
+ throw runtimeError(
22148
+ "project_home_conflict",
22149
+ ".kxm/project.yaml",
22150
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
22151
+ );
22152
+ }
22153
+ const sameRepo = live.filter((row) => commonByRoot.get(row.project_root) === candidateCommon);
22154
+ const home = sameRepo.find((row) => row.lane_of === null);
22155
+ const registeringIsPrimary = isPrimaryWorktree(projectRoot, candidateCommon);
22156
+ const homeIsPrimary = home !== void 0 && isPrimaryWorktree(home.project_root, commonByRoot.get(home.project_root));
22157
+ if (registeringIsPrimary && !homeIsPrimary) {
22158
+ this.insertHome(registration, projectRoot, projectKey);
22159
+ for (const row of sameRepo) {
22160
+ this.database.prepare("UPDATE projects SET lane_of = ? WHERE project_key = ?").run(projectKey, row.project_key);
22161
+ }
22162
+ this.database.exec("COMMIT");
22163
+ registration.logger?.({
22164
+ event: "project_home_promoted",
22165
+ projectId: registration.projectId,
22166
+ projectRoot,
22167
+ homeRuntimeId: registration.homeRuntimeId,
22168
+ previousHomeRoot: home?.project_root ?? null,
22169
+ previousHomeKey: home?.project_key ?? null
22170
+ });
22171
+ return this.insertedRegistration(registration, projectRoot, projectKey);
22172
+ }
22173
+ if (registeringIsPrimary) {
22174
+ this.database.exec("ROLLBACK");
22175
+ throw runtimeError(
22176
+ "project_home_conflict",
22177
+ ".kxm/project.yaml",
22178
+ `project ${registration.projectId} primary checkout is already the home row`
22179
+ );
22180
+ }
22181
+ const laneOf = anchor.lane_of ?? anchor.project_key;
22182
+ this.database.prepare(`
22183
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
22184
+ VALUES (?, ?, ?, ?, ?, ?, ?)
22185
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now, laneOf);
22186
+ const result = this.insertedRegistration(registration, projectRoot, projectKey, laneOf);
22187
+ this.database.exec("COMMIT");
22188
+ return result;
22189
+ } else if (dead.length > 0) {
22190
+ const replaced = dead[0];
22191
+ if (dead.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
22192
+ this.database.exec("ROLLBACK");
22193
+ throw runtimeError(
22194
+ "project_home_conflict",
22195
+ ".kxm/project.yaml",
22196
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
22197
+ );
22198
+ }
22199
+ this.database.prepare("DELETE FROM projects WHERE project_id = ?").run(registration.projectId);
22200
+ this.insertHome(registration, projectRoot, projectKey);
22201
+ this.database.exec("COMMIT");
22202
+ registration.logger?.({
22203
+ event: "project_registration_replaced",
22204
+ projectId: registration.projectId,
22205
+ projectRoot,
22206
+ replacedRoot: replaced.project_root,
22207
+ replacedCount: dead.length,
22208
+ replacedHomeRuntimeId: replaced.home_runtime_id,
22209
+ replacedProjectKey: replaced.project_key
22210
+ });
22211
+ return this.insertedRegistration(registration, projectRoot, projectKey);
22212
+ } else {
22213
+ this.insertHome(registration, projectRoot, projectKey);
22214
+ this.database.exec("COMMIT");
22215
+ return this.insertedRegistration(registration, projectRoot, projectKey);
21278
22216
  }
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;
22217
+ } catch (error) {
22218
+ try {
22219
+ this.database.exec("ROLLBACK");
22220
+ } catch {
22221
+ }
22222
+ throw error;
22223
+ }
22224
+ for (const root of rootsToMeasure ?? []) {
22225
+ commonByRoot.set(root, gitCommonDirectory(root));
21289
22226
  }
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);
22227
+ }
22228
+ throw runtimeError(
22229
+ "runtime_registry_busy",
22230
+ projectRoot,
22231
+ `project ${registration.projectId} registration could not be rechecked`
22232
+ );
22233
+ }
22234
+ /** Remove one control root. Returns false when that root was not registered. */
22235
+ unregisterProject(projectRoot) {
22236
+ this.database.exec("BEGIN IMMEDIATE");
22237
+ try {
22238
+ const removed = this.deleteRegisteredRoot(projectRoot);
21294
22239
  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
- };
22240
+ return removed;
22241
+ } catch (error) {
22242
+ try {
22243
+ this.database.exec("ROLLBACK");
22244
+ } catch {
22245
+ }
22246
+ throw error;
22247
+ }
22248
+ }
22249
+ /**
22250
+ * Delete the row only when `supervisorIsLive` is false for the supervisor
22251
+ * row read inside this write transaction. A live supervisor keeps the row;
22252
+ * the caller uses the supervisor route instead of editing the file.
22253
+ */
22254
+ unregisterProjectIfIdle(projectRoot, supervisorIsLive) {
22255
+ this.database.exec("BEGIN IMMEDIATE");
22256
+ try {
22257
+ if (supervisorIsLive(this.readSupervisorRow())) {
22258
+ this.database.exec("ROLLBACK");
22259
+ return "supervisor_live";
22260
+ }
22261
+ const removed = this.deleteRegisteredRoot(projectRoot);
22262
+ this.database.exec("COMMIT");
22263
+ return removed ? "removed" : "absent";
21303
22264
  } catch (error) {
21304
22265
  try {
21305
22266
  this.database.exec("ROLLBACK");
@@ -21308,30 +22269,68 @@ CREATE TABLE projects (
21308
22269
  throw error;
21309
22270
  }
21310
22271
  }
22272
+ /** How many other rows share this home root's project. Zero when this root is a lane or missing. */
22273
+ homeLaneCount(projectRoot) {
22274
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve8(projectRoot)));
22275
+ if (!row || row.lane_of !== null) return 0;
22276
+ const count = this.database.prepare(
22277
+ "SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
22278
+ ).get(row.project_id, row.project_key);
22279
+ return Number(count?.total ?? 0);
22280
+ }
22281
+ /** Caller holds the write transaction. Does not commit. */
22282
+ deleteRegisteredRoot(projectRoot) {
22283
+ const projectKey = projectRuntimeKey(resolve8(projectRoot));
22284
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
22285
+ if (!row) return false;
22286
+ this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
22287
+ if (row.lane_of === null) {
22288
+ const successor = this.database.prepare(
22289
+ "SELECT project_key FROM projects WHERE project_id = ? ORDER BY registered_at LIMIT 1"
22290
+ ).get(row.project_id);
22291
+ if (successor) {
22292
+ this.database.prepare("UPDATE projects SET lane_of = NULL WHERE project_key = ?").run(successor.project_key);
22293
+ this.database.prepare(
22294
+ "UPDATE projects SET lane_of = ? WHERE project_id = ? AND project_key != ? AND (lane_of IS NULL OR lane_of = ?)"
22295
+ ).run(successor.project_key, row.project_id, successor.project_key, projectKey);
22296
+ }
22297
+ }
22298
+ return true;
22299
+ }
22300
+ /** The home row for a project id, or the earliest row when every row is a lane. */
21311
22301
  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;
22302
+ const row = this.projectRow(
22303
+ `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`,
22304
+ projectId
22305
+ );
22306
+ return row ? registrationFromRow(row) : void 0;
21321
22307
  }
21322
22308
  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;
22309
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(projectRoot));
22310
+ return row ? registrationFromRow(row) : void 0;
22311
+ }
22312
+ projectRow(sql, parameter) {
22313
+ return this.database.prepare(sql).get(parameter);
22314
+ }
22315
+ insertHome(registration, projectRoot, projectKey) {
22316
+ this.database.prepare(`
22317
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
22318
+ VALUES (?, ?, ?, ?, ?, ?, NULL)
22319
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
22320
+ }
22321
+ insertedRegistration(registration, projectRoot, projectKey, laneOf) {
22322
+ return {
22323
+ projectId: registration.projectId,
22324
+ projectRoot,
22325
+ projectKey,
22326
+ homeRuntimeId: registration.homeRuntimeId,
22327
+ ...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
22328
+ ...laneOf !== void 0 ? { laneOf } : {},
22329
+ registeredAt: registration.now
22330
+ };
21333
22331
  }
21334
22332
  };
22333
+ KXM_EVENT_STORE_SCHEMA_VERSION = 7;
21335
22334
  DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
21336
22335
  EVENT_STORE_TABLES = {
21337
22336
  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 +22448,969 @@ CREATE TABLE projects (
21449
22448
  ]
21450
22449
  };
21451
22450
  KXM_EVENT_STORE_TABLE_NAMES = Object.keys(EVENT_STORE_TABLES).sort();
22451
+ EVENT_STORE_SCHEMA = `
22452
+ CREATE TABLE runs (
22453
+ run_id TEXT PRIMARY KEY,
22454
+ project_id TEXT NOT NULL,
22455
+ home_runtime_id TEXT NOT NULL,
22456
+ workflow_id TEXT NOT NULL,
22457
+ prompt_sha256 TEXT NOT NULL,
22458
+ status TEXT NOT NULL,
22459
+ config_revision TEXT NOT NULL,
22460
+ memory_revision TEXT NOT NULL,
22461
+ executor_policy_revision TEXT NOT NULL,
22462
+ tool_policy_revision TEXT NOT NULL,
22463
+ created_at TEXT NOT NULL,
22464
+ updated_at TEXT NOT NULL
22465
+ ) STRICT;
22466
+ CREATE TABLE events (
22467
+ project_id TEXT NOT NULL,
22468
+ run_id TEXT NOT NULL,
22469
+ sequence INTEGER NOT NULL,
22470
+ event_id TEXT NOT NULL UNIQUE,
22471
+ event_type TEXT NOT NULL,
22472
+ command_id TEXT,
22473
+ occurred_at TEXT NOT NULL,
22474
+ recorded_at TEXT NOT NULL,
22475
+ monotonic_ns TEXT NOT NULL,
22476
+ config_revision TEXT NOT NULL,
22477
+ memory_revision TEXT NOT NULL,
22478
+ executor_policy_revision TEXT NOT NULL,
22479
+ tool_policy_revision TEXT NOT NULL,
22480
+ payload TEXT NOT NULL,
22481
+ schema TEXT NOT NULL,
22482
+ home_runtime_id TEXT NOT NULL,
22483
+ PRIMARY KEY (project_id, run_id, sequence)
22484
+ ) STRICT;
22485
+ CREATE INDEX events_run ON events(run_id, sequence);
22486
+ CREATE TABLE commands (
22487
+ command_id TEXT PRIMARY KEY,
22488
+ run_id TEXT NOT NULL,
22489
+ kind TEXT NOT NULL,
22490
+ result TEXT NOT NULL,
22491
+ recorded_at TEXT NOT NULL
22492
+ ) STRICT;
22493
+ CREATE TABLE run_plans (
22494
+ run_id TEXT PRIMARY KEY REFERENCES runs(run_id),
22495
+ run_plan_hash TEXT NOT NULL,
22496
+ envelope TEXT NOT NULL,
22497
+ pinned_sequence INTEGER NOT NULL
22498
+ ) STRICT;
22499
+ CREATE TABLE run_state (
22500
+ run_id TEXT PRIMARY KEY REFERENCES runs(run_id),
22501
+ last_sequence INTEGER NOT NULL,
22502
+ state TEXT NOT NULL
22503
+ ) STRICT;
22504
+ CREATE TABLE attempt_capabilities (
22505
+ attempt_id TEXT PRIMARY KEY,
22506
+ run_id TEXT NOT NULL,
22507
+ assignment_id TEXT NOT NULL,
22508
+ step_id TEXT NOT NULL,
22509
+ step_attempt INTEGER NOT NULL,
22510
+ producer_id TEXT NOT NULL,
22511
+ capability_hash TEXT NOT NULL UNIQUE,
22512
+ state TEXT NOT NULL CHECK (state IN ('issued','settled','revoked'))
22513
+ ) STRICT;
22514
+ CREATE TABLE gate_attempts (
22515
+ attempt_id TEXT PRIMARY KEY REFERENCES attempt_capabilities(attempt_id),
22516
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
22517
+ project_id TEXT NOT NULL,
22518
+ home_runtime_id TEXT NOT NULL,
22519
+ step_id TEXT NOT NULL,
22520
+ step_attempt INTEGER NOT NULL,
22521
+ assignment_id TEXT NOT NULL,
22522
+ effect_id TEXT NOT NULL UNIQUE,
22523
+ gate_id TEXT NOT NULL,
22524
+ gate_kind TEXT NOT NULL CHECK (gate_kind IN ('command','artifacts-exist')),
22525
+ expect TEXT NOT NULL CHECK (expect IN ('pass','fail')),
22526
+ gate_definition_hash TEXT NOT NULL,
22527
+ registry_hash TEXT NOT NULL,
22528
+ run_plan_hash TEXT NOT NULL,
22529
+ control_project_key TEXT NOT NULL,
22530
+ producer_id TEXT NOT NULL CHECK (producer_id = 'kxm-gate'),
22531
+ intent_event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id),
22532
+ content_hash TEXT NOT NULL
22533
+ ) STRICT;
22534
+ CREATE INDEX gate_attempts_run ON gate_attempts(run_id);
22535
+ CREATE INDEX gate_attempts_project ON gate_attempts(project_id);
22536
+ CREATE TABLE gate_observations (
22537
+ observation_id TEXT PRIMARY KEY,
22538
+ attempt_id TEXT NOT NULL UNIQUE REFERENCES gate_attempts(attempt_id),
22539
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
22540
+ project_id TEXT NOT NULL,
22541
+ home_runtime_id TEXT NOT NULL,
22542
+ step_id TEXT NOT NULL,
22543
+ step_attempt INTEGER NOT NULL,
22544
+ assignment_id TEXT NOT NULL,
22545
+ effect_id TEXT NOT NULL,
22546
+ completeness TEXT NOT NULL CHECK (completeness IN ('complete','incomplete','no-start')),
22547
+ spawned INTEGER NOT NULL CHECK (spawned IN (0, 1)),
22548
+ pid INTEGER,
22549
+ exit_code INTEGER,
22550
+ signal TEXT,
22551
+ exit_observed INTEGER NOT NULL CHECK (exit_observed IN (0, 1)),
22552
+ close_observed INTEGER NOT NULL CHECK (close_observed IN (0, 1)),
22553
+ stop_cause TEXT NOT NULL CHECK (stop_cause IN ('none','timeout','cancel','error')),
22554
+ signals_attempted TEXT NOT NULL CHECK (signals_attempted IN ('none','term','term-kill')),
22555
+ error_class TEXT CHECK (error_class IN ('validation','spawn-error','stream-error','stop-error','recording-error','lost-close')),
22556
+ stdout_sha256 TEXT,
22557
+ stdout_bytes INTEGER,
22558
+ stdout_complete INTEGER CHECK (stdout_complete IN (0, 1)),
22559
+ stderr_sha256 TEXT,
22560
+ stderr_bytes INTEGER,
22561
+ stderr_complete INTEGER CHECK (stderr_complete IN (0, 1)),
22562
+ checked_count INTEGER,
22563
+ failed_count INTEGER,
22564
+ elapsed_ms INTEGER NOT NULL,
22565
+ started_at TEXT NOT NULL,
22566
+ finished_at TEXT,
22567
+ recorded_event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id),
22568
+ content_hash TEXT NOT NULL
22569
+ ) STRICT;
22570
+ CREATE INDEX gate_observations_run ON gate_observations(run_id);
22571
+ CREATE TABLE gate_evidence (
22572
+ evidence_id TEXT PRIMARY KEY,
22573
+ attempt_id TEXT NOT NULL UNIQUE REFERENCES gate_attempts(attempt_id),
22574
+ observation_id TEXT NOT NULL UNIQUE REFERENCES gate_observations(observation_id),
22575
+ run_id TEXT NOT NULL REFERENCES runs(run_id),
22576
+ project_id TEXT NOT NULL,
22577
+ home_runtime_id TEXT NOT NULL,
22578
+ step_id TEXT NOT NULL,
22579
+ step_attempt INTEGER NOT NULL,
22580
+ assignment_id TEXT NOT NULL,
22581
+ effect_id TEXT NOT NULL,
22582
+ evidence_key TEXT,
22583
+ kind TEXT NOT NULL CHECK (kind = 'gate'),
22584
+ expect TEXT NOT NULL CHECK (expect IN ('pass','fail')),
22585
+ outcome TEXT NOT NULL CHECK (outcome IN ('passed','implementation-failure','repro-missing')),
22586
+ settled_event_id TEXT NOT NULL UNIQUE REFERENCES events(event_id),
22587
+ content_hash TEXT NOT NULL
22588
+ ) STRICT;
22589
+ CREATE UNIQUE INDEX gate_evidence_key ON gate_evidence(run_id, step_id, step_attempt, evidence_key) WHERE evidence_key IS NOT NULL;
22590
+ CREATE INDEX gate_evidence_run ON gate_evidence(run_id);
22591
+ CREATE TABLE drive_receipts (
22592
+ drive_id TEXT PRIMARY KEY,
22593
+ run_id TEXT NOT NULL,
22594
+ project_id TEXT NOT NULL,
22595
+ opened_sequence INTEGER NOT NULL,
22596
+ last_sequence INTEGER NOT NULL,
22597
+ closed_at TEXT NOT NULL,
22598
+ schema TEXT NOT NULL,
22599
+ receipt TEXT NOT NULL
22600
+ ) STRICT;
22601
+ CREATE INDEX drive_receipts_run ON drive_receipts(run_id);
22602
+ CREATE TABLE coordinators (
22603
+ coordinator_id TEXT PRIMARY KEY,
22604
+ project_id TEXT NOT NULL,
22605
+ role TEXT NOT NULL,
22606
+ channel TEXT NOT NULL,
22607
+ ceiling_hash TEXT NOT NULL,
22608
+ config_revision TEXT NOT NULL,
22609
+ bound_at TEXT NOT NULL,
22610
+ schema TEXT NOT NULL,
22611
+ record TEXT NOT NULL
22612
+ ) STRICT;
22613
+ CREATE UNIQUE INDEX coordinators_slot ON coordinators(project_id, role, channel);
22614
+ CREATE TABLE intake_messages (
22615
+ message_id TEXT PRIMARY KEY,
22616
+ project_id TEXT NOT NULL,
22617
+ coordinator_id TEXT NOT NULL,
22618
+ idempotency_key TEXT NOT NULL,
22619
+ content_hash TEXT NOT NULL,
22620
+ received_at TEXT NOT NULL,
22621
+ dispatch_state TEXT NOT NULL CHECK (dispatch_state IN ('ready','held_paused','admitted','refused')),
22622
+ schema TEXT NOT NULL,
22623
+ record TEXT NOT NULL
22624
+ ) STRICT;
22625
+ CREATE UNIQUE INDEX intake_idempotency ON intake_messages(project_id, coordinator_id, idempotency_key);
22626
+ CREATE INDEX intake_dispatch ON intake_messages(project_id, dispatch_state, received_at, message_id);
22627
+ CREATE TABLE project_controls (
22628
+ project_id TEXT PRIMARY KEY,
22629
+ paused INTEGER NOT NULL CHECK (paused IN (0, 1)),
22630
+ reason TEXT,
22631
+ updated_at TEXT NOT NULL,
22632
+ actor TEXT NOT NULL,
22633
+ schema TEXT NOT NULL,
22634
+ record TEXT NOT NULL
22635
+ ) STRICT;
22636
+ CREATE TABLE IF NOT EXISTS outbox (
22637
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
22638
+ run_id TEXT NOT NULL,
22639
+ sequence INTEGER NOT NULL,
22640
+ sync_event TEXT NOT NULL,
22641
+ attempted_at TEXT,
22642
+ attempt_count INTEGER NOT NULL DEFAULT 0,
22643
+ acked_at TEXT,
22644
+ refused_code TEXT,
22645
+ refused_at TEXT,
22646
+ UNIQUE (run_id, sequence)
22647
+ ) STRICT;
22648
+ CREATE INDEX outbox_pending ON outbox(seq) WHERE acked_at IS NULL AND refused_code IS NULL;
22649
+ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
22650
+ `;
22651
+ KxmRunEventStore = class {
22652
+ path;
22653
+ /** Secret values registered here are replaced in every sync object this
22654
+ * store derives. In memory only: they are never written anywhere. */
22655
+ syncRedactor = new KxmSyncRedactor();
22656
+ database;
22657
+ constructor(path4) {
22658
+ this.path = resolve8(path4);
22659
+ this.database = openDatabase(this.path, "run event store", {
22660
+ schema: EVENT_STORE_SCHEMA,
22661
+ version: KXM_EVENT_STORE_SCHEMA_VERSION,
22662
+ tables: EVENT_STORE_TABLES
22663
+ });
22664
+ }
22665
+ close() {
22666
+ this.database.close();
22667
+ }
22668
+ /** Persist the accepted prompt outside the event log (hashed in events only). */
22669
+ putRunPrompt(runId, prompt) {
22670
+ const all = this.readRunPrompts();
22671
+ all[runId] = prompt;
22672
+ this.writeRunPrompts(all);
22673
+ }
22674
+ getRunPrompt(runId) {
22675
+ const value = this.readRunPrompts()[runId];
22676
+ return typeof value === "string" ? value : void 0;
22677
+ }
22678
+ runPromptSidecarPath() {
22679
+ return `${this.path}.run-prompts.json`;
22680
+ }
22681
+ readRunPrompts() {
22682
+ const sidecar = this.runPromptSidecarPath();
22683
+ if (!existsSync10(sidecar)) return {};
22684
+ try {
22685
+ const parsed = JSON.parse(readFileSync8(sidecar, "utf8"));
22686
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
22687
+ return Object.fromEntries(
22688
+ Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
22689
+ );
22690
+ } catch {
22691
+ return {};
22692
+ }
22693
+ }
22694
+ writeRunPrompts(all) {
22695
+ writeFileSync6(this.runPromptSidecarPath(), `${JSON.stringify(all)}
22696
+ `, { encoding: "utf8", mode: 384 });
22697
+ }
22698
+ /** Run one immutable transaction, rolling back on any failure. */
22699
+ transaction(work) {
22700
+ return withDatabaseTransaction(this.database, work);
22701
+ }
22702
+ command(commandId) {
22703
+ const row = this.database.prepare("SELECT command_id, run_id, kind, result, recorded_at FROM commands WHERE command_id = ?").get(commandId);
22704
+ return row ? { commandId: row.command_id, runId: row.run_id, kind: row.kind, result: JSON.parse(row.result), recordedAt: row.recorded_at } : void 0;
22705
+ }
22706
+ insertCommand(record) {
22707
+ 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);
22708
+ }
22709
+ insertRun(record) {
22710
+ this.database.prepare(`
22711
+ 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)
22712
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22713
+ `).run(
22714
+ record.runId,
22715
+ record.projectId,
22716
+ record.homeRuntimeId,
22717
+ record.workflowId,
22718
+ record.promptSha256,
22719
+ record.status,
22720
+ record.configRevision,
22721
+ record.memoryRevision,
22722
+ record.executorPolicyRevision,
22723
+ record.toolPolicyRevision,
22724
+ record.createdAt,
22725
+ record.updatedAt
22726
+ );
22727
+ }
22728
+ updateRunStatus(runId, status, updatedAt) {
22729
+ this.database.prepare("UPDATE runs SET status = ?, updated_at = ? WHERE run_id = ?").run(status, updatedAt, runId);
22730
+ }
22731
+ run(runId) {
22732
+ const row = this.database.prepare(`
22733
+ 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
22734
+ FROM runs WHERE run_id = ?
22735
+ `).get(runId);
22736
+ return row ? runFromRow(row) : void 0;
22737
+ }
22738
+ /** All projects registered to this Runtime, for restart recovery: the
22739
+ * supervisor needs to reopen their contexts so pending outbox rows resume
22740
+ * syncing and presence keeps beating. */
22741
+ projectsForRuntime(homeRuntimeId) {
22742
+ const rows = this.database.prepare(
22743
+ "SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
22744
+ ).all(homeRuntimeId);
22745
+ return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
22746
+ }
22747
+ runsForProject(projectId, limit = 50) {
22748
+ const rows = this.database.prepare(`
22749
+ 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
22750
+ FROM runs WHERE project_id = ? ORDER BY created_at DESC, run_id DESC LIMIT ?
22751
+ `).all(projectId, limit);
22752
+ return rows.map(runFromRow);
22753
+ }
22754
+ nextSequence(runId) {
22755
+ const row = this.database.prepare("SELECT COALESCE(MAX(sequence), 0) AS max_sequence FROM events WHERE run_id = ?").get(runId);
22756
+ return row.max_sequence + 1;
22757
+ }
22758
+ appendEvent(event) {
22759
+ validateRunEvent(event, "run-event");
22760
+ const run = this.run(event.runId);
22761
+ if (!run) throw runtimeError("run_unknown", event.runId, "run does not exist in this event store");
22762
+ if (event.homeRuntimeId !== run.homeRuntimeId || event.projectId !== run.projectId) {
22763
+ throw runtimeError("run_event_owner_mismatch", event.runId, "event homeRuntimeId or projectId does not match the run");
22764
+ }
22765
+ this.database.prepare(`
22766
+ 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)
22767
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22768
+ `).run(
22769
+ event.projectId,
22770
+ event.runId,
22771
+ event.sequence,
22772
+ event.eventId,
22773
+ event.eventType,
22774
+ event.commandId ?? null,
22775
+ event.occurredAt,
22776
+ event.recordedAt,
22777
+ event.monotonicNs,
22778
+ event.configRevision,
22779
+ event.memoryRevision,
22780
+ event.executorPolicyRevision,
22781
+ event.toolPolicyRevision,
22782
+ JSON.stringify(event.payload),
22783
+ event.schema,
22784
+ event.homeRuntimeId
22785
+ );
22786
+ const syncEvent = deriveKxmSyncEvent(event, { redactor: this.syncRedactor });
22787
+ this.database.prepare("INSERT INTO outbox (run_id, sequence, sync_event) VALUES (?, ?, ?)").run(event.runId, event.sequence, kxmSyncEventBytes(syncEvent));
22788
+ }
22789
+ /**
22790
+ * Retryable outbox rows after `afterSeq`, in outbox order, oldest first.
22791
+ * Acked and durably refused rows are never returned: a refused row is not
22792
+ * waiting for a retry, it is waiting for an operator.
22793
+ */
22794
+ pendingOutbox(limit = 100, afterSeq = 0) {
22795
+ const rows = this.database.prepare(`
22796
+ SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
22797
+ FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL AND seq > ? ORDER BY seq ASC LIMIT ?
22798
+ `).all(afterSeq, limit);
22799
+ return rows.map(outboxFromRow);
22800
+ }
22801
+ /** Every outbox row of one run, in any state, in sequence order. */
22802
+ outboxForRun(runId) {
22803
+ const rows = this.database.prepare(`
22804
+ SELECT seq, run_id, sequence, sync_event, attempt_count, attempted_at, acked_at, refused_code, refused_at
22805
+ FROM outbox WHERE run_id = ? ORDER BY sequence ASC
22806
+ `).all(runId);
22807
+ return rows.map(outboxFromRow);
22808
+ }
22809
+ /**
22810
+ * Count one attempt against each row. Refused rows are never re-counted: they
22811
+ * are out of the queue, and a tick that reaches them again would mean the
22812
+ * refusal was cleared (which resets the count).
22813
+ */
22814
+ markOutboxAttempted(seqs, at2) {
22815
+ const statement = this.database.prepare(`
22816
+ UPDATE outbox SET attempted_at = ?, attempt_count = attempt_count + 1
22817
+ WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
22818
+ `);
22819
+ this.transaction(() => {
22820
+ for (const seq of seqs) statement.run(at2, seq);
22821
+ });
22822
+ }
22823
+ /** Advance the cursor: the hub holds these rows now. Returns rows newly acked. */
22824
+ ackOutbox(seqs, at2) {
22825
+ const statement = this.database.prepare("UPDATE outbox SET acked_at = ?, refused_code = NULL, refused_at = NULL WHERE seq = ? AND acked_at IS NULL");
22826
+ return this.transaction(() => {
22827
+ let changed = 0;
22828
+ for (const seq of seqs) changed += Number(statement.run(at2, seq).changes);
22829
+ return changed;
22830
+ });
22831
+ }
22832
+ /**
22833
+ * Record a durable hub refusal: the answer will not change if the same bytes
22834
+ * are sent again. Returns rows newly moved out of the pending queue.
22835
+ */
22836
+ refuseOutbox(refusals, at2) {
22837
+ const statement = this.database.prepare(`
22838
+ UPDATE outbox SET refused_code = ?, refused_at = ?
22839
+ WHERE seq = ? AND acked_at IS NULL AND refused_code IS NULL
22840
+ `);
22841
+ return this.transaction(() => {
22842
+ let changed = 0;
22843
+ for (const refusal of refusals) changed += Number(statement.run(refusal.code, at2, refusal.seq).changes);
22844
+ return changed;
22845
+ });
22846
+ }
22847
+ /**
22848
+ * Operator revive: put refused rows back in the pending queue with a fresh
22849
+ * attempt budget. Used after the hub-side state is corrected — the Runtime
22850
+ * must not decide on its own that a refusal has become retryable.
22851
+ */
22852
+ retryRefusedOutbox() {
22853
+ return Number(this.database.prepare(`
22854
+ UPDATE outbox SET refused_code = NULL, refused_at = NULL, attempted_at = NULL, attempt_count = 0
22855
+ WHERE acked_at IS NULL AND refused_code IS NOT NULL
22856
+ `).run().changes);
22857
+ }
22858
+ /**
22859
+ * The outbox as one read model: what is still retryable, what the hub holds,
22860
+ * and what the hub refused with which codes. This is what makes a stalled
22861
+ * sync answerable without opening SQLite.
22862
+ */
22863
+ outboxStatus() {
22864
+ const counts = this.database.prepare(`
22865
+ SELECT
22866
+ COALESCE(SUM(acked_at IS NULL AND refused_code IS NULL), 0) AS pending,
22867
+ COALESCE(SUM(acked_at IS NOT NULL), 0) AS acked,
22868
+ COALESCE(SUM(acked_at IS NULL AND refused_code IS NOT NULL), 0) AS refused,
22869
+ MAX(attempted_at) AS last_attempt_at
22870
+ FROM outbox
22871
+ `).get();
22872
+ const refusals = this.database.prepare(`
22873
+ SELECT refused_code AS code, COUNT(*) AS count
22874
+ FROM outbox WHERE acked_at IS NULL AND refused_code IS NOT NULL
22875
+ GROUP BY refused_code ORDER BY count DESC, code ASC
22876
+ `).all();
22877
+ const oldest = this.database.prepare(`
22878
+ SELECT seq FROM outbox WHERE acked_at IS NULL AND refused_code IS NULL ORDER BY seq ASC LIMIT 1
22879
+ `).get();
22880
+ return {
22881
+ pending: counts.pending,
22882
+ acked: counts.acked,
22883
+ refused: counts.refused,
22884
+ ...counts.last_attempt_at !== null ? { lastAttemptAt: counts.last_attempt_at } : {},
22885
+ ...oldest ? { oldestPendingSeq: oldest.seq } : {},
22886
+ // Mapped, not spread: node:sqlite rows are null-prototype objects, and this
22887
+ // read model is handed straight to the CLI and the supervisor's JSON answer.
22888
+ refusals: refusals.map((refusal) => ({ code: refusal.code, count: refusal.count }))
22889
+ };
22890
+ }
22891
+ events(runId, afterSequence = 0, limit = 200) {
22892
+ const rows = this.database.prepare(`
22893
+ 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
22894
+ FROM events WHERE run_id = ? AND sequence > ? ORDER BY sequence ASC LIMIT ?
22895
+ `).all(runId, afterSequence, limit);
22896
+ return rows.map((row) => ({
22897
+ schema: row.schema,
22898
+ eventId: row.event_id,
22899
+ eventType: row.event_type,
22900
+ projectId: row.project_id,
22901
+ runId: row.run_id,
22902
+ homeRuntimeId: row.home_runtime_id,
22903
+ sequence: row.sequence,
22904
+ ...row.command_id !== null ? { commandId: row.command_id } : {},
22905
+ occurredAt: row.occurred_at,
22906
+ recordedAt: row.recorded_at,
22907
+ monotonicNs: row.monotonic_ns,
22908
+ configRevision: row.config_revision,
22909
+ memoryRevision: row.memory_revision,
22910
+ executorPolicyRevision: row.executor_policy_revision,
22911
+ toolPolicyRevision: row.tool_policy_revision,
22912
+ payload: JSON.parse(row.payload)
22913
+ }));
22914
+ }
22915
+ insertRunPlan(row) {
22916
+ 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);
22917
+ }
22918
+ runPlan(runId) {
22919
+ const row = this.database.prepare("SELECT run_id, run_plan_hash, envelope, pinned_sequence FROM run_plans WHERE run_id = ?").get(runId);
22920
+ return row ? { runId: row.run_id, runPlanHash: row.run_plan_hash, envelope: row.envelope, pinnedSequence: row.pinned_sequence } : void 0;
22921
+ }
22922
+ runState(runId) {
22923
+ const row = this.database.prepare("SELECT run_id, last_sequence, state FROM run_state WHERE run_id = ?").get(runId);
22924
+ return row ? { runId: row.run_id, lastSequence: row.last_sequence, state: row.state } : void 0;
22925
+ }
22926
+ upsertRunState(row) {
22927
+ this.database.prepare(`
22928
+ INSERT INTO run_state (run_id, last_sequence, state) VALUES (?, ?, ?)
22929
+ ON CONFLICT(run_id) DO UPDATE SET last_sequence = excluded.last_sequence, state = excluded.state
22930
+ `).run(row.runId, row.lastSequence, row.state);
22931
+ }
22932
+ insertDriveReceipt(receipt) {
22933
+ validateDriveReceipt(receipt, "drive-receipt");
22934
+ const canonical2 = kxmCanonicalJson(receipt);
22935
+ if (Buffer.byteLength(canonical2, "utf8") > DRIVE_RECEIPT_MAX_BYTES) {
22936
+ throw runtimeError("drive_receipt_invalid", receipt.driveId, `drive receipt exceeds ${DRIVE_RECEIPT_MAX_BYTES} bytes`);
22937
+ }
22938
+ const result = this.database.prepare(`
22939
+ INSERT INTO drive_receipts (drive_id, run_id, project_id, opened_sequence, last_sequence, closed_at, schema, receipt)
22940
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
22941
+ ON CONFLICT(drive_id) DO NOTHING
22942
+ `).run(
22943
+ receipt.driveId,
22944
+ receipt.runId,
22945
+ receipt.projectId,
22946
+ receipt.openedSequence,
22947
+ receipt.lastSequence,
22948
+ receipt.closedAt,
22949
+ receipt.schema,
22950
+ canonical2
22951
+ );
22952
+ return { inserted: Number(result.changes) === 1 };
22953
+ }
22954
+ driveReceipt(driveId) {
22955
+ const row = this.database.prepare("SELECT receipt FROM drive_receipts WHERE drive_id = ?").get(driveId);
22956
+ return row ? parseDriveReceipt(row.receipt) : void 0;
22957
+ }
22958
+ driveReceiptsForRun(runId, limit = 20) {
22959
+ const rows = this.database.prepare(`
22960
+ SELECT drive_id, receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
22961
+ `).all(runId, limit);
22962
+ return rows.map((row) => {
22963
+ try {
22964
+ return parseDriveReceipt(row.receipt);
22965
+ } catch {
22966
+ return { driveId: row.drive_id, divergence: "receipt unreadable" };
22967
+ }
22968
+ });
22969
+ }
22970
+ /** Insert a coordinator unless the (project, role, channel) slot is taken. */
22971
+ insertCoordinatorIfAbsent(row) {
22972
+ const result = this.database.prepare(`
22973
+ INSERT OR IGNORE INTO coordinators
22974
+ (coordinator_id, project_id, role, channel, ceiling_hash, config_revision, bound_at, schema, record)
22975
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
22976
+ `).run(
22977
+ row.coordinatorId,
22978
+ row.projectId,
22979
+ row.role,
22980
+ row.channel,
22981
+ row.ceilingHash,
22982
+ row.configRevision,
22983
+ row.boundAt,
22984
+ "kxm.coordinator.v1",
22985
+ row.record
22986
+ );
22987
+ return Number(result.changes) === 1;
22988
+ }
22989
+ coordinatorById(coordinatorId) {
22990
+ const row = this.database.prepare("SELECT * FROM coordinators WHERE coordinator_id = ?").get(coordinatorId);
22991
+ return row ? coordinatorFromRow(row) : void 0;
22992
+ }
22993
+ coordinatorInSlot(projectId, role, channel) {
22994
+ const row = this.database.prepare(`
22995
+ SELECT * FROM coordinators WHERE project_id = ? AND role = ? AND channel = ?
22996
+ `).get(projectId, role, channel);
22997
+ return row ? coordinatorFromRow(row) : void 0;
22998
+ }
22999
+ /**
23000
+ * Replace the coordinator holding a (project, role, channel) slot, guarded by
23001
+ * the identity that is currently there. A rebind therefore cannot clobber a
23002
+ * record that changed underneath it, and cannot leave two live identities for
23003
+ * one slot.
23004
+ */
23005
+ replaceCoordinatorInSlot(expectedCoordinatorId, row) {
23006
+ const result = this.database.prepare(`
23007
+ UPDATE coordinators
23008
+ SET coordinator_id = ?, ceiling_hash = ?, config_revision = ?, bound_at = ?, schema = ?, record = ?
23009
+ WHERE project_id = ? AND role = ? AND channel = ? AND coordinator_id = ?
23010
+ `).run(
23011
+ row.coordinatorId,
23012
+ row.ceilingHash,
23013
+ row.configRevision,
23014
+ row.boundAt,
23015
+ "kxm.coordinator.v1",
23016
+ row.record,
23017
+ row.projectId,
23018
+ row.role,
23019
+ row.channel,
23020
+ expectedCoordinatorId
23021
+ );
23022
+ return Number(result.changes) === 1;
23023
+ }
23024
+ /** Insert an intake message unless its idempotency slot is taken. */
23025
+ insertIntakeMessageIfAbsent(row) {
23026
+ const result = this.database.prepare(`
23027
+ INSERT OR IGNORE INTO intake_messages
23028
+ (message_id, project_id, coordinator_id, idempotency_key, content_hash, received_at, dispatch_state, schema, record)
23029
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
23030
+ `).run(
23031
+ row.messageId,
23032
+ row.projectId,
23033
+ row.coordinatorId,
23034
+ row.idempotencyKey,
23035
+ row.contentHash,
23036
+ row.receivedAt,
23037
+ row.dispatchState,
23038
+ "kxm.intake-message.v1",
23039
+ row.record
23040
+ );
23041
+ return Number(result.changes) === 1;
23042
+ }
23043
+ intakeMessage(messageId) {
23044
+ const row = this.database.prepare("SELECT * FROM intake_messages WHERE message_id = ?").get(messageId);
23045
+ return row ? intakeFromRow(row) : void 0;
23046
+ }
23047
+ intakeBySlot(projectId, coordinatorId, idempotencyKey) {
23048
+ const row = this.database.prepare(`
23049
+ SELECT * FROM intake_messages WHERE project_id = ? AND coordinator_id = ? AND idempotency_key = ?
23050
+ `).get(projectId, coordinatorId, idempotencyKey);
23051
+ return row ? intakeFromRow(row) : void 0;
23052
+ }
23053
+ /**
23054
+ * Intake rows in the given dispatch states, in arrival order (replay-safe).
23055
+ *
23056
+ * `rowid` gives same-store arrival order, which is what queue priority needs
23057
+ * here. It is **not** a durable sequence: this repository backs stores up with
23058
+ * `VACUUM INTO`, and a vacuum may renumber implicit rowids. An explicit
23059
+ * immutable arrival sequence is tracked in the plan's schema-v6 follow-ups.
23060
+ */
23061
+ intakeInStates(projectId, states, limit = 100) {
23062
+ if (states.length === 0) return [];
23063
+ const placeholders = states.map(() => "?").join(", ");
23064
+ const rows = this.database.prepare(`
23065
+ SELECT * FROM intake_messages
23066
+ WHERE project_id = ? AND dispatch_state IN (${placeholders})
23067
+ ORDER BY rowid ASC
23068
+ LIMIT ?
23069
+ `).all(projectId, ...states, limit);
23070
+ return rows.map(intakeFromRow);
23071
+ }
23072
+ /** Rewrite one intake row's dispatch state and record. Returns false when it raced away. */
23073
+ updateIntakeDispatch(messageId, expectState, next) {
23074
+ const result = this.database.prepare(`
23075
+ UPDATE intake_messages SET dispatch_state = ?, record = ?, schema = ?
23076
+ WHERE message_id = ? AND dispatch_state = ?
23077
+ `).run(next.state, next.record, "kxm.intake-message.v1", messageId, expectState);
23078
+ return Number(result.changes) === 1;
23079
+ }
23080
+ projectControl(projectId) {
23081
+ const row = this.database.prepare("SELECT * FROM project_controls WHERE project_id = ?").get(projectId);
23082
+ return row ? controlFromRow(row) : void 0;
23083
+ }
23084
+ putProjectControl(row) {
23085
+ this.database.prepare(`
23086
+ INSERT INTO project_controls (project_id, paused, reason, updated_at, actor, schema, record)
23087
+ VALUES (?, ?, ?, ?, ?, ?, ?)
23088
+ ON CONFLICT(project_id) DO UPDATE SET
23089
+ paused = excluded.paused,
23090
+ reason = excluded.reason,
23091
+ updated_at = excluded.updated_at,
23092
+ actor = excluded.actor,
23093
+ schema = excluded.schema,
23094
+ record = excluded.record
23095
+ `).run(
23096
+ row.projectId,
23097
+ row.paused ? 1 : 0,
23098
+ row.reason ?? null,
23099
+ row.updatedAt,
23100
+ row.actor,
23101
+ "kxm.project-control.v1",
23102
+ row.record
23103
+ );
23104
+ }
23105
+ insertCapability(row) {
23106
+ this.database.prepare(`
23107
+ INSERT INTO attempt_capabilities (attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state)
23108
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
23109
+ `).run(row.attemptId, row.runId, row.assignmentId, row.stepId, row.stepAttempt, row.producerId, row.capabilityHash, row.state);
23110
+ }
23111
+ capabilityByHash(capabilityHash) {
23112
+ return capabilityFromRow(this.database.prepare(`
23113
+ SELECT attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state
23114
+ FROM attempt_capabilities WHERE capability_hash = ?
23115
+ `).get(capabilityHash));
23116
+ }
23117
+ capabilityByAttempt(attemptId) {
23118
+ return capabilityFromRow(this.database.prepare(`
23119
+ SELECT attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state
23120
+ FROM attempt_capabilities WHERE attempt_id = ?
23121
+ `).get(attemptId));
23122
+ }
23123
+ settleCapability(attemptId, state) {
23124
+ const updated = this.database.prepare("UPDATE attempt_capabilities SET state = ? WHERE attempt_id = ?").run(state, attemptId);
23125
+ if (Number(updated.changes) !== 1) {
23126
+ throw runtimeError("capability_unknown", attemptId, "attempt capability does not exist");
23127
+ }
23128
+ }
23129
+ insertGateAttempt(row) {
23130
+ assertGateAttemptRow(row);
23131
+ this.assertEventType(row.runId, row.intentEventId, ["effect.intent_recorded"]);
23132
+ const capability = this.capabilityByAttempt(row.attemptId);
23133
+ if (!capability || capability.runId !== row.runId || capability.assignmentId !== row.assignmentId || capability.stepId !== row.stepId || capability.stepAttempt !== row.stepAttempt) {
23134
+ throw gateRowInvalid(row.attemptId, "gate attempt identity does not match attempt_capabilities");
23135
+ }
23136
+ this.database.prepare(`
23137
+ INSERT INTO gate_attempts (
23138
+ attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23139
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23140
+ producer_id, intent_event_id, content_hash
23141
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
23142
+ `).run(
23143
+ row.attemptId,
23144
+ row.runId,
23145
+ row.projectId,
23146
+ row.homeRuntimeId,
23147
+ row.stepId,
23148
+ row.stepAttempt,
23149
+ row.assignmentId,
23150
+ row.effectId,
23151
+ row.gateId,
23152
+ row.gateKind,
23153
+ row.expect,
23154
+ row.gateDefinitionHash,
23155
+ row.registryHash,
23156
+ row.runPlanHash,
23157
+ row.controlProjectKey,
23158
+ row.producerId,
23159
+ row.intentEventId,
23160
+ row.contentHash
23161
+ );
23162
+ }
23163
+ insertGateObservation(row) {
23164
+ assertGateObservationRow(row);
23165
+ this.assertEventType(row.runId, row.recordedEventId, ["effect.observed", "effect.blocked_uncertain"]);
23166
+ const attempt = this.gateAttempt(row.attemptId);
23167
+ if (!attempt) throw gateRowInvalid(row.attemptId, "gate observation has no matching gate attempt");
23168
+ assertSharedIdentity(row, attempt, row.observationId);
23169
+ assertClosedGateObservation(attempt.gateKind, row, row.observationId);
23170
+ this.database.prepare(`
23171
+ INSERT INTO gate_observations (
23172
+ observation_id, attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23173
+ completeness, spawned, pid, exit_code, signal, exit_observed, close_observed, stop_cause, signals_attempted,
23174
+ error_class, stdout_sha256, stdout_bytes, stdout_complete, stderr_sha256, stderr_bytes, stderr_complete,
23175
+ checked_count, failed_count, elapsed_ms, started_at, finished_at, recorded_event_id, content_hash
23176
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
23177
+ `).run(
23178
+ row.observationId,
23179
+ row.attemptId,
23180
+ row.runId,
23181
+ row.projectId,
23182
+ row.homeRuntimeId,
23183
+ row.stepId,
23184
+ row.stepAttempt,
23185
+ row.assignmentId,
23186
+ row.effectId,
23187
+ row.completeness,
23188
+ row.spawned,
23189
+ row.pid,
23190
+ row.exitCode,
23191
+ row.signal,
23192
+ row.exitObserved,
23193
+ row.closeObserved,
23194
+ row.stopCause,
23195
+ row.signalsAttempted,
23196
+ row.errorClass,
23197
+ row.stdoutSha256,
23198
+ row.stdoutBytes,
23199
+ row.stdoutComplete,
23200
+ row.stderrSha256,
23201
+ row.stderrBytes,
23202
+ row.stderrComplete,
23203
+ row.checkedCount,
23204
+ row.failedCount,
23205
+ row.elapsedMs,
23206
+ row.startedAt,
23207
+ row.finishedAt,
23208
+ row.recordedEventId,
23209
+ row.contentHash
23210
+ );
23211
+ }
23212
+ insertGateEvidence(row) {
23213
+ assertGateEvidenceRow(row);
23214
+ this.assertEventType(row.runId, row.settledEventId, ["effect.settled"]);
23215
+ const attempt = this.gateAttempt(row.attemptId);
23216
+ if (!attempt) throw gateRowInvalid(row.attemptId, "gate evidence has no matching gate attempt");
23217
+ assertSharedIdentity(row, attempt, row.evidenceId);
23218
+ const observation = this.gateObservationForAttempt(row.attemptId);
23219
+ if (!observation || observation.observationId !== row.observationId) {
23220
+ throw gateRowInvalid(row.evidenceId, "gate evidence observation_id does not match the attempt observation");
23221
+ }
23222
+ this.database.prepare(`
23223
+ INSERT INTO gate_evidence (
23224
+ evidence_id, attempt_id, observation_id, run_id, project_id, home_runtime_id, step_id, step_attempt,
23225
+ assignment_id, effect_id, evidence_key, kind, expect, outcome, settled_event_id, content_hash
23226
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
23227
+ `).run(
23228
+ row.evidenceId,
23229
+ row.attemptId,
23230
+ row.observationId,
23231
+ row.runId,
23232
+ row.projectId,
23233
+ row.homeRuntimeId,
23234
+ row.stepId,
23235
+ row.stepAttempt,
23236
+ row.assignmentId,
23237
+ row.effectId,
23238
+ row.evidenceKey,
23239
+ row.kind,
23240
+ row.expect,
23241
+ row.outcome,
23242
+ row.settledEventId,
23243
+ row.contentHash
23244
+ );
23245
+ }
23246
+ gateAttempt(attemptId) {
23247
+ const row = this.database.prepare(`
23248
+ SELECT attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23249
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23250
+ producer_id, intent_event_id, content_hash
23251
+ FROM gate_attempts WHERE attempt_id = ?
23252
+ `).get(attemptId);
23253
+ return row ? gateAttemptFromSql(row) : void 0;
23254
+ }
23255
+ gateAttemptsForRun(runId) {
23256
+ const rows = this.database.prepare(`
23257
+ SELECT attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23258
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23259
+ producer_id, intent_event_id, content_hash
23260
+ FROM gate_attempts WHERE run_id = ? ORDER BY step_attempt ASC, attempt_id ASC
23261
+ `).all(runId);
23262
+ return rows.map(gateAttemptFromSql);
23263
+ }
23264
+ gateAttemptsForProject(projectId) {
23265
+ const rows = this.database.prepare(`
23266
+ SELECT attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id, effect_id,
23267
+ gate_id, gate_kind, expect, gate_definition_hash, registry_hash, run_plan_hash, control_project_key,
23268
+ producer_id, intent_event_id, content_hash
23269
+ FROM gate_attempts WHERE project_id = ? ORDER BY run_id ASC, step_attempt ASC, attempt_id ASC
23270
+ `).all(projectId);
23271
+ return rows.map(gateAttemptFromSql);
23272
+ }
23273
+ issuedOrRevokedCapabilities() {
23274
+ const rows = this.database.prepare(`
23275
+ SELECT attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state
23276
+ FROM attempt_capabilities
23277
+ WHERE state IN ('issued','revoked')
23278
+ ORDER BY run_id ASC, step_attempt ASC, attempt_id ASC
23279
+ `).all();
23280
+ const mapped = [];
23281
+ for (const row of rows) {
23282
+ const capability = capabilityFromRow(row);
23283
+ if (!capability) {
23284
+ throw runtimeError("gate_recovery_corrupt", "attempt_capabilities", "issued or revoked capability row could not be read");
23285
+ }
23286
+ mapped.push(capability);
23287
+ }
23288
+ return mapped;
23289
+ }
23290
+ gateObservationForAttempt(attemptId) {
23291
+ const row = this.database.prepare(`
23292
+ SELECT observation_id, attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id,
23293
+ effect_id, completeness, spawned, pid, exit_code, signal, exit_observed, close_observed, stop_cause,
23294
+ signals_attempted, error_class, stdout_sha256, stdout_bytes, stdout_complete, stderr_sha256, stderr_bytes,
23295
+ stderr_complete, checked_count, failed_count, elapsed_ms, started_at, finished_at, recorded_event_id, content_hash
23296
+ FROM gate_observations WHERE attempt_id = ?
23297
+ `).get(attemptId);
23298
+ return row ? gateObservationFromSql(row) : void 0;
23299
+ }
23300
+ gateObservationsForRun(runId) {
23301
+ const rows = this.database.prepare(`
23302
+ SELECT observation_id, attempt_id, run_id, project_id, home_runtime_id, step_id, step_attempt, assignment_id,
23303
+ effect_id, completeness, spawned, pid, exit_code, signal, exit_observed, close_observed, stop_cause,
23304
+ signals_attempted, error_class, stdout_sha256, stdout_bytes, stdout_complete, stderr_sha256, stderr_bytes,
23305
+ stderr_complete, checked_count, failed_count, elapsed_ms, started_at, finished_at, recorded_event_id, content_hash
23306
+ FROM gate_observations WHERE run_id = ? ORDER BY step_attempt ASC, observation_id ASC
23307
+ `).all(runId);
23308
+ return rows.map(gateObservationFromSql);
23309
+ }
23310
+ gateEvidenceForRun(runId) {
23311
+ const rows = this.database.prepare(`
23312
+ SELECT evidence_id, attempt_id, observation_id, run_id, project_id, home_runtime_id, step_id, step_attempt,
23313
+ assignment_id, effect_id, evidence_key, kind, expect, outcome, settled_event_id, content_hash
23314
+ FROM gate_evidence WHERE run_id = ? ORDER BY step_attempt ASC, evidence_id ASC
23315
+ `).all(runId);
23316
+ return rows.map(gateEvidenceFromSql);
23317
+ }
23318
+ gateEvidenceForAttempt(attemptId) {
23319
+ const row = this.database.prepare(`
23320
+ SELECT evidence_id, attempt_id, observation_id, run_id, project_id, home_runtime_id, step_id, step_attempt,
23321
+ assignment_id, effect_id, evidence_key, kind, expect, outcome, settled_event_id, content_hash
23322
+ FROM gate_evidence WHERE attempt_id = ?
23323
+ `).get(attemptId);
23324
+ return row ? gateEvidenceFromSql(row) : void 0;
23325
+ }
23326
+ gateRowCounts(runId) {
23327
+ const attempts = this.database.prepare("SELECT COUNT(*) AS n FROM gate_attempts WHERE run_id = ?").get(runId);
23328
+ const observations = this.database.prepare("SELECT COUNT(*) AS n FROM gate_observations WHERE run_id = ?").get(runId);
23329
+ const evidence = this.database.prepare("SELECT COUNT(*) AS n FROM gate_evidence WHERE run_id = ?").get(runId);
23330
+ return { attempts: Number(attempts.n), observations: Number(observations.n), evidence: Number(evidence.n) };
23331
+ }
23332
+ assertEventType(runId, eventId, allowed) {
23333
+ const row = this.database.prepare("SELECT run_id, event_type FROM events WHERE event_id = ?").get(eventId);
23334
+ if (!row || row.run_id !== runId || !allowed.includes(row.event_type)) {
23335
+ throw gateRowInvalid(eventId, `referenced event is not ${allowed.join("|")} for this run`);
23336
+ }
23337
+ }
23338
+ };
23339
+ SHA2562 = /^sha256:[a-f0-9]{64}$/;
23340
+ OPAQUE = /^[a-z][a-z0-9]{1,15}_[A-Za-z0-9][A-Za-z0-9_-]{5,127}$/;
23341
+ FORBIDDEN_TEXT_KEYS = /* @__PURE__ */ new Set(["stdout", "stderr", "error", "errorText", "stdoutText", "stderrText", "errorMessage", "message"]);
23342
+ GATE_ATTEMPT_KEYS = [
23343
+ "attemptId",
23344
+ "runId",
23345
+ "projectId",
23346
+ "homeRuntimeId",
23347
+ "stepId",
23348
+ "stepAttempt",
23349
+ "assignmentId",
23350
+ "effectId",
23351
+ "gateId",
23352
+ "gateKind",
23353
+ "expect",
23354
+ "gateDefinitionHash",
23355
+ "registryHash",
23356
+ "runPlanHash",
23357
+ "controlProjectKey",
23358
+ "producerId",
23359
+ "intentEventId",
23360
+ "contentHash"
23361
+ ];
23362
+ GATE_OBSERVATION_KEYS = [
23363
+ "observationId",
23364
+ "attemptId",
23365
+ "runId",
23366
+ "projectId",
23367
+ "homeRuntimeId",
23368
+ "stepId",
23369
+ "stepAttempt",
23370
+ "assignmentId",
23371
+ "effectId",
23372
+ "completeness",
23373
+ "spawned",
23374
+ "pid",
23375
+ "exitCode",
23376
+ "signal",
23377
+ "exitObserved",
23378
+ "closeObserved",
23379
+ "stopCause",
23380
+ "signalsAttempted",
23381
+ "errorClass",
23382
+ "stdoutSha256",
23383
+ "stdoutBytes",
23384
+ "stdoutComplete",
23385
+ "stderrSha256",
23386
+ "stderrBytes",
23387
+ "stderrComplete",
23388
+ "checkedCount",
23389
+ "failedCount",
23390
+ "elapsedMs",
23391
+ "startedAt",
23392
+ "finishedAt",
23393
+ "recordedEventId",
23394
+ "contentHash"
23395
+ ];
23396
+ GATE_EVIDENCE_KEYS = [
23397
+ "evidenceId",
23398
+ "attemptId",
23399
+ "observationId",
23400
+ "runId",
23401
+ "projectId",
23402
+ "homeRuntimeId",
23403
+ "stepId",
23404
+ "stepAttempt",
23405
+ "assignmentId",
23406
+ "effectId",
23407
+ "evidenceKey",
23408
+ "kind",
23409
+ "expect",
23410
+ "outcome",
23411
+ "settledEventId",
23412
+ "contentHash"
23413
+ ];
21452
23414
  }
21453
23415
  });
21454
23416
 
@@ -21970,9 +23932,9 @@ var init_engine_compile = __esm({
21970
23932
  });
21971
23933
 
21972
23934
  // plugins/kxm/src/engine-plan.ts
21973
- import { createHash as createHash12 } from "node:crypto";
23935
+ import { createHash as createHash13 } from "node:crypto";
21974
23936
  function kxmSha256(input) {
21975
- return `sha256:${createHash12("sha256").update(input, "utf8").digest("hex")}`;
23937
+ return `sha256:${createHash13("sha256").update(input, "utf8").digest("hex")}`;
21976
23938
  }
21977
23939
  function parseGateDefinition(raw, gateId, runId) {
21978
23940
  const value = asObject(raw, runId, `gates.definitions.${gateId}`);
@@ -22184,8 +24146,8 @@ var init_price_calc = __esm({
22184
24146
  });
22185
24147
 
22186
24148
  // 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";
24149
+ import { createHash as createHash14 } from "node:crypto";
24150
+ import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
22189
24151
  import { join as join11 } from "node:path";
22190
24152
  function hashPriceCatalog(catalog) {
22191
24153
  const canonical2 = {
@@ -22206,7 +24168,7 @@ function hashPriceCatalog(catalog) {
22206
24168
  }))
22207
24169
  })).sort((a, b2) => a.id.localeCompare(b2.id))
22208
24170
  };
22209
- return createHash13("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
24171
+ return createHash14("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
22210
24172
  }
22211
24173
  function parsePriceCatalog(text) {
22212
24174
  const parsed = (0, import_yaml3.parse)(text);
@@ -22297,7 +24259,7 @@ function parsePriceCatalog(text) {
22297
24259
  return catalog;
22298
24260
  }
22299
24261
  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;
24262
+ 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
24263
  if (!candidatePath || !existsSync11(candidatePath)) {
22302
24264
  return void 0;
22303
24265
  }
@@ -22883,7 +24845,7 @@ var init_arbiter = __esm({
22883
24845
  });
22884
24846
 
22885
24847
  // plugins/kxm/src/memory.ts
22886
- import { randomUUID as randomUUID4 } from "node:crypto";
24848
+ import { randomUUID as randomUUID5 } from "node:crypto";
22887
24849
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
22888
24850
  import { extname as extname2, join as join15, resolve as resolve10 } from "node:path";
22889
24851
  function parseFrontmatter(content) {
@@ -23020,7 +24982,7 @@ function createMemoryNote(repoRoot, summary, options = {}) {
23020
24982
  if (!VALID_SCOPES.has(scope)) {
23021
24983
  throw new Error(`invalid memory note scope: ${scope}; must be one of agent, project, run, operator`);
23022
24984
  }
23023
- const hex = randomUUID4().replaceAll("-", "").slice(0, 12);
24985
+ const hex = randomUUID5().replaceAll("-", "").slice(0, 12);
23024
24986
  const id = `cand_${scope}_${hex}`;
23025
24987
  const record = {
23026
24988
  schema: MEMORY_SCHEMA,
@@ -23166,11 +25128,11 @@ var init_memory = __esm({
23166
25128
  });
23167
25129
 
23168
25130
  // 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";
25131
+ import { createHash as createHash15 } from "node:crypto";
25132
+ 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
25133
  import { dirname as dirname10, join as join16 } from "node:path";
23172
25134
  function skillContentSha256(content) {
23173
- return createHash14("sha256").update(content, "utf8").digest("hex");
25135
+ return createHash15("sha256").update(content, "utf8").digest("hex");
23174
25136
  }
23175
25137
  function skillIdFor(name, contentSha256) {
23176
25138
  const slug = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -23243,7 +25205,7 @@ function boundedList(value, field) {
23243
25205
  return [...new Set(refs)];
23244
25206
  }
23245
25207
  function readdirSorted(dir) {
23246
- return readdirSync6(dir).filter((entry) => statSync2(join16(dir, entry)).isDirectory()).sort();
25208
+ return readdirSync6(dir).filter((entry) => statSync3(join16(dir, entry)).isDirectory()).sort();
23247
25209
  }
23248
25210
  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
25211
  var init_skills = __esm({
@@ -23587,11 +25549,11 @@ var init_dispatch_context = __esm({
23587
25549
  });
23588
25550
 
23589
25551
  // 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";
25552
+ import { lstatSync as lstatSync5, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
25553
+ import { isAbsolute as isAbsolute6, relative as relative4, resolve as resolve11 } from "node:path";
23592
25554
  function staysUnder(root, candidate) {
23593
25555
  const child = relative4(root, candidate);
23594
- return child === "" || !isAbsolute5(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
25556
+ return child === "" || !isAbsolute6(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
23595
25557
  }
23596
25558
  function verifyArtifactExists(rootInput, pathInput) {
23597
25559
  const lexicalRoot = resolve11(rootInput);
@@ -23602,12 +25564,12 @@ function verifyArtifactExists(rootInput, pathInput) {
23602
25564
  let realRoot;
23603
25565
  let realPath;
23604
25566
  try {
23605
- realRoot = realpathSync4(lexicalRoot);
25567
+ realRoot = realpathSync5(lexicalRoot);
23606
25568
  } catch {
23607
25569
  return { ok: false, error: "artifact_unreadable", path: lexicalPath };
23608
25570
  }
23609
25571
  try {
23610
- realPath = realpathSync4(lexicalPath);
25572
+ realPath = realpathSync5(lexicalPath);
23611
25573
  } catch {
23612
25574
  return { ok: false, error: "artifact_missing", path: lexicalPath };
23613
25575
  }
@@ -23619,7 +25581,7 @@ function verifyArtifactExists(rootInput, pathInput) {
23619
25581
  if (link.isSymbolicLink()) {
23620
25582
  return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
23621
25583
  }
23622
- const artifact = statSync3(realPath);
25584
+ const artifact = statSync4(realPath);
23623
25585
  if (!artifact.isFile()) return { ok: false, error: "artifact_not_file", path: lexicalPath };
23624
25586
  if (artifact.size < 1) return { ok: false, error: "artifact_empty", path: lexicalPath };
23625
25587
  return { ok: true, path: realPath, bytes: artifact.size };
@@ -24384,9 +26346,9 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
24384
26346
  const workflow = bundle.workflows.get(workflowId);
24385
26347
  if (!workflow) throw runtimeError("run_workflow_unknown", workflowId, `workflow ${workflowId} does not exist in this project`);
24386
26348
  const plan = compileKxmWorkflow({ id: workflowId, value: workflow.value, logicalPath: workflow.logicalPath });
24387
- const envelope = { plan, projectLimits: kxmProjectAdmissionLimits(bundle), gates: pinnedGatesForCompiledPlan(plan, bundle, projectRoot) };
26349
+ const envelope2 = { plan, projectLimits: kxmProjectAdmissionLimits(bundle), gates: pinnedGatesForCompiledPlan(plan, bundle, projectRoot) };
24388
26350
  const prerequisites = [];
24389
- const limit = unsupportedLimit(envelope);
26351
+ const limit = unsupportedLimit(envelope2);
24390
26352
  if (limit) prerequisites.push(limit);
24391
26353
  const visited = /* @__PURE__ */ new Set();
24392
26354
  const pending = [plan.entryStepId];
@@ -24398,8 +26360,8 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
24398
26360
  for (const transition2 of Object.values(step.transitions)) {
24399
26361
  if (transition2.to === "step") pending.push(transition2.target);
24400
26362
  }
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");
26363
+ const limitMs = envelope2.projectLimits.agentStepTimeoutMs ?? KXM_DEFAULT_AGENT_STEP_TIMEOUT_MS;
26364
+ const unsupported = step.kind === "gate" ? unsupportedGateStep(plan, step, envelope2, { projectRoot }) ?? starterGatePrerequisite(step, envelope2.gates, projectRoot) : unsupportedAgentStepTimeout(step, limitMs) ?? unsupportedStep(plan, step, "oneshot");
24403
26365
  if (unsupported) {
24404
26366
  prerequisites.push({ ...unsupported, stepId });
24405
26367
  continue;
@@ -24419,7 +26381,7 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
24419
26381
  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
26382
  continue;
24421
26383
  }
24422
- const writeRefusal = unsupportedLiveWrite(projectRoot, step, agentId, route.selector, envelope.projectLimits.maxConcurrentRuns);
26384
+ const writeRefusal = unsupportedLiveWrite(projectRoot, step, agentId, route.selector, envelope2.projectLimits.maxConcurrentRuns);
24423
26385
  if (writeRefusal) {
24424
26386
  prerequisites.push({ ...writeRefusal, stepId });
24425
26387
  continue;
@@ -24449,11 +26411,11 @@ function starterGatePrerequisite(step, gates, projectRoot) {
24449
26411
  }
24450
26412
  return void 0;
24451
26413
  }
24452
- function unsupportedLimit(envelope) {
24453
- if (envelope.plan.limits.maxAgentTimeMs !== void 0) {
26414
+ function unsupportedLimit(envelope2) {
26415
+ if (envelope2.plan.limits.maxAgentTimeMs !== void 0) {
24454
26416
  return { reason: "limit_unsupported", field: "limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
24455
26417
  }
24456
- if (envelope.projectLimits.maxAgentTimeMs !== void 0) {
26418
+ if (envelope2.projectLimits.maxAgentTimeMs !== void 0) {
24457
26419
  return { reason: "limit_unsupported", field: "project.limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
24458
26420
  }
24459
26421
  return void 0;
@@ -24573,12 +26535,12 @@ function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurren
24573
26535
  }
24574
26536
  return void 0;
24575
26537
  }
24576
- function unsupportedGateStep(plan, step, envelope, context) {
26538
+ function unsupportedGateStep(plan, step, envelope2, context) {
24577
26539
  if (!step.gate) return { reason: "step_unsupported", field: "gate", detail: "gate step is missing gate id" };
24578
- if (envelope.gates.registry === null) {
26540
+ if (envelope2.gates.registry === null) {
24579
26541
  return { reason: "step_unsupported", field: "gate", detail: `step ${step.id} refers to a gate but registry is null` };
24580
26542
  }
24581
- const definition = envelope.gates.definitions[step.gate];
26543
+ const definition = envelope2.gates.definitions[step.gate];
24582
26544
  if (!definition) {
24583
26545
  return { reason: "step_unsupported", field: "gate", detail: `gate definition ${step.gate} not found in registry` };
24584
26546
  }
@@ -24684,7 +26646,7 @@ function unsupportedGateStep(plan, step, envelope, context) {
24684
26646
  return { reason: "gate_outcome_undeclared", field: "outcomes", detail: "step expects fail but missing required failure outcome; declare implementation-failure or failed" };
24685
26647
  }
24686
26648
  }
24687
- if (context && projectRuntimeKey(context.projectRoot) !== envelope.gates.controlRoot.projectKey) {
26649
+ if (context && projectRuntimeKey(context.projectRoot) !== envelope2.gates.controlRoot.projectKey) {
24688
26650
  throw runtimeError("run_owner_mismatch", plan.workflowId, `gate control root does not match project`);
24689
26651
  }
24690
26652
  if (definition.kind === "artifacts-exist" && step.expect === "fail") {
@@ -24754,21 +26716,21 @@ var init_oneshot_producer = __esm({
24754
26716
  // plugins/kxm/src/hub-binding.ts
24755
26717
  import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync16, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "node:fs";
24756
26718
  import { homedir as homedir5 } from "node:os";
24757
- import { dirname as dirname11, isAbsolute as isAbsolute6, join as join18, resolve as resolve12 } from "node:path";
26719
+ import { dirname as dirname11, isAbsolute as isAbsolute7, join as join18, resolve as resolve12 } from "node:path";
24758
26720
  function resolveUserStateRoot2(env) {
24759
26721
  const explicit = env.KXM_STATE_HOME?.trim();
24760
26722
  if (explicit) {
24761
- if (!isAbsolute6(explicit)) throw new HubBindingError("local_state_root_not_absolute");
26723
+ if (!isAbsolute7(explicit)) throw new HubBindingError("local_state_root_not_absolute");
24762
26724
  return resolve12(explicit);
24763
26725
  }
24764
26726
  if (process.platform === "win32") {
24765
26727
  const localAppData = env.LOCALAPPDATA?.trim();
24766
- const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join18(homedir5(), "AppData", "Local");
26728
+ const base2 = localAppData && isAbsolute7(localAppData) ? localAppData : join18(homedir5(), "AppData", "Local");
24767
26729
  return resolve12(base2, "KXM");
24768
26730
  }
24769
26731
  if (process.platform === "darwin") return resolve12(homedir5(), "Library", "Application Support", "KXM");
24770
26732
  const xdgState = env.XDG_STATE_HOME?.trim();
24771
- const base = xdgState && isAbsolute6(xdgState) ? xdgState : join18(homedir5(), ".local", "state");
26733
+ const base = xdgState && isAbsolute7(xdgState) ? xdgState : join18(homedir5(), ".local", "state");
24772
26734
  return resolve12(base, "kxm");
24773
26735
  }
24774
26736
  function hubBindingFile(env = process.env) {
@@ -24904,9 +26866,9 @@ var init_logger = __esm({
24904
26866
 
24905
26867
  // plugins/kxm/src/runtime-supervisor.ts
24906
26868
  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";
26869
+ import { createHash as createHash16, createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
24908
26870
  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";
26871
+ import { dirname as dirname12, isAbsolute as isAbsolute8, join as join19 } from "node:path";
24910
26872
  function kxmSupervisorTokenFile(paths) {
24911
26873
  return join19(paths.runtimeDir, "supervisor.token");
24912
26874
  }
@@ -24947,6 +26909,9 @@ function readRecentSupervisorError(paths) {
24947
26909
  return void 0;
24948
26910
  }
24949
26911
  }
26912
+ function kxmSupervisorRecordIsLive(record) {
26913
+ return supervisorStatusOf(record).running;
26914
+ }
24950
26915
  function supervisorStatusOf(record) {
24951
26916
  if (!record) return { running: false };
24952
26917
  const heartbeatAgeMs = Date.now() - Date.parse(record.heartbeatAt);
@@ -25284,7 +27249,7 @@ function refuseDryRun(io, jsonMode, command, reason) {
25284
27249
  }
25285
27250
  function printWorker(runtime, worker, payload, text, outcome) {
25286
27251
  const sessionId = runtime.env.KXM_SESSION_ID?.trim();
25287
- const envelope = workerResult(worker, {
27252
+ const envelope2 = workerResult(worker, {
25288
27253
  ...payload,
25289
27254
  summary: text,
25290
27255
  ...outcome ? { outcome } : {},
@@ -25292,7 +27257,7 @@ function printWorker(runtime, worker, payload, text, outcome) {
25292
27257
  });
25293
27258
  if (!runtime.dryRun) {
25294
27259
  try {
25295
- const safeEnvelope = redactCliValue(envelope);
27260
+ const safeEnvelope = redactCliValue(envelope2);
25296
27261
  appendTelemetry(telemetryPath(runtime.dirs.logs), makeTelemetryEvent({
25297
27262
  envelope: safeEnvelope,
25298
27263
  ...sessionId ? { sessionId } : {},
@@ -25305,7 +27270,7 @@ function printWorker(runtime, worker, payload, text, outcome) {
25305
27270
  } catch {
25306
27271
  }
25307
27272
  }
25308
- print(runtime.io, runtime.json, envelope, text);
27273
+ print(runtime.io, runtime.json, envelope2, text);
25309
27274
  }
25310
27275
  function hostMode(runtime) {
25311
27276
  try {
@@ -25561,8 +27526,8 @@ var init_model_inventory = __esm({
25561
27526
  });
25562
27527
 
25563
27528
  // plugins/kxm/src/permission.ts
25564
- import { spawnSync as spawnSync4 } from "node:child_process";
25565
- import { createHash as createHash16 } from "node:crypto";
27529
+ import { spawnSync as spawnSync5 } from "node:child_process";
27530
+ import { createHash as createHash17 } from "node:crypto";
25566
27531
  import { existsSync as existsSync24, mkdtempSync, mkdirSync as mkdirSync20, readFileSync as readFileSync24, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "node:fs";
25567
27532
  import { tmpdir } from "node:os";
25568
27533
  import { dirname as dirname14, join as join28, resolve as resolve19, sep as sep4 } from "node:path";
@@ -25766,7 +27731,7 @@ function kxmProseEntries(resource) {
25766
27731
  return entries;
25767
27732
  }
25768
27733
  function valueHash(canonicalValue) {
25769
- return `sha256:${createHash16("sha256").update(canonicalValue, "utf8").digest("hex")}`;
27734
+ return `sha256:${createHash17("sha256").update(canonicalValue, "utf8").digest("hex")}`;
25770
27735
  }
25771
27736
  function rankDirection(rank, baseValue, candidateValue) {
25772
27737
  const base = JSON.parse(baseValue);
@@ -26035,7 +28000,7 @@ function gitEnvironment2() {
26035
28000
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
26036
28001
  }
26037
28002
  function git(root, args) {
26038
- const result = spawnSync4("git", ["-C", root, ...args], {
28003
+ const result = spawnSync5("git", ["-C", root, ...args], {
26039
28004
  encoding: "utf8",
26040
28005
  env: gitEnvironment2(),
26041
28006
  timeout: 15e3,
@@ -26053,7 +28018,7 @@ function git(root, args) {
26053
28018
  return result.stdout;
26054
28019
  }
26055
28020
  function gitBuffer(root, args) {
26056
- const result = spawnSync4("git", ["-C", root, ...args], {
28021
+ const result = spawnSync5("git", ["-C", root, ...args], {
26057
28022
  env: gitEnvironment2(),
26058
28023
  timeout: 15e3,
26059
28024
  windowsHide: true,
@@ -26086,7 +28051,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
26086
28051
  const paths = listing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
26087
28052
  const shadow = mkdtempSync(join28(tmpdir(), "kxm-permission-base-"));
26088
28053
  const shadowResolved = resolve19(shadow);
26089
- const shadowMembersDir = `.kxm-shadow-members-${createHash16("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
28054
+ const shadowMembersDir = `.kxm-shadow-members-${createHash17("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
26090
28055
  try {
26091
28056
  for (const path4 of paths) {
26092
28057
  const segments = path4.split("/");
@@ -26251,7 +28216,7 @@ function loadBaseProjectDeclarations(projectFile) {
26251
28216
  return members;
26252
28217
  }
26253
28218
  function initShadowGitRoot(directory) {
26254
- const result = spawnSync4("git", ["-c", "init.defaultBranch=main", "init", "--quiet", directory], {
28219
+ const result = spawnSync5("git", ["-c", "init.defaultBranch=main", "init", "--quiet", directory], {
26255
28220
  encoding: "utf8",
26256
28221
  env: gitEnvironment2(),
26257
28222
  timeout: 1e4,
@@ -26306,7 +28271,7 @@ var init_permission = __esm({
26306
28271
  });
26307
28272
 
26308
28273
  // plugins/kxm/src/repair.ts
26309
- import { randomUUID as randomUUID10 } from "node:crypto";
28274
+ import { randomUUID as randomUUID11 } from "node:crypto";
26310
28275
  import {
26311
28276
  chmodSync as chmodSync5,
26312
28277
  closeSync as closeSync3,
@@ -26322,7 +28287,7 @@ import {
26322
28287
  rmSync as rmSync9,
26323
28288
  writeFileSync as writeFileSync20
26324
28289
  } from "node:fs";
26325
- import { dirname as dirname15, isAbsolute as isAbsolute8, join as join29, relative as relative5, resolve as resolve20 } from "node:path";
28290
+ import { dirname as dirname15, isAbsolute as isAbsolute9, join as join29, relative as relative5, resolve as resolve20 } from "node:path";
26326
28291
  function resourceKindForTemplatePath(path4) {
26327
28292
  if (path4 === ".kxm/project.yaml") return "project";
26328
28293
  if (path4 === ".kxm/gates.yaml") return "gate-registry";
@@ -26409,7 +28374,7 @@ function backupFile(projectRoot, portablePath2) {
26409
28374
  return join29(transactionRoot(projectRoot), "backups", ...portablePath2.split("/"));
26410
28375
  }
26411
28376
  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);
28377
+ return path4.startsWith(".kxm/") && path4 !== KXM_TEMPLATE_PROVENANCE_PATH && path4.length <= 1024 && !path4.includes("\\") && !isAbsolute9(path4) && PORTABLE_PATH.test(path4) && !/[<>:"|?*]/.test(path4);
26413
28378
  }
26414
28379
  function readRegularBounded(file, label) {
26415
28380
  const stat = lstatSync7(file);
@@ -26596,7 +28561,7 @@ function operationPlanSha(operation) {
26596
28561
  function writeOperation(projectRoot, operation) {
26597
28562
  const root = transactionRoot(projectRoot);
26598
28563
  const file = operationFile(projectRoot);
26599
- const temporary = join29(root, `.operation-${operation.operationId}-${randomUUID10()}.tmp`);
28564
+ const temporary = join29(root, `.operation-${operation.operationId}-${randomUUID11()}.tmp`);
26600
28565
  try {
26601
28566
  writeDurableNew(temporary, Buffer.from(`${JSON.stringify(operation, null, 2)}
26602
28567
  `, "utf8"), 384);
@@ -26640,7 +28605,7 @@ function readOperation(projectRoot, schemasDir) {
26640
28605
  seen.add(folded);
26641
28606
  prior = entry.path;
26642
28607
  }
26643
- if (!isAbsolute8(operation.projectRoot)) fail3("init_transaction_project_root_invalid", TRANSACTION_NAME, "operation project root must be absolute", "path");
28608
+ if (!isAbsolute9(operation.projectRoot)) fail3("init_transaction_project_root_invalid", TRANSACTION_NAME, "operation project root must be absolute", "path");
26644
28609
  validateOperationIntent(projectRoot, operation);
26645
28610
  return operation;
26646
28611
  }
@@ -26788,7 +28753,7 @@ function createOperation(projectRoot, kind, rendered, files, sourceTemplateRevis
26788
28753
  const root = resolve20(projectRoot);
26789
28754
  const operationWithoutHash = {
26790
28755
  schema: "kxm.init-operation.v1",
26791
- operationId: `op_${randomUUID10().replaceAll("-", "")}`,
28756
+ operationId: `op_${randomUUID11().replaceAll("-", "")}`,
26792
28757
  kind,
26793
28758
  projectRoot: root,
26794
28759
  projectId: rendered.projectId,
@@ -26852,7 +28817,7 @@ function destinationSha(projectRoot, path4) {
26852
28817
  return kxmContentSha256(readRegularBounded(file, path4));
26853
28818
  }
26854
28819
  function requireConditionalLinkSupport(directory, operationId, path4) {
26855
- const nonce = randomUUID10();
28820
+ const nonce = randomUUID11();
26856
28821
  const source = join29(directory, `.kxm-repair-${operationId}-${nonce}.link-source`);
26857
28822
  const target = join29(directory, `.kxm-repair-${operationId}-${nonce}.link-target`);
26858
28823
  try {
@@ -26906,7 +28871,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
26906
28871
  requireConditionalLinkSupport(directory, operation.operationId, entry.path);
26907
28872
  const bytes = readTarget(projectRoot, entry.path);
26908
28873
  if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
26909
- const temporary = join29(directory, `${temporaryPrefix}${randomUUID10()}.tmp`);
28874
+ const temporary = join29(directory, `${temporaryPrefix}${randomUUID11()}.tmp`);
26910
28875
  const mode = existsSync25(destination) ? lstatSync7(destination).mode & 511 : 420;
26911
28876
  try {
26912
28877
  writeDurableNew(temporary, bytes, mode);
@@ -27182,7 +29147,7 @@ function commitKxmInitTransaction(projectRoot, schemasDir) {
27182
29147
  fail3("init_transaction_not_verified", TRANSACTION_NAME, "initialization transaction cannot commit before installed state is verified");
27183
29148
  }
27184
29149
  const transaction = transactionRoot(projectRoot);
27185
- const retired = join29(projectRoot, `${TRANSACTION_NAME}-cleanup-${operation.operationId}-${randomUUID10()}`);
29150
+ const retired = join29(projectRoot, `${TRANSACTION_NAME}-cleanup-${operation.operationId}-${randomUUID11()}`);
27186
29151
  renameSync7(transaction, retired);
27187
29152
  syncDirectory2(projectRoot);
27188
29153
  rmSync9(retired, { recursive: true, force: true });
@@ -27213,7 +29178,7 @@ var init_repair = __esm({
27213
29178
  });
27214
29179
 
27215
29180
  // plugins/kxm/src/init.ts
27216
- import { randomUUID as randomUUID11 } from "node:crypto";
29181
+ import { randomUUID as randomUUID12 } from "node:crypto";
27217
29182
  import { existsSync as existsSync26 } from "node:fs";
27218
29183
  import { basename as basename6, join as join30 } from "node:path";
27219
29184
  function initIssue(code, file, message) {
@@ -27227,7 +29192,7 @@ function normalizedProjectName(root, requested) {
27227
29192
  return name;
27228
29193
  }
27229
29194
  function generatedProjectId(requested) {
27230
- const id = requested?.trim() || `prj_${randomUUID11().replaceAll("-", "")}`;
29195
+ const id = requested?.trim() || `prj_${randomUUID12().replaceAll("-", "")}`;
27231
29196
  if (!PROJECT_ID.test(id) || id.length > 144) {
27232
29197
  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
29198
  }
@@ -28059,29 +30024,29 @@ function runEngineNotice(runId, defaultHarness, prerequisites) {
28059
30024
  "These are local Runtime runs, not webhook workflows; use kxm runs, not kxm workflow get."
28060
30025
  ].join("\n");
28061
30026
  }
28062
- function resolveKxmRunTarget(runtime, workflow, forTask = false, envelope = {}, allowDefaultWorkflow = false) {
30027
+ function resolveKxmRunTarget(runtime, workflow, forTask = false, envelope2 = {}, allowDefaultWorkflow = false) {
28063
30028
  if (runtime.workspaceFlag !== void 0) {
28064
30029
  print(runtime.io, runtime.json, {
28065
30030
  ok: false,
28066
30031
  command: "run",
28067
30032
  error: "workspace_option_unsupported",
28068
- ...envelope
30033
+ ...envelope2
28069
30034
  }, "kxm run discovers the authoritative project from the current directory; --workspace is not supported");
28070
30035
  return 2;
28071
30036
  }
28072
30037
  if (!workflow && !forTask && !allowDefaultWorkflow) {
28073
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required", ...envelope }, "usage: kxm run <workflow> [prompt]");
30038
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required", ...envelope2 }, "usage: kxm run <workflow> [prompt]");
28074
30039
  return 2;
28075
30040
  }
28076
30041
  const projectRoot = discoverKxmProjectRoot(runtime.cwd);
28077
30042
  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)");
30043
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "project_required", ...envelope2 }, "kxm run requires a KXM project (run kxm init first)");
28079
30044
  return 1;
28080
30045
  }
28081
30046
  const bundle = loadKxmProject(projectRoot, {});
28082
30047
  const workflowId = workflow ?? String(bundle.project.value.defaultWorkflow ?? "default");
28083
30048
  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`);
30049
+ 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
30050
  return 1;
28086
30051
  }
28087
30052
  const defaultHarness = String(bundle.project.value.defaultHarness ?? "pi");
@@ -28101,9 +30066,9 @@ ${prerequisites.map((item) => `${item.stepId ?? workflowId}: ${item.detail}`).jo
28101
30066
  return { projectRoot, workflowId, configRevision: bundle.configRevision, defaultHarness, prerequisites };
28102
30067
  }
28103
30068
  async function cmdKxmRun(runtime, workflow, promptParts, forTask = false, call) {
28104
- const envelope = call?.brief !== void 0 ? { brief: call.brief } : {};
30069
+ const envelope2 = call?.brief !== void 0 ? { brief: call.brief } : {};
28105
30070
  try {
28106
- const target = resolveKxmRunTarget(runtime, workflow, forTask, envelope, call?.allowDefaultWorkflow === true);
30071
+ const target = resolveKxmRunTarget(runtime, workflow, forTask, envelope2, call?.allowDefaultWorkflow === true);
28107
30072
  if (typeof target === "number") return target;
28108
30073
  const { projectRoot } = target;
28109
30074
  if (runtime.dryRun) {
@@ -28111,7 +30076,7 @@ async function cmdKxmRun(runtime, workflow, promptParts, forTask = false, call)
28111
30076
  ok: true,
28112
30077
  command: "run",
28113
30078
  dryRun: true,
28114
- ...envelope,
30079
+ ...envelope2,
28115
30080
  ...target
28116
30081
  }, `run plan: workflow ${target.workflowId} at ${target.configRevision.slice(0, 19)}\u2026 (no run created)`);
28117
30082
  return 0;
@@ -28128,7 +30093,7 @@ async function cmdKxmRun(runtime, workflow, promptParts, forTask = false, call)
28128
30093
  print(runtime.io, runtime.json, {
28129
30094
  ok: true,
28130
30095
  command: "run",
28131
- ...envelope,
30096
+ ...envelope2,
28132
30097
  execution: {
28133
30098
  status: "not_started",
28134
30099
  mode: "live",
@@ -28150,10 +30115,10 @@ ${runEngineNotice(run.runId, target.defaultHarness, target.prerequisites)}`);
28150
30115
  return 0;
28151
30116
  } catch (error) {
28152
30117
  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}`);
30118
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_failed", issues: error.issues, ...envelope2 }, `run failed: ${error.message}`);
28154
30119
  return 1;
28155
30120
  }
28156
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_io_failed", ...envelope }, "run failed because a local operation did not complete");
30121
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_io_failed", ...envelope2 }, "run failed because a local operation did not complete");
28157
30122
  return 1;
28158
30123
  }
28159
30124
  }
@@ -28210,8 +30175,9 @@ async function cmdKxmRunStatus(runtime, runId) {
28210
30175
  print(
28211
30176
  runtime.io,
28212
30177
  runtime.json,
28213
- { ok: true, command: "runs status", run, ...drive !== void 0 ? { drive } : {} },
28214
- `${formatRunStatusLine(run, drive)}${driveLine ? `
30178
+ { ok: true, command: "runs status", projectRoot, run, ...drive !== void 0 ? { drive } : {} },
30179
+ `${formatRunStatusLine(run, drive)}
30180
+ root ${projectRoot}${driveLine ? `
28215
30181
  ${driveLine}` : ""}`
28216
30182
  );
28217
30183
  return 0;
@@ -33215,7 +35181,7 @@ init_runtime_supervisor();
33215
35181
  init_runtime_store();
33216
35182
  init_workflow();
33217
35183
  init_types();
33218
- import { randomUUID as randomUUID5 } from "node:crypto";
35184
+ import { randomUUID as randomUUID6 } from "node:crypto";
33219
35185
  import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
33220
35186
  import { join as join22, resolve as resolve14 } from "node:path";
33221
35187
  import { createInterface } from "node:readline";
@@ -33649,7 +35615,7 @@ async function cmdRoleResume(runtime, runId, ruling) {
33649
35615
  JSON.stringify(resumeResult.run),
33650
35616
  runId
33651
35617
  );
33652
- const journalId = randomUUID5();
35618
+ const journalId = randomUUID6();
33653
35619
  const journalPayload = {
33654
35620
  id: journalId,
33655
35621
  runId,
@@ -33695,7 +35661,7 @@ async function cmdRoleResume(runtime, runId, ruling) {
33695
35661
  // plugins/kxm/src/cli/workflows.ts
33696
35662
  var import_yaml12 = __toESM(require_dist(), 1);
33697
35663
  init_sqlite();
33698
- import { randomUUID as randomUUID7 } from "node:crypto";
35664
+ import { randomUUID as randomUUID8 } from "node:crypto";
33699
35665
  import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
33700
35666
  import { join as join24, resolve as resolve16 } from "node:path";
33701
35667
 
@@ -33943,7 +35909,7 @@ init_redact();
33943
35909
  // plugins/kxm/src/github-watch.ts
33944
35910
  init_redact();
33945
35911
  init_workflow();
33946
- import { randomUUID as randomUUID6 } from "node:crypto";
35912
+ import { randomUUID as randomUUID7 } from "node:crypto";
33947
35913
  async function fetchWithTimeout(fetchImpl, input, init, timeoutMs) {
33948
35914
  const controller = new AbortController();
33949
35915
  const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));
@@ -34045,7 +36011,7 @@ async function watchGithubChecks(input) {
34045
36011
  "workflow.signal": input.signalKey
34046
36012
  };
34047
36013
  const explicitDeliveryId = input.deliveryId?.trim();
34048
- const deliveryGeneration = randomUUID6();
36014
+ const deliveryGeneration = randomUUID7();
34049
36015
  let deliveryId = explicitDeliveryId || `github-watch:${deliveryGeneration}:pr-${input.pr}`;
34050
36016
  const deliver = async (status, summary, evidence) => {
34051
36017
  const boundedEvidence = Object.fromEntries(Object.entries({ ...contextEvidence, ...evidence }).slice(0, 64));
@@ -34786,7 +36752,7 @@ async function cmdWorkflowModify(runtime, workflowId, options) {
34786
36752
  }
34787
36753
  async function cmdWorkflowStart(runtime, definitionIdArg, options) {
34788
36754
  const definitionId = definitionIdArg || runtime.env.KXM_WORKFLOW_ID?.trim();
34789
- const deliveryId = String(options.deliveryId || `cli-${randomUUID7()}`);
36755
+ const deliveryId = String(options.deliveryId || `cli-${randomUUID8()}`);
34790
36756
  const event = options.event;
34791
36757
  const payloadFlag = options.payload ?? "{}";
34792
36758
  if (!definitionId) {
@@ -34917,7 +36883,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
34917
36883
  printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signal to KXM run");
34918
36884
  return 0;
34919
36885
  }
34920
- const deliveryId2 = String(deliveryIdFlag || `cli-signal:${randomUUID7()}`);
36886
+ const deliveryId2 = String(deliveryIdFlag || `cli-signal:${randomUUID8()}`);
34921
36887
  try {
34922
36888
  const supervisor = await ensureKxmSupervisor({ env: runtime.env });
34923
36889
  const posted = await kxmRuntimeRequest(
@@ -34953,7 +36919,7 @@ async function cmdSignal(runtime, runId, signalKey, status, summary, evidenceArg
34953
36919
  printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signed signal");
34954
36920
  return 0;
34955
36921
  }
34956
- const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID7()}`);
36922
+ const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID8()}`);
34957
36923
  try {
34958
36924
  const posted = await postWorkflowSignal({
34959
36925
  serverUrl: runtime.serverUrl,
@@ -35232,7 +37198,7 @@ function suggestWorkflowAndRoles(prompt, options = {}) {
35232
37198
 
35233
37199
  // plugins/kxm/src/task-manager.ts
35234
37200
  var import_yaml13 = __toESM(require_dist(), 1);
35235
- import { randomUUID as randomUUID8 } from "node:crypto";
37201
+ import { randomUUID as randomUUID9 } from "node:crypto";
35236
37202
  import { existsSync as existsSync23, mkdirSync as mkdirSync17, readFileSync as readFileSync22, readdirSync as readdirSync8, writeFileSync as writeFileSync17 } from "node:fs";
35237
37203
  import { join as join25, resolve as resolve17 } from "node:path";
35238
37204
  var GOAL_SCHEMA = "kxm.goal.v1";
@@ -35250,7 +37216,7 @@ function taskFilePath(repoRoot, taskId) {
35250
37216
  return join25(tasksDirectory(repoRoot), `${taskId}.yaml`);
35251
37217
  }
35252
37218
  function createGoal(repoRoot, input, options = {}) {
35253
- const id = `goal_${randomUUID8().replaceAll("-", "").slice(0, 12)}`;
37219
+ const id = `goal_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
35254
37220
  const now = (/* @__PURE__ */ new Date()).toISOString();
35255
37221
  const record = {
35256
37222
  schema: GOAL_SCHEMA,
@@ -35286,7 +37252,7 @@ function listGoals(repoRoot) {
35286
37252
  return goals.sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
35287
37253
  }
35288
37254
  function createTask(repoRoot, input, options = {}) {
35289
- const id = `task_${randomUUID8().replaceAll("-", "").slice(0, 12)}`;
37255
+ const id = `task_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
35290
37256
  const now = (/* @__PURE__ */ new Date()).toISOString();
35291
37257
  const record = {
35292
37258
  schema: TASK_SCHEMA,
@@ -35367,7 +37333,7 @@ init_engine_compile();
35367
37333
 
35368
37334
  // plugins/kxm/src/studio-layout.ts
35369
37335
  import { createServer } from "node:http";
35370
- import { randomUUID as randomUUID9 } from "node:crypto";
37336
+ import { randomUUID as randomUUID10 } from "node:crypto";
35371
37337
  var STUDIO_LAYOUT_SCHEMA = "kxm.studio-layout.v1";
35372
37338
  function generateStudioLayout(plan, state) {
35373
37339
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -35630,7 +37596,7 @@ function createStudioServer(options = {}) {
35630
37596
  }));
35631
37597
  return;
35632
37598
  }
35633
- const mutationId = `mut_${randomUUID9().replaceAll("-", "").slice(0, 12)}`;
37599
+ const mutationId = `mut_${randomUUID10().replaceAll("-", "").slice(0, 12)}`;
35634
37600
  if (options.onMutation) {
35635
37601
  try {
35636
37602
  const outcome = await options.onMutation(command, args);
@@ -36904,7 +38870,7 @@ init_project();
36904
38870
  init_repo_root();
36905
38871
  init_harness();
36906
38872
  init_types();
36907
- import { spawnSync as spawnSync5 } from "node:child_process";
38873
+ import { spawnSync as spawnSync6 } from "node:child_process";
36908
38874
  var PLUGIN_SUPPORTED_HARNESSES = Object.freeze(["pi", "omp", "claude"]);
36909
38875
  async function cmdPluginInstall(runtime, options) {
36910
38876
  const repoRoot = findKxmRepoRoot(import.meta.url);
@@ -36943,7 +38909,7 @@ async function cmdPluginInstall(runtime, options) {
36943
38909
  detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`
36944
38910
  });
36945
38911
  } else {
36946
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync5(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
38912
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync6(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
36947
38913
  if (run.error || run.status !== 0 && run.status !== null) {
36948
38914
  results.push({
36949
38915
  harness: "pi",
@@ -36974,7 +38940,7 @@ async function cmdPluginInstall(runtime, options) {
36974
38940
  detail: `would install kxm plugin into omp: ${command} ${args.join(" ")}`
36975
38941
  });
36976
38942
  } else {
36977
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync5(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
38943
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync6(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
36978
38944
  if (run.error || run.status !== 0 && run.status !== null) {
36979
38945
  results.push({
36980
38946
  harness: "omp",
@@ -37009,9 +38975,9 @@ async function cmdPluginInstall(runtime, options) {
37009
38975
  if (runtime.io.spawnSync) {
37010
38976
  runtime.io.spawnSync(command, marketplaceArgs);
37011
38977
  } else {
37012
- spawnSync5(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
38978
+ spawnSync6(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
37013
38979
  }
37014
- const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync5(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
38980
+ const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync6(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
37015
38981
  if (run.error || run.status !== 0 && run.status !== null) {
37016
38982
  results.push({
37017
38983
  harness: "claude",
@@ -37132,25 +39098,27 @@ async function cmdDocsServe(runtime, options = {}) {
37132
39098
 
37133
39099
  // plugins/kxm/src/cli/lanes.ts
37134
39100
  init_project_config();
39101
+ init_runtime_store();
37135
39102
  init_runtime_supervisor();
37136
39103
  init_types();
37137
- import { spawnSync as spawnSync6 } from "node:child_process";
39104
+ import { spawnSync as spawnSync7 } from "node:child_process";
37138
39105
  import { chmodSync as chmodSync6, existsSync as existsSync30, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "node:fs";
37139
39106
  import { basename as basename8, dirname as dirname17, join as join36, resolve as resolve24 } from "node:path";
37140
39107
  var LANE_SCHEMA = "kxm.lanes.v1";
37141
39108
  var SHA_RE = /^[a-f0-9]{40,64}$/;
37142
39109
  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
39110
  var SETTLED_RUN = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
39111
+ var LANE_RUN_LIST_LIMIT = 1e4;
37144
39112
  function lanesFile(runtime) {
37145
39113
  return join36(runtime.dirs.state, "lanes.json");
37146
39114
  }
37147
- function gitEnv() {
39115
+ function gitEnv2() {
37148
39116
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
37149
39117
  }
37150
39118
  function git2(cwd, args) {
37151
- const result = spawnSync6("git", ["-C", cwd, ...args], {
39119
+ const result = spawnSync7("git", ["-C", cwd, ...args], {
37152
39120
  encoding: "utf8",
37153
- env: gitEnv(),
39121
+ env: gitEnv2(),
37154
39122
  timeout: 3e4,
37155
39123
  windowsHide: true
37156
39124
  });
@@ -37373,6 +39341,61 @@ async function projectedRunStatus(runtime, projectRoot, runId, allowStart) {
37373
39341
  function isSettled(status) {
37374
39342
  return SETTLED_RUN.has(status);
37375
39343
  }
39344
+ async function laneStoreRuns(runtime, lanePath) {
39345
+ const project = await Promise.resolve().then(() => (init_project(), project_exports));
39346
+ const request = project.kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest;
39347
+ const handle = await attachKxmSupervisor({ env: runtime.env });
39348
+ const projectId = () => String(loadKxmProject(lanePath, {}).project.value.id);
39349
+ if (handle) {
39350
+ try {
39351
+ const result = await request(
39352
+ handle,
39353
+ "GET",
39354
+ `/v1/projects/${encodeURIComponent(projectId())}/runs?projectRoot=${encodeURIComponent(lanePath)}&limit=${LANE_RUN_LIST_LIMIT}`
39355
+ );
39356
+ const runs = Array.isArray(result.runs) ? result.runs : [];
39357
+ return runs.flatMap((run) => {
39358
+ if (!run || typeof run !== "object" || Array.isArray(run)) return [];
39359
+ const record = run;
39360
+ if (typeof record.runId !== "string" || record.runId.length === 0) return [];
39361
+ const status = typeof record.status === "string" && record.status.length > 0 ? record.status : "unknown";
39362
+ return [{ runId: record.runId, status }];
39363
+ });
39364
+ } catch {
39365
+ }
39366
+ }
39367
+ const eventsPath = kxmProjectRunEventsPath(lanePath, runtime.env);
39368
+ if (!existsSync30(eventsPath)) return void 0;
39369
+ const store = new KxmRunEventStore(eventsPath);
39370
+ try {
39371
+ return store.runsForProject(projectId(), LANE_RUN_LIST_LIMIT).map((run) => ({ runId: run.runId, status: run.status }));
39372
+ } finally {
39373
+ store.close();
39374
+ }
39375
+ }
39376
+ async function refuseIfLaneStoreRunsOpen(runtime, unit, command) {
39377
+ const loaded = loadLanes(runtime, command);
39378
+ if (typeof loaded === "number") return loaded;
39379
+ const lane = loaded.lanes[unit];
39380
+ if (!lane) return void 0;
39381
+ let runs;
39382
+ try {
39383
+ runs = await laneStoreRuns(runtime, lane.path);
39384
+ } catch (error) {
39385
+ const detail = error instanceof Error ? error.message : "run list failed";
39386
+ return refuse(runtime, command, "lane_run_open", `lane ${unit} runs could not be read: ${detail.slice(0, 200)}`, { unit });
39387
+ }
39388
+ if (!runs) return void 0;
39389
+ const unsettled = runs.filter((run) => !isSettled(run.status)).map((run) => run.runId);
39390
+ if (unsettled.length === 0) return void 0;
39391
+ return refuse(
39392
+ runtime,
39393
+ command,
39394
+ "lane_run_open",
39395
+ `lane ${unit} run ${unsettled.join(", ")} is unsettled`,
39396
+ { unit, runIds: unsettled }
39397
+ );
39398
+ }
37376
39399
  async function refuseIfLaneRunOpen(runtime, unit, command) {
37377
39400
  const loaded = loadLanes(runtime, command);
37378
39401
  if (typeof loaded === "number") return loaded;
@@ -37483,11 +39506,43 @@ async function cmdLaneStatus(runtime, unit) {
37483
39506
  print(
37484
39507
  runtime.io,
37485
39508
  runtime.json,
37486
- { ok: true, command, lane: { ...view, status: runStatus } },
37487
- `${formatLaneLine(view)} status=${runStatus}`
39509
+ { ok: true, command, root: record.path, lane: { ...view, status: runStatus } },
39510
+ `${formatLaneLine(view)} status=${runStatus} root=${record.path}`
37488
39511
  );
37489
39512
  return 0;
37490
39513
  }
39514
+ async function unregisterLaneRoot(runtime, projectRoot, force) {
39515
+ const body = force ? { projectRoot, force: true } : { projectRoot };
39516
+ try {
39517
+ const project = await Promise.resolve().then(() => (init_project(), project_exports));
39518
+ const request = project.kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest;
39519
+ const post = async (handle2) => {
39520
+ await request(handle2, "POST", "/v1/projects/unregister", body);
39521
+ };
39522
+ const handle = await attachKxmSupervisor({ env: runtime.env });
39523
+ if (handle) {
39524
+ await post(handle);
39525
+ return void 0;
39526
+ }
39527
+ const paths = kxmRuntimePaths({ env: runtime.env });
39528
+ if (!existsSync30(paths.registryDb)) return void 0;
39529
+ const registry = new KxmRuntimeRegistry(paths.registryDb);
39530
+ let outcome;
39531
+ try {
39532
+ outcome = registry.unregisterProjectIfIdle(projectRoot, kxmSupervisorRecordIsLive);
39533
+ } finally {
39534
+ registry.close();
39535
+ }
39536
+ if (outcome === "supervisor_live") {
39537
+ const live = await attachKxmSupervisor({ env: runtime.env });
39538
+ if (!live) return "runtime supervisor is live but could not be reached";
39539
+ await post(live);
39540
+ }
39541
+ return void 0;
39542
+ } catch (error) {
39543
+ return error instanceof Error ? error.message : "lane root could not be unregistered";
39544
+ }
39545
+ }
37491
39546
  async function cmdLaneDrop(runtime, unit, options = {}) {
37492
39547
  const command = "lane drop";
37493
39548
  const invalid = refuseUnit(runtime, command, unit);
@@ -37509,16 +39564,23 @@ async function cmdLaneDrop(runtime, unit, options = {}) {
37509
39564
  return refuse(runtime, command, "lane_dirty", `lane ${unit} has ${dirty} uncommitted change(s)`, { unit, dirty });
37510
39565
  }
37511
39566
  }
39567
+ const listed = await refuseIfLaneStoreRunsOpen(runtime, unit, command);
39568
+ if (listed !== void 0) return listed;
37512
39569
  const open = await refuseIfLaneRunOpen(runtime, unit, command);
37513
39570
  if (open !== void 0) return open;
37514
39571
  }
37515
39572
  if (runtime.dryRun) {
37516
39573
  printPlan(runtime, { command, unit, branchKept: record.branch }, [
39574
+ { action: "delete", target: `runtime registry ${record.path}` },
37517
39575
  { action: "delete", target: record.path },
37518
39576
  { action: "write", target: lanesFile(runtime) }
37519
39577
  ], `drop lane ${unit}; branch ${record.branch} is not deleted`);
37520
39578
  return 0;
37521
39579
  }
39580
+ const unregistered = await unregisterLaneRoot(runtime, record.path, force);
39581
+ if (unregistered !== void 0) {
39582
+ return refuse(runtime, command, "lane_unregister_failed", `lane ${unit} could not be unregistered: ${unregistered.slice(0, 300)}`, { unit });
39583
+ }
37522
39584
  if (existsSync30(record.path)) {
37523
39585
  const removed = git2(root, ["worktree", "remove", ...force ? ["--force"] : [], record.path]);
37524
39586
  if (removed.status !== 0) {
@@ -37629,7 +39691,7 @@ function cmdLand(runtime, options = {}) {
37629
39691
  // plugins/kxm/src/cli/assign.ts
37630
39692
  init_project_config();
37631
39693
  init_types();
37632
- import { spawnSync as spawnSync7 } from "node:child_process";
39694
+ import { spawnSync as spawnSync8 } from "node:child_process";
37633
39695
  import { existsSync as existsSync31 } from "node:fs";
37634
39696
  import { join as join38 } from "node:path";
37635
39697
  var kxmAssignCliSeams = {};
@@ -37715,7 +39777,7 @@ async function cmdAssign(runtime, subcommand, args) {
37715
39777
  return 0;
37716
39778
  }
37717
39779
  const spawn6 = kxmAssignCliSeams.spawn ?? ((commandName, commandArgs, options) => {
37718
- const result2 = spawnSync7(commandName, commandArgs, {
39780
+ const result2 = spawnSync8(commandName, commandArgs, {
37719
39781
  cwd: options.cwd,
37720
39782
  stdio: options.stdio,
37721
39783
  env: options.env,
@@ -37733,7 +39795,7 @@ async function cmdAssign(runtime, subcommand, args) {
37733
39795
 
37734
39796
  // plugins/kxm/src/cli/hub.ts
37735
39797
  init_redact();
37736
- import { randomUUID as randomUUID13 } from "node:crypto";
39798
+ import { randomUUID as randomUUID14 } from "node:crypto";
37737
39799
  import { existsSync as existsSync37, mkdirSync as mkdirSync28, readFileSync as readFileSync35, readdirSync as readdirSync12, rmSync as rmSync11, writeFileSync as writeFileSync25 } from "node:fs";
37738
39800
  import { join as join48, resolve as resolve27 } from "node:path";
37739
39801
  init_hub_env();
@@ -46177,7 +48239,7 @@ var MAX_RENDER_WRITE_CHARS = 1024 * 1024;
46177
48239
  // plugins/kxm/src/tui.ts
46178
48240
  import { mkdirSync as mkdirSync25 } from "node:fs";
46179
48241
  import { join as join43, resolve as resolve26, dirname as dirname19 } from "node:path";
46180
- import { spawnSync as spawnSync8 } from "node:child_process";
48242
+ import { spawnSync as spawnSync9 } from "node:child_process";
46181
48243
 
46182
48244
  // packages/core/tui/src/types/surface.ts
46183
48245
  var KXM_TUI_LIMITS = Object.freeze({
@@ -46322,7 +48384,7 @@ init_sqlite();
46322
48384
  init_telemetry();
46323
48385
  import { existsSync as existsSync32, readdirSync as readdirSync11, readFileSync as readFileSync30 } from "node:fs";
46324
48386
  import { homedir as homedir6 } from "node:os";
46325
- import { isAbsolute as isAbsolute9, join as join42, resolve as resolve25 } from "node:path";
48387
+ import { isAbsolute as isAbsolute10, join as join42, resolve as resolve25 } from "node:path";
46326
48388
  var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
46327
48389
  var RUNTIME_PROJECT_KEY = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
46328
48390
  function processExists2(pid) {
@@ -46461,19 +48523,19 @@ function resolveKxmStateRoot(stateDir, options) {
46461
48523
  }
46462
48524
  const env = options?.env ?? process.env;
46463
48525
  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))) {
48526
+ if (explicit && isAbsolute10(explicit) && existsSync32(resolve25(explicit))) {
46465
48527
  return resolve25(explicit);
46466
48528
  }
46467
48529
  let base;
46468
48530
  if (process.platform === "win32") {
46469
48531
  const localAppData = env.LOCALAPPDATA?.trim();
46470
- base = localAppData && isAbsolute9(localAppData) ? localAppData : join42(homedir6(), "AppData", "Local");
48532
+ base = localAppData && isAbsolute10(localAppData) ? localAppData : join42(homedir6(), "AppData", "Local");
46471
48533
  base = resolve25(base, "KXM");
46472
48534
  } else if (process.platform === "darwin") {
46473
48535
  base = resolve25(homedir6(), "Library", "Application Support", "KXM");
46474
48536
  } else {
46475
48537
  const xdgState = env.XDG_STATE_HOME?.trim();
46476
- base = xdgState && isAbsolute9(xdgState) ? xdgState : join42(homedir6(), ".local", "state");
48538
+ base = xdgState && isAbsolute10(xdgState) ? xdgState : join42(homedir6(), ".local", "state");
46477
48539
  base = resolve25(base, "kxm");
46478
48540
  }
46479
48541
  if (existsSync32(base)) return base;
@@ -46734,16 +48796,16 @@ function applyMeshTuiKey(view, key, itemCount = 0) {
46734
48796
  function copyToClipboard(text) {
46735
48797
  try {
46736
48798
  if (process.platform === "darwin") {
46737
- const proc = spawnSync8("pbcopy", { input: text, encoding: "utf8", windowsHide: true });
48799
+ const proc = spawnSync9("pbcopy", { input: text, encoding: "utf8", windowsHide: true });
46738
48800
  return proc.status === 0;
46739
48801
  }
46740
48802
  if (process.platform === "win32") {
46741
- const proc = spawnSync8("clip", { input: text, encoding: "utf8", windowsHide: true });
48803
+ const proc = spawnSync9("clip", { input: text, encoding: "utf8", windowsHide: true });
46742
48804
  return proc.status === 0;
46743
48805
  }
46744
- const wl = spawnSync8("wl-copy", [text], { encoding: "utf8", windowsHide: true });
48806
+ const wl = spawnSync9("wl-copy", [text], { encoding: "utf8", windowsHide: true });
46745
48807
  if (wl.status === 0) return true;
46746
- const xclip = spawnSync8("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true });
48808
+ const xclip = spawnSync9("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true });
46747
48809
  return xclip.status === 0;
46748
48810
  } catch {
46749
48811
  return false;
@@ -46751,7 +48813,7 @@ function copyToClipboard(text) {
46751
48813
  }
46752
48814
  function spawnDegradeWorktree(repoRoot, runId, options) {
46753
48815
  const runner = options?.execFn ?? ((cmd, args) => {
46754
- const res = spawnSync8(cmd, args, {
48816
+ const res = spawnSync9(cmd, args, {
46755
48817
  cwd: repoRoot,
46756
48818
  encoding: "utf8",
46757
48819
  windowsHide: true,
@@ -47535,8 +49597,8 @@ async function runMeshTui(input) {
47535
49597
  }
47536
49598
 
47537
49599
  // plugins/kxm/src/session-work.ts
47538
- import { spawnSync as spawnSync9 } from "node:child_process";
47539
- import { randomUUID as randomUUID12 } from "node:crypto";
49600
+ import { spawnSync as spawnSync10 } from "node:child_process";
49601
+ import { randomUUID as randomUUID13 } from "node:crypto";
47540
49602
  import { existsSync as existsSync33, mkdirSync as mkdirSync26, readFileSync as readFileSync31, renameSync as renameSync8, writeFileSync as writeFileSync23 } from "node:fs";
47541
49603
  import { join as join44 } from "node:path";
47542
49604
  init_hub_binding();
@@ -47596,10 +49658,10 @@ function formatShipLine(ship) {
47596
49658
  }
47597
49659
  function readGitShip(cwd) {
47598
49660
  try {
47599
- const dirty = spawnSync9("git", ["-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
49661
+ const dirty = spawnSync10("git", ["-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
47600
49662
  if (dirty.status !== 0) return void 0;
47601
49663
  const isDirty = dirty.stdout.trim().length > 0;
47602
- const upstream = spawnSync9("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
49664
+ const upstream = spawnSync10("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
47603
49665
  if (upstream.status === 0) {
47604
49666
  return {
47605
49667
  dirty: isDirty,
@@ -47607,9 +49669,9 @@ function readGitShip(cwd) {
47607
49669
  };
47608
49670
  }
47609
49671
  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 });
49672
+ const mb = spawnSync10("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
47611
49673
  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 });
49674
+ const count = spawnSync10("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
47613
49675
  if (count.status === 0) {
47614
49676
  return {
47615
49677
  dirty: isDirty,
@@ -47717,7 +49779,7 @@ function writeCachedSessionBrief(stateDir, brief) {
47717
49779
  try {
47718
49780
  mkdirSync26(stateDir, { recursive: true, mode: 448 });
47719
49781
  const file = join44(stateDir, "session-brief.json");
47720
- const tmp = `${file}.tmp.${randomUUID12().slice(0, 8)}`;
49782
+ const tmp = `${file}.tmp.${randomUUID13().slice(0, 8)}`;
47721
49783
  writeFileSync23(tmp, JSON.stringify(brief, null, 2), { encoding: "utf8", mode: 384 });
47722
49784
  renameSync8(tmp, file);
47723
49785
  } catch {
@@ -48588,16 +50650,16 @@ async function cmdSessionStatus(runtime) {
48588
50650
  const recoveries = [];
48589
50651
  for (const file of names3.filter((name) => name.startsWith("worker-recovery-") && name.endsWith(".json"))) {
48590
50652
  try {
48591
- const envelope = JSON.parse(readFileSync35(join48(stateDir, file), "utf8"));
50653
+ const envelope2 = JSON.parse(readFileSync35(join48(stateDir, file), "utf8"));
48592
50654
  recoveries.push({
48593
50655
  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
50656
+ reason: envelope2.reason,
50657
+ agentName: envelope2.agentName,
50658
+ project: envelope2.project,
50659
+ createdAt: envelope2.createdAt,
50660
+ runId: envelope2.runId ?? void 0,
50661
+ stageId: envelope2.stageId ?? void 0,
50662
+ freshSession: envelope2.freshSession === true
48601
50663
  });
48602
50664
  } catch {
48603
50665
  recoveries.push({ file, error: "invalid_recovery_envelope" });
@@ -48752,7 +50814,7 @@ Session token: ${sessionToken}
48752
50814
  return 0;
48753
50815
  }
48754
50816
  async function cmdSessionStart(runtime, options) {
48755
- const id = options.id?.trim() || `session_${randomUUID13().replaceAll("-", "").slice(0, 12)}`;
50817
+ const id = options.id?.trim() || `session_${randomUUID14().replaceAll("-", "").slice(0, 12)}`;
48756
50818
  const workflowId = options.workflow?.trim();
48757
50819
  const mix = options.mix?.trim();
48758
50820
  if (workflowId && mix) {
@@ -48811,8 +50873,8 @@ init_redact();
48811
50873
  init_telemetry();
48812
50874
  init_routing();
48813
50875
  init_prices();
48814
- import { spawnSync as spawnSync11 } from "node:child_process";
48815
- import { createHash as createHash19 } from "node:crypto";
50876
+ import { spawnSync as spawnSync12 } from "node:child_process";
50877
+ import { createHash as createHash20 } from "node:crypto";
48816
50878
  import { existsSync as existsSync44, mkdtempSync as mkdtempSync3, readFileSync as readFileSync41, rmSync as rmSync13 } from "node:fs";
48817
50879
  import { tmpdir as tmpdir2 } from "node:os";
48818
50880
  import { basename as basename11, extname as extname3, join as join54, resolve as resolve32 } from "node:path";
@@ -48822,7 +50884,7 @@ import { createInterface as createInterface3 } from "node:readline";
48822
50884
  init_routing();
48823
50885
  init_protocol();
48824
50886
  init_relevance();
48825
- import { createHash as createHash17 } from "node:crypto";
50887
+ import { createHash as createHash18 } from "node:crypto";
48826
50888
  import { existsSync as existsSync38, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
48827
50889
  import { join as join49, relative as relative7, resolve as resolve28 } from "node:path";
48828
50890
  var CANDIDATE_SCHEMA = "kxm.candidate.v1";
@@ -49039,7 +51101,7 @@ function groupRoutingRecords(records, options = {}) {
49039
51101
  }
49040
51102
  const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : void 0;
49041
51103
  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);
51104
+ const keyHash = createHash18("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
49043
51105
  const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : void 0;
49044
51106
  keyed.push({
49045
51107
  key: group.key,
@@ -49668,8 +51730,8 @@ ${divider}
49668
51730
 
49669
51731
  // plugins/kxm/src/ssh-remote.ts
49670
51732
  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";
51733
+ import { spawnSync as spawnSync11 } from "node:child_process";
51734
+ import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync13, rmSync as rmSync12, statSync as statSync6 } from "node:fs";
49673
51735
  import { homedir as homedir8 } from "node:os";
49674
51736
  import { join as join51, resolve as resolve30 } from "node:path";
49675
51737
  var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
@@ -49744,7 +51806,7 @@ function parseSshConfig(configPath) {
49744
51806
  return [];
49745
51807
  }
49746
51808
  }
49747
- function resolveSshHostG(host, execFn = spawnSync10) {
51809
+ function resolveSshHostG(host, execFn = spawnSync11) {
49748
51810
  try {
49749
51811
  const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
49750
51812
  if (result.status !== 0 || !result.stdout) {
@@ -49810,7 +51872,7 @@ function buildSshArgs(options) {
49810
51872
  args.push(options.host);
49811
51873
  return args;
49812
51874
  }
49813
- function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync10) {
51875
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync11) {
49814
51876
  const resolvedDir = ensureSocketDir(socketDir);
49815
51877
  const controlPath = join51(resolvedDir, "%C");
49816
51878
  try {
@@ -49822,7 +51884,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
49822
51884
  return false;
49823
51885
  }
49824
51886
  }
49825
- function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync10) {
51887
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync11) {
49826
51888
  const resolvedDir = ensureSocketDir(socketDir);
49827
51889
  const controlPath = join51(resolvedDir, "%C");
49828
51890
  try {
@@ -49836,7 +51898,7 @@ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
49836
51898
  }
49837
51899
  function executeSshRun(params) {
49838
51900
  const startTime = Date.now();
49839
- const execSyncFn = params.execFn ?? spawnSync10;
51901
+ const execSyncFn = params.execFn ?? spawnSync11;
49840
51902
  if (params.action === "info") {
49841
51903
  if (params.host) {
49842
51904
  const hostInfo = resolveSshHostG(params.host, execSyncFn);
@@ -50277,7 +52339,7 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
50277
52339
  // plugins/kxm/src/completion-install.ts
50278
52340
  import { existsSync as existsSync42, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync27 } from "node:fs";
50279
52341
  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";
52342
+ import { basename as basename10, delimiter, dirname as dirname23, isAbsolute as isAbsolute11, join as join52, resolve as resolve31 } from "node:path";
50281
52343
  var COMPLETION_MARKER = "# kxm completion";
50282
52344
  var PATH_MARKER = "# kxm path";
50283
52345
  function detectShell(env = process.env, platform = process.platform) {
@@ -50403,7 +52465,7 @@ ${line}
50403
52465
  }
50404
52466
  function kxmBinDir(env = process.env) {
50405
52467
  const argv1 = env.KXM_ENTRY ?? process.argv[1];
50406
- if (argv1 && isAbsolute10(argv1)) {
52468
+ if (argv1 && isAbsolute11(argv1)) {
50407
52469
  const dir = dirname23(argv1);
50408
52470
  if (existsSync42(join52(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
50409
52471
  }
@@ -50444,7 +52506,7 @@ ${line}
50444
52506
  // plugins/kxm/src/init-guide-setup.ts
50445
52507
  var import_yaml18 = __toESM(require_dist(), 1);
50446
52508
  init_routes();
50447
- import { createHash as createHash18 } from "node:crypto";
52509
+ import { createHash as createHash19 } from "node:crypto";
50448
52510
  import { existsSync as existsSync43, mkdirSync as mkdirSync32, readFileSync as readFileSync40, writeFileSync as writeFileSync28 } from "node:fs";
50449
52511
  import { dirname as dirname24, join as join53 } from "node:path";
50450
52512
  var ADMITTED_GUIDE_BINDINGS = Object.freeze({
@@ -50829,7 +52891,7 @@ function workflowDocument(workflow) {
50829
52891
  }
50830
52892
  function renderGuideSetupFiles(projectRoot, plan) {
50831
52893
  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;
52894
+ const projectOrigin = existsSync43(projectYaml) ? { source: ".kxm/project.yaml", sha256: createHash19("sha256").update(readFileSync40(projectYaml)).digest("hex") } : void 0;
50833
52895
  const files = [];
50834
52896
  const roleStages = /* @__PURE__ */ new Map();
50835
52897
  for (const workflow of plan.workflows) {
@@ -50931,7 +52993,7 @@ init_types();
50931
52993
  init_repo_root();
50932
52994
  function cliSpawn(runtime, command, args, extra) {
50933
52995
  if (runtime.io.spawnSync) return runtime.io.spawnSync(command, args);
50934
- const result = spawnSync11(command, [...args], {
52996
+ const result = spawnSync12(command, [...args], {
50935
52997
  encoding: "utf8",
50936
52998
  windowsHide: true,
50937
52999
  shell: process.platform === "win32",
@@ -50978,7 +53040,7 @@ function applyKxmPackageUpdate(runtime, notice) {
50978
53040
  for (const step of planned) {
50979
53041
  if (step.kind === "verify") {
50980
53042
  if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
50981
- const actual = existsSync44(step.path) ? createHash19("sha256").update(readFileSync41(step.path)).digest("hex") : "missing";
53043
+ const actual = existsSync44(step.path) ? createHash20("sha256").update(readFileSync41(step.path)).digest("hex") : "missing";
50982
53044
  return {
50983
53045
  ok: false,
50984
53046
  error: "release_digest_mismatch",