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