@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +20 -0
- package/docs/architecture/inventory.md +1 -1
- package/docs/concepts/data-and-storage.md +1 -1
- package/docs/contracts/README.md +1 -1
- package/docs/contracts/migration.md +12 -8
- package/docs/contributing/development.md +3 -1
- package/docs/contributing/learnings.md +21 -0
- package/docs/operations/backup-and-restore.md +3 -3
- package/docs/operations/upgrade.md +2 -2
- package/docs/reference/cli-reference.md +11 -6
- package/docs/reference/config-reference.md +5 -2
- package/package.json +8 -8
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +2284 -222
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +461 -106
- package/plugins/kxm/dist/runtime.js +470 -114
- package/plugins/kxm/dist/server.js +17 -14
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/lanes.ts +109 -4
- package/plugins/kxm/src/cli/project.ts +2 -2
- package/plugins/kxm/src/database.ts +25 -19
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/runtime-service.ts +1 -0
- package/plugins/kxm/src/runtime-store.ts +390 -77
- package/plugins/kxm/src/runtime-supervisor.ts +87 -3
- package/scripts/run-bounded.mjs +53 -0
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 {
|
|
21104
|
-
import {
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
21253
|
-
|
|
21254
|
-
*
|
|
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
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
const
|
|
21272
|
-
if (
|
|
21273
|
-
|
|
21274
|
-
|
|
21275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21280
|
-
|
|
21281
|
-
|
|
21282
|
-
|
|
21283
|
-
|
|
21284
|
-
|
|
21285
|
-
|
|
21286
|
-
|
|
21287
|
-
|
|
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
|
-
|
|
21291
|
-
|
|
21292
|
-
|
|
21293
|
-
|
|
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
|
-
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
|
|
21301
|
-
|
|
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.
|
|
21313
|
-
|
|
21314
|
-
projectId
|
|
21315
|
-
|
|
21316
|
-
|
|
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
|
|
21324
|
-
|
|
21325
|
-
|
|
21326
|
-
|
|
21327
|
-
|
|
21328
|
-
|
|
21329
|
-
|
|
21330
|
-
|
|
21331
|
-
|
|
21332
|
-
|
|
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
|
|
23935
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
21974
23936
|
function kxmSha256(input) {
|
|
21975
|
-
return `sha256:${
|
|
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
|
|
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
|
|
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) &&
|
|
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
|
|
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 =
|
|
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
|
|
23170
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync4, statSync as
|
|
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
|
|
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) =>
|
|
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
|
|
23591
|
-
import { isAbsolute as
|
|
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 === "" || !
|
|
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 =
|
|
25567
|
+
realRoot = realpathSync5(lexicalRoot);
|
|
23606
25568
|
} catch {
|
|
23607
25569
|
return { ok: false, error: "artifact_unreadable", path: lexicalPath };
|
|
23608
25570
|
}
|
|
23609
25571
|
try {
|
|
23610
|
-
realPath =
|
|
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 =
|
|
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
|
|
26349
|
+
const envelope2 = { plan, projectLimits: kxmProjectAdmissionLimits(bundle), gates: pinnedGatesForCompiledPlan(plan, bundle, projectRoot) };
|
|
24388
26350
|
const prerequisites = [];
|
|
24389
|
-
const limit = unsupportedLimit(
|
|
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 =
|
|
24402
|
-
const unsupported = step.kind === "gate" ? unsupportedGateStep(plan, step,
|
|
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,
|
|
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(
|
|
24453
|
-
if (
|
|
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 (
|
|
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,
|
|
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 (
|
|
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 =
|
|
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) !==
|
|
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
|
|
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 (!
|
|
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 &&
|
|
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 &&
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
25565
|
-
import { createHash as
|
|
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:${
|
|
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 =
|
|
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 =
|
|
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-${
|
|
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 =
|
|
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
|
|
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
|
|
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("\\") && !
|
|
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}-${
|
|
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 (!
|
|
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_${
|
|
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 =
|
|
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}${
|
|
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}-${
|
|
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
|
|
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_${
|
|
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,
|
|
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
|
-
...
|
|
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", ...
|
|
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", ...
|
|
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, ...
|
|
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
|
|
30069
|
+
const envelope2 = call?.brief !== void 0 ? { brief: call.brief } : {};
|
|
28105
30070
|
try {
|
|
28106
|
-
const target = resolveKxmRunTarget(runtime, workflow, forTask,
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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, ...
|
|
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", ...
|
|
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)}
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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-${
|
|
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:${
|
|
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:${
|
|
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
|
|
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_${
|
|
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_${
|
|
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
|
|
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_${
|
|
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
|
|
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) :
|
|
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) :
|
|
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
|
-
|
|
38978
|
+
spawnSync6(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
37013
38979
|
}
|
|
37014
|
-
const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) :
|
|
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
|
|
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
|
|
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 =
|
|
39119
|
+
const result = spawnSync7("git", ["-C", cwd, ...args], {
|
|
37152
39120
|
encoding: "utf8",
|
|
37153
|
-
env:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 =
|
|
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 =
|
|
48803
|
+
const proc = spawnSync9("clip", { input: text, encoding: "utf8", windowsHide: true });
|
|
46742
48804
|
return proc.status === 0;
|
|
46743
48805
|
}
|
|
46744
|
-
const wl =
|
|
48806
|
+
const wl = spawnSync9("wl-copy", [text], { encoding: "utf8", windowsHide: true });
|
|
46745
48807
|
if (wl.status === 0) return true;
|
|
46746
|
-
const xclip =
|
|
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 =
|
|
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
|
|
47539
|
-
import { randomUUID as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.${
|
|
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
|
|
50653
|
+
const envelope2 = JSON.parse(readFileSync35(join48(stateDir, file), "utf8"));
|
|
48592
50654
|
recoveries.push({
|
|
48593
50655
|
file,
|
|
48594
|
-
reason:
|
|
48595
|
-
agentName:
|
|
48596
|
-
project:
|
|
48597
|
-
createdAt:
|
|
48598
|
-
runId:
|
|
48599
|
-
stageId:
|
|
48600
|
-
freshSession:
|
|
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_${
|
|
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
|
|
48815
|
-
import { createHash as
|
|
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
|
|
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 =
|
|
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
|
|
49672
|
-
import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync13, rmSync as rmSync12, statSync as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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
|
|
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 &&
|
|
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
|
|
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:
|
|
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 =
|
|
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) ?
|
|
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",
|