@kontextmind/kxm 0.7.126 → 0.7.127
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/roles/planner.yaml +6 -7
- package/.kxm/roles/reviewer-arch.yaml +6 -7
- package/.kxm/roles/reviewer-cli.yaml +6 -7
- package/.kxm/roles/writer.yaml +8 -17
- package/CHANGELOG.md +17 -0
- package/docs/architecture/access.md +2 -4
- package/docs/contracts/validation.md +1 -1
- package/docs/contributing/harness-routing-internals.md +1 -1
- package/docs/contributing/test-matrix.md +1 -1
- package/docs/glossary.md +1 -1
- package/docs/guides/agent-skills.md +1 -1
- package/docs/operations/backup-and-restore.md +2 -2
- package/docs/reference/cli-reference.md +31 -74
- package/docs/reference/config-reference.md +83 -118
- package/docs/reference/harness-routing.md +15 -8
- package/examples/project/.kxm/models/critic-claude.yaml +6 -2
- package/examples/project/.kxm/models/critic-gemini.yaml +6 -2
- package/examples/project/.kxm/models/critic-grok.yaml +6 -2
- package/examples/project/.kxm/models/implementation.yaml +6 -2
- package/examples/project/.kxm/models/primary.yaml +6 -2
- package/examples/project/.kxm/roles/planner.yaml +8 -0
- package/examples/project/.kxm/roles/reviewer-arch.yaml +8 -0
- package/examples/project/.kxm/roles/reviewer-cli.yaml +8 -0
- package/examples/project/.kxm/roles/writer.yaml +8 -0
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +1759 -1306
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +994 -245
- package/plugins/kxm/dist/runtime.js +1018 -269
- package/plugins/kxm/dist/server.js +55 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +3 -7
- package/plugins/kxm/src/autocomplete.ts +1 -1
- package/plugins/kxm/src/cli/project.ts +7 -1
- package/plugins/kxm/src/cli/roles.ts +46 -150
- package/plugins/kxm/src/cli.ts +8 -22
- package/plugins/kxm/src/engine.ts +24 -1
- package/plugins/kxm/src/init-guide-setup.ts +38 -4
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/permission.ts +11 -0
- package/plugins/kxm/src/policy-draft.mjs +56 -16
- package/plugins/kxm/src/project-config.ts +121 -10
- package/plugins/kxm/src/repair.ts +1 -0
- package/plugins/kxm/src/role.ts +40 -440
- package/plugins/kxm/src/routes.ts +28 -5
- package/plugins/kxm/src/template.ts +34 -1
- package/schemas/README.md +2 -1
- package/schemas/model.schema.json +111 -13
- package/schemas/role.schema.json +61 -31
- package/schemas/policy-draft/README.md +0 -17
- package/schemas/policy-draft/model.v2.schema.json +0 -140
- package/schemas/policy-draft/role.v2.schema.json +0 -91
|
@@ -163,10 +163,10 @@ var require_code = __commonJS({
|
|
|
163
163
|
function interpolate(x) {
|
|
164
164
|
return typeof x == "number" || typeof x == "boolean" || x === null ? x : safeStringify(Array.isArray(x) ? x.join(",") : x);
|
|
165
165
|
}
|
|
166
|
-
function
|
|
166
|
+
function stringify7(x) {
|
|
167
167
|
return new _Code(safeStringify(x));
|
|
168
168
|
}
|
|
169
|
-
exports.stringify =
|
|
169
|
+
exports.stringify = stringify7;
|
|
170
170
|
function safeStringify(x) {
|
|
171
171
|
return JSON.stringify(x).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
172
172
|
}
|
|
@@ -3834,7 +3834,7 @@ var require_fast_uri = __commonJS({
|
|
|
3834
3834
|
normalizeString(uri, options);
|
|
3835
3835
|
} else if (typeof uri === "object") {
|
|
3836
3836
|
uri = /** @type {T} */
|
|
3837
|
-
|
|
3837
|
+
parse10(serialize(uri, options), options);
|
|
3838
3838
|
}
|
|
3839
3839
|
return uri;
|
|
3840
3840
|
}
|
|
@@ -3874,8 +3874,8 @@ var require_fast_uri = __commonJS({
|
|
|
3874
3874
|
function resolveComponent(base, relative6, options, skipNormalization) {
|
|
3875
3875
|
const target = {};
|
|
3876
3876
|
if (!skipNormalization) {
|
|
3877
|
-
base =
|
|
3878
|
-
relative6 =
|
|
3877
|
+
base = parse10(serialize(base, options), options);
|
|
3878
|
+
relative6 = parse10(serialize(relative6, options), options);
|
|
3879
3879
|
}
|
|
3880
3880
|
options = options || {};
|
|
3881
3881
|
if (!options.tolerant && relative6.scheme) {
|
|
@@ -4167,7 +4167,7 @@ var require_fast_uri = __commonJS({
|
|
|
4167
4167
|
}
|
|
4168
4168
|
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
|
|
4169
4169
|
}
|
|
4170
|
-
function
|
|
4170
|
+
function parse10(uri, opts) {
|
|
4171
4171
|
return parseWithStatus(uri, opts).parsed;
|
|
4172
4172
|
}
|
|
4173
4173
|
function normalizeString(uri, opts) {
|
|
@@ -4204,7 +4204,7 @@ var require_fast_uri = __commonJS({
|
|
|
4204
4204
|
resolveComponent,
|
|
4205
4205
|
equal,
|
|
4206
4206
|
serialize,
|
|
4207
|
-
parse:
|
|
4207
|
+
parse: parse10
|
|
4208
4208
|
};
|
|
4209
4209
|
module.exports = fastUri;
|
|
4210
4210
|
module.exports.default = fastUri;
|
|
@@ -6943,8 +6943,8 @@ var require_discriminator = __commonJS({
|
|
|
6943
6943
|
if (!tagRequired)
|
|
6944
6944
|
throw new Error(`discriminator: "${tagName}" must be required`);
|
|
6945
6945
|
return oneOfMapping;
|
|
6946
|
-
function hasRequired({ required }) {
|
|
6947
|
-
return Array.isArray(
|
|
6946
|
+
function hasRequired({ required: required2 }) {
|
|
6947
|
+
return Array.isArray(required2) && required2.includes(tagName);
|
|
6948
6948
|
}
|
|
6949
6949
|
function addMappings(sch, i) {
|
|
6950
6950
|
if (sch.const) {
|
|
@@ -8917,7 +8917,7 @@ var require_stringify = __commonJS({
|
|
|
8917
8917
|
props.push(doc.directives.tagString(tag));
|
|
8918
8918
|
return props.join(" ");
|
|
8919
8919
|
}
|
|
8920
|
-
function
|
|
8920
|
+
function stringify7(item, ctx, onComment, onChompKeep) {
|
|
8921
8921
|
if (identity.isPair(item))
|
|
8922
8922
|
return item.toString(ctx, onComment, onChompKeep);
|
|
8923
8923
|
if (identity.isAlias(item)) {
|
|
@@ -8946,7 +8946,7 @@ var require_stringify = __commonJS({
|
|
|
8946
8946
|
${ctx.indent}${str}`;
|
|
8947
8947
|
}
|
|
8948
8948
|
exports.createStringifyContext = createStringifyContext;
|
|
8949
|
-
exports.stringify =
|
|
8949
|
+
exports.stringify = stringify7;
|
|
8950
8950
|
}
|
|
8951
8951
|
});
|
|
8952
8952
|
|
|
@@ -8956,7 +8956,7 @@ var require_stringifyPair = __commonJS({
|
|
|
8956
8956
|
"use strict";
|
|
8957
8957
|
var identity = require_identity();
|
|
8958
8958
|
var Scalar = require_Scalar();
|
|
8959
|
-
var
|
|
8959
|
+
var stringify7 = require_stringify();
|
|
8960
8960
|
var stringifyComment = require_stringifyComment();
|
|
8961
8961
|
function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
|
|
8962
8962
|
const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
|
|
@@ -8978,7 +8978,7 @@ var require_stringifyPair = __commonJS({
|
|
|
8978
8978
|
});
|
|
8979
8979
|
let keyCommentDone = false;
|
|
8980
8980
|
let chompKeep = false;
|
|
8981
|
-
let str =
|
|
8981
|
+
let str = stringify7.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
|
|
8982
8982
|
if (!explicitKey && !ctx.inFlow && str.length > 1024) {
|
|
8983
8983
|
if (simpleKeys)
|
|
8984
8984
|
throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
|
|
@@ -9030,7 +9030,7 @@ ${indent}:`;
|
|
|
9030
9030
|
ctx.indent = ctx.indent.substring(2);
|
|
9031
9031
|
}
|
|
9032
9032
|
let valueCommentDone = false;
|
|
9033
|
-
const valueStr =
|
|
9033
|
+
const valueStr = stringify7.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
|
|
9034
9034
|
let ws = " ";
|
|
9035
9035
|
if (keyComment || vsb || vcb) {
|
|
9036
9036
|
ws = vsb ? "\n" : "";
|
|
@@ -9171,7 +9171,7 @@ var require_addPairToJSMap = __commonJS({
|
|
|
9171
9171
|
"use strict";
|
|
9172
9172
|
var log = require_log();
|
|
9173
9173
|
var merge = require_merge();
|
|
9174
|
-
var
|
|
9174
|
+
var stringify7 = require_stringify();
|
|
9175
9175
|
var identity = require_identity();
|
|
9176
9176
|
var toJS = require_toJS();
|
|
9177
9177
|
function addPairToJSMap(ctx, map, { key, value }) {
|
|
@@ -9207,7 +9207,7 @@ var require_addPairToJSMap = __commonJS({
|
|
|
9207
9207
|
if (typeof jsKey !== "object")
|
|
9208
9208
|
return String(jsKey);
|
|
9209
9209
|
if (identity.isNode(key) && ctx?.doc) {
|
|
9210
|
-
const strCtx =
|
|
9210
|
+
const strCtx = stringify7.createStringifyContext(ctx.doc, {});
|
|
9211
9211
|
strCtx.anchors = /* @__PURE__ */ new Set();
|
|
9212
9212
|
for (const node of ctx.anchors.keys())
|
|
9213
9213
|
strCtx.anchors.add(node.anchor);
|
|
@@ -9274,12 +9274,12 @@ var require_stringifyCollection = __commonJS({
|
|
|
9274
9274
|
"node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
|
|
9275
9275
|
"use strict";
|
|
9276
9276
|
var identity = require_identity();
|
|
9277
|
-
var
|
|
9277
|
+
var stringify7 = require_stringify();
|
|
9278
9278
|
var stringifyComment = require_stringifyComment();
|
|
9279
9279
|
function stringifyCollection(collection, ctx, options) {
|
|
9280
9280
|
const flow = ctx.inFlow ?? collection.flow;
|
|
9281
|
-
const
|
|
9282
|
-
return
|
|
9281
|
+
const stringify8 = flow ? stringifyFlowCollection : stringifyBlockCollection;
|
|
9282
|
+
return stringify8(collection, ctx, options);
|
|
9283
9283
|
}
|
|
9284
9284
|
function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
|
|
9285
9285
|
const { indent, options: { commentString } } = ctx;
|
|
@@ -9304,7 +9304,7 @@ var require_stringifyCollection = __commonJS({
|
|
|
9304
9304
|
}
|
|
9305
9305
|
}
|
|
9306
9306
|
chompKeep = false;
|
|
9307
|
-
let str2 =
|
|
9307
|
+
let str2 = stringify7.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
|
|
9308
9308
|
if (comment2)
|
|
9309
9309
|
str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
|
|
9310
9310
|
if (chompKeep && comment2)
|
|
@@ -9371,7 +9371,7 @@ ${indent}${line}` : "\n";
|
|
|
9371
9371
|
}
|
|
9372
9372
|
if (comment)
|
|
9373
9373
|
reqNewline = true;
|
|
9374
|
-
let str =
|
|
9374
|
+
let str = stringify7.stringify(item, itemCtx, () => comment = null);
|
|
9375
9375
|
reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
|
|
9376
9376
|
if (i < items.length - 1) {
|
|
9377
9377
|
str += ",";
|
|
@@ -10732,7 +10732,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
10732
10732
|
"node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
|
|
10733
10733
|
"use strict";
|
|
10734
10734
|
var identity = require_identity();
|
|
10735
|
-
var
|
|
10735
|
+
var stringify7 = require_stringify();
|
|
10736
10736
|
var stringifyComment = require_stringifyComment();
|
|
10737
10737
|
function stringifyDocument(doc, options) {
|
|
10738
10738
|
const lines = [];
|
|
@@ -10747,7 +10747,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
10747
10747
|
}
|
|
10748
10748
|
if (hasDirectives)
|
|
10749
10749
|
lines.push("---");
|
|
10750
|
-
const ctx =
|
|
10750
|
+
const ctx = stringify7.createStringifyContext(doc, options);
|
|
10751
10751
|
const { commentString } = ctx.options;
|
|
10752
10752
|
if (doc.commentBefore) {
|
|
10753
10753
|
if (lines.length !== 1)
|
|
@@ -10769,7 +10769,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
10769
10769
|
contentComment = doc.contents.comment;
|
|
10770
10770
|
}
|
|
10771
10771
|
const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
|
|
10772
|
-
let body =
|
|
10772
|
+
let body = stringify7.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
|
|
10773
10773
|
if (contentComment)
|
|
10774
10774
|
body += stringifyComment.lineComment(body, "", commentString(contentComment));
|
|
10775
10775
|
if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
|
|
@@ -10777,7 +10777,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
10777
10777
|
} else
|
|
10778
10778
|
lines.push(body);
|
|
10779
10779
|
} else {
|
|
10780
|
-
lines.push(
|
|
10780
|
+
lines.push(stringify7.stringify(doc.contents, ctx));
|
|
10781
10781
|
}
|
|
10782
10782
|
if (doc.directives?.docEnd) {
|
|
10783
10783
|
if (doc.comment) {
|
|
@@ -12913,7 +12913,7 @@ var require_cst_scalar = __commonJS({
|
|
|
12913
12913
|
var require_cst_stringify = __commonJS({
|
|
12914
12914
|
"node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
|
|
12915
12915
|
"use strict";
|
|
12916
|
-
var
|
|
12916
|
+
var stringify7 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
|
|
12917
12917
|
function stringifyToken(token) {
|
|
12918
12918
|
switch (token.type) {
|
|
12919
12919
|
case "block-scalar": {
|
|
@@ -12966,7 +12966,7 @@ var require_cst_stringify = __commonJS({
|
|
|
12966
12966
|
res += stringifyToken(value);
|
|
12967
12967
|
return res;
|
|
12968
12968
|
}
|
|
12969
|
-
exports.stringify =
|
|
12969
|
+
exports.stringify = stringify7;
|
|
12970
12970
|
}
|
|
12971
12971
|
});
|
|
12972
12972
|
|
|
@@ -14658,7 +14658,7 @@ var require_public_api = __commonJS({
|
|
|
14658
14658
|
return docs;
|
|
14659
14659
|
return Object.assign([], { empty: true }, composer$1.streamInfo());
|
|
14660
14660
|
}
|
|
14661
|
-
function
|
|
14661
|
+
function parseDocument3(source, options = {}) {
|
|
14662
14662
|
const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options);
|
|
14663
14663
|
const parser$1 = new parser.Parser(lineCounter2?.addNewLine);
|
|
14664
14664
|
const composer$1 = new composer.Composer(options);
|
|
@@ -14677,14 +14677,14 @@ var require_public_api = __commonJS({
|
|
|
14677
14677
|
}
|
|
14678
14678
|
return doc;
|
|
14679
14679
|
}
|
|
14680
|
-
function
|
|
14680
|
+
function parse10(src, reviver, options) {
|
|
14681
14681
|
let _reviver = void 0;
|
|
14682
14682
|
if (typeof reviver === "function") {
|
|
14683
14683
|
_reviver = reviver;
|
|
14684
14684
|
} else if (options === void 0 && reviver && typeof reviver === "object") {
|
|
14685
14685
|
options = reviver;
|
|
14686
14686
|
}
|
|
14687
|
-
const doc =
|
|
14687
|
+
const doc = parseDocument3(src, options);
|
|
14688
14688
|
if (!doc)
|
|
14689
14689
|
return null;
|
|
14690
14690
|
doc.warnings.forEach((warning) => log.warn(doc.options.logLevel, warning));
|
|
@@ -14696,7 +14696,7 @@ var require_public_api = __commonJS({
|
|
|
14696
14696
|
}
|
|
14697
14697
|
return doc.toJS(Object.assign({ reviver: _reviver }, options));
|
|
14698
14698
|
}
|
|
14699
|
-
function
|
|
14699
|
+
function stringify7(value, replacer, options) {
|
|
14700
14700
|
let _replacer = null;
|
|
14701
14701
|
if (typeof replacer === "function" || Array.isArray(replacer)) {
|
|
14702
14702
|
_replacer = replacer;
|
|
@@ -14718,10 +14718,10 @@ var require_public_api = __commonJS({
|
|
|
14718
14718
|
return value.toString(options);
|
|
14719
14719
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
14720
14720
|
}
|
|
14721
|
-
exports.parse =
|
|
14721
|
+
exports.parse = parse10;
|
|
14722
14722
|
exports.parseAllDocuments = parseAllDocuments;
|
|
14723
|
-
exports.parseDocument =
|
|
14724
|
-
exports.stringify =
|
|
14723
|
+
exports.parseDocument = parseDocument3;
|
|
14724
|
+
exports.stringify = stringify7;
|
|
14725
14725
|
}
|
|
14726
14726
|
});
|
|
14727
14727
|
|
|
@@ -14778,14 +14778,14 @@ var require_dist = __commonJS({
|
|
|
14778
14778
|
});
|
|
14779
14779
|
|
|
14780
14780
|
// plugins/kxm/src/runtime-service.ts
|
|
14781
|
-
import { createHash as
|
|
14781
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
14782
14782
|
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
14783
14783
|
import { join as join7 } from "node:path";
|
|
14784
14784
|
|
|
14785
14785
|
// plugins/kxm/src/project-config.ts
|
|
14786
14786
|
var import__ = __toESM(require__(), 1);
|
|
14787
14787
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14788
|
-
import { createHash as
|
|
14788
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
14789
14789
|
import { existsSync as existsSync3, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
14790
14790
|
import { basename, dirname as dirname2, extname, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
|
|
14791
14791
|
|
|
@@ -14914,14 +14914,598 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
|
|
|
14914
14914
|
return value;
|
|
14915
14915
|
}
|
|
14916
14916
|
|
|
14917
|
+
// plugins/kxm/src/project-config.ts
|
|
14918
|
+
import { pathToFileURL } from "node:url";
|
|
14919
|
+
|
|
14920
|
+
// plugins/kxm/src/policy-draft.mjs
|
|
14921
|
+
import { createHash } from "node:crypto";
|
|
14922
|
+
var POLICY_DRAFT_MODEL_SCHEMA = "kxm.model.v2";
|
|
14923
|
+
var POLICY_DRAFT_ROLE_SCHEMA = "kxm.role.v2";
|
|
14924
|
+
var POLICY_DRAFT_PURPOSES = Object.freeze([
|
|
14925
|
+
"writer",
|
|
14926
|
+
"planner",
|
|
14927
|
+
"reviewer-arch",
|
|
14928
|
+
"reviewer-cli",
|
|
14929
|
+
"experiment"
|
|
14930
|
+
]);
|
|
14931
|
+
var POLICY_DRAFT_PERMISSIONS = Object.freeze(["edit", "read-only"]);
|
|
14932
|
+
var POLICY_DRAFT_STATUSES = Object.freeze(["admitted", "candidate", "retired"]);
|
|
14933
|
+
var IDENTIFIER = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
14934
|
+
var ROUTE_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
14935
|
+
var SHA256 = /^[a-f0-9]{64}$/;
|
|
14936
|
+
var TOKEN = /[\s\x00-\x1f]/u;
|
|
14937
|
+
var EFFORTS = Object.freeze(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
14938
|
+
var MODES = Object.freeze(["headless", "interactive", "either"]);
|
|
14939
|
+
var MODEL_KEYS = Object.freeze([
|
|
14940
|
+
"schema",
|
|
14941
|
+
"id",
|
|
14942
|
+
"harness",
|
|
14943
|
+
"model",
|
|
14944
|
+
"vendor",
|
|
14945
|
+
"status",
|
|
14946
|
+
"permissions",
|
|
14947
|
+
"origin",
|
|
14948
|
+
"thinking",
|
|
14949
|
+
"tags",
|
|
14950
|
+
"capabilities",
|
|
14951
|
+
"priority",
|
|
14952
|
+
"fallbacks",
|
|
14953
|
+
"limits"
|
|
14954
|
+
]);
|
|
14955
|
+
var ROLE_KEYS = Object.freeze([
|
|
14956
|
+
"schema",
|
|
14957
|
+
"id",
|
|
14958
|
+
"purpose",
|
|
14959
|
+
"permission",
|
|
14960
|
+
"description",
|
|
14961
|
+
"extends",
|
|
14962
|
+
"roster",
|
|
14963
|
+
"skills",
|
|
14964
|
+
"tools",
|
|
14965
|
+
"produces",
|
|
14966
|
+
"consumes",
|
|
14967
|
+
"policy"
|
|
14968
|
+
]);
|
|
14969
|
+
var ORIGIN_KEYS = Object.freeze(["source", "sha256"]);
|
|
14970
|
+
var LIMIT_KEYS = Object.freeze(["contextTokens", "outputTokens", "timeoutMs"]);
|
|
14971
|
+
var TOOL_KEYS = Object.freeze(["preset", "allow", "deny"]);
|
|
14972
|
+
var TEMPLATE_KEYS = Object.freeze(["template", "schema"]);
|
|
14973
|
+
var POLICY_KEYS = Object.freeze(["vendorIndependenceRequired", "maxTransitions", "requiresGateVerification", "fallback"]);
|
|
14974
|
+
var FALLBACK_KEYS = Object.freeze(["onError", "maxSwitches", "revert"]);
|
|
14975
|
+
var FALLBACK_ERRORS = Object.freeze(["rate_limit", "transport", "provider_unavailable"]);
|
|
14976
|
+
var FALLBACK_REVERT = Object.freeze(["next_run", "never"]);
|
|
14977
|
+
var ROSTER_ENTRY_KEYS = Object.freeze(["route", "effort", "mode"]);
|
|
14978
|
+
var CRITIC_PURPOSES = Object.freeze(["reviewer-arch", "reviewer-cli"]);
|
|
14979
|
+
function compareCodeUnits2(left, right) {
|
|
14980
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
14981
|
+
}
|
|
14982
|
+
function sortIssues2(issues) {
|
|
14983
|
+
return [...issues].sort((left, right) => compareCodeUnits2(left.file, right.file) || compareCodeUnits2(left.phase, right.phase) || compareCodeUnits2(left.code, right.code) || compareCodeUnits2(left.message, right.message));
|
|
14984
|
+
}
|
|
14985
|
+
function issue2(phase, code, file, message) {
|
|
14986
|
+
return { phase, code, file, message };
|
|
14987
|
+
}
|
|
14988
|
+
function isObject(value) {
|
|
14989
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
14990
|
+
}
|
|
14991
|
+
function own(object, key) {
|
|
14992
|
+
return Object.hasOwn(object, key);
|
|
14993
|
+
}
|
|
14994
|
+
function extraKeys(value, allowed) {
|
|
14995
|
+
return Object.keys(value).filter((key) => !allowed.includes(key));
|
|
14996
|
+
}
|
|
14997
|
+
function freezeDeep(value) {
|
|
14998
|
+
if (value && typeof value === "object") {
|
|
14999
|
+
for (const child of Object.values(value)) freezeDeep(child);
|
|
15000
|
+
Object.freeze(value);
|
|
15001
|
+
}
|
|
15002
|
+
return value;
|
|
15003
|
+
}
|
|
15004
|
+
function cloneJson(value) {
|
|
15005
|
+
return JSON.parse(JSON.stringify(value));
|
|
15006
|
+
}
|
|
15007
|
+
function sha256Bytes(input) {
|
|
15008
|
+
const bytes = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(input);
|
|
15009
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
15010
|
+
}
|
|
15011
|
+
function canonicalVendor(value, aliases) {
|
|
15012
|
+
if (typeof value !== "string") return "";
|
|
15013
|
+
const lower = value.toLowerCase();
|
|
15014
|
+
return own(aliases, lower) ? aliases[lower] : lower;
|
|
15015
|
+
}
|
|
15016
|
+
function identifier(value, max = 64) {
|
|
15017
|
+
return typeof value === "string" && value.length >= 1 && value.length <= max && IDENTIFIER.test(value);
|
|
15018
|
+
}
|
|
15019
|
+
function routeId(value) {
|
|
15020
|
+
return typeof value === "string" && value.length >= 1 && value.length <= 64 && ROUTE_ID.test(value);
|
|
15021
|
+
}
|
|
15022
|
+
function nonemptyToken(value, max = 512) {
|
|
15023
|
+
return typeof value === "string" && value.length >= 1 && value.length <= max && !TOKEN.test(value);
|
|
15024
|
+
}
|
|
15025
|
+
function codePointCount(value) {
|
|
15026
|
+
return [...value].length;
|
|
15027
|
+
}
|
|
15028
|
+
function sourcePath(value) {
|
|
15029
|
+
if (!nonemptyToken(value)) return false;
|
|
15030
|
+
if (value.startsWith("/") || value.includes("\\")) return false;
|
|
15031
|
+
const parts = value.split("/");
|
|
15032
|
+
return parts.length > 0 && parts.every((part) => part && part !== "." && part !== "..");
|
|
15033
|
+
}
|
|
15034
|
+
function uniqueStringList(value, allowed, { min = 1, max = 64 } = {}) {
|
|
15035
|
+
if (!Array.isArray(value) || value.length < min || value.length > max) return false;
|
|
15036
|
+
if (new Set(value).size !== value.length) return false;
|
|
15037
|
+
return value.every((item) => allowed.includes(item));
|
|
15038
|
+
}
|
|
15039
|
+
function identifierList(value, { maxItems = 32, itemMax = 64 } = {}) {
|
|
15040
|
+
if (!Array.isArray(value) || value.length > maxItems) return false;
|
|
15041
|
+
if (new Set(value).size !== value.length) return false;
|
|
15042
|
+
return value.every((item) => identifier(item, itemMax));
|
|
15043
|
+
}
|
|
15044
|
+
function parseDocument2(value, label, issues) {
|
|
15045
|
+
if (typeof value === "string" || value instanceof Uint8Array) {
|
|
15046
|
+
try {
|
|
15047
|
+
return parseRestrictedYaml(value, label);
|
|
15048
|
+
} catch (error) {
|
|
15049
|
+
if (error instanceof RestrictedYamlError) issues.push(...error.issues);
|
|
15050
|
+
else issues.push(issue2("parse", "invalid_yaml", label, error instanceof Error ? error.message : "invalid YAML"));
|
|
15051
|
+
return void 0;
|
|
15052
|
+
}
|
|
15053
|
+
}
|
|
15054
|
+
if (isObject(value)) return value;
|
|
15055
|
+
issues.push(issue2("schema", "root_not_object", label, "resource root must be a mapping"));
|
|
15056
|
+
return void 0;
|
|
15057
|
+
}
|
|
15058
|
+
function collectDocuments(raw, kind, issues) {
|
|
15059
|
+
const collected = [];
|
|
15060
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15061
|
+
if (raw === void 0) return collected;
|
|
15062
|
+
if (Array.isArray(raw)) {
|
|
15063
|
+
for (const [index, value] of raw.entries()) {
|
|
15064
|
+
const label = `${kind}[${index}]`;
|
|
15065
|
+
const document = parseDocument2(value, label, issues);
|
|
15066
|
+
if (!document) continue;
|
|
15067
|
+
const declared = document.id;
|
|
15068
|
+
if (!routeId(declared) && !(kind === "roles" && identifier(declared))) {
|
|
15069
|
+
issues.push(issue2("schema", "missing_id", label, `${kind.slice(0, -1)} identity is missing or invalid`));
|
|
15070
|
+
continue;
|
|
15071
|
+
}
|
|
15072
|
+
if (seen.has(declared)) {
|
|
15073
|
+
issues.push(issue2("schema", kind === "models" ? "duplicate_route_id" : "duplicate_role_id", label, `duplicate ${kind.slice(0, -1)} identity ${declared}`));
|
|
15074
|
+
continue;
|
|
15075
|
+
}
|
|
15076
|
+
seen.add(declared);
|
|
15077
|
+
collected.push({ id: declared, label: `${kind}/${declared}`, document });
|
|
15078
|
+
}
|
|
15079
|
+
return collected;
|
|
15080
|
+
}
|
|
15081
|
+
if (!isObject(raw)) {
|
|
15082
|
+
issues.push(issue2("schema", "root_not_object", kind, `${kind} must be a mapping or array`));
|
|
15083
|
+
return collected;
|
|
15084
|
+
}
|
|
15085
|
+
for (const id of Object.keys(raw).sort(compareCodeUnits2)) {
|
|
15086
|
+
const label = `${kind}/${id}`;
|
|
15087
|
+
if (kind === "models" ? !routeId(id) : !identifier(id)) {
|
|
15088
|
+
issues.push(issue2("schema", "invalid_id", label, `invalid ${kind.slice(0, -1)} identity`));
|
|
15089
|
+
continue;
|
|
15090
|
+
}
|
|
15091
|
+
if (seen.has(id)) {
|
|
15092
|
+
issues.push(issue2("schema", kind === "models" ? "duplicate_route_id" : "duplicate_role_id", label, `duplicate ${kind.slice(0, -1)} identity ${id}`));
|
|
15093
|
+
continue;
|
|
15094
|
+
}
|
|
15095
|
+
seen.add(id);
|
|
15096
|
+
const document = parseDocument2(raw[id], label, issues);
|
|
15097
|
+
if (!document) continue;
|
|
15098
|
+
if (document.id !== void 0 && document.id !== id) {
|
|
15099
|
+
issues.push(issue2("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
|
|
15100
|
+
continue;
|
|
15101
|
+
}
|
|
15102
|
+
collected.push({ id, label, document });
|
|
15103
|
+
}
|
|
15104
|
+
return collected;
|
|
15105
|
+
}
|
|
15106
|
+
function closedObject(value, allowed, label, issues, code = "schema_additionalProperties") {
|
|
15107
|
+
if (!isObject(value)) {
|
|
15108
|
+
issues.push(issue2("schema", "root_not_object", label, "value must be a mapping"));
|
|
15109
|
+
return false;
|
|
15110
|
+
}
|
|
15111
|
+
const unexpected = extraKeys(value, allowed);
|
|
15112
|
+
if (unexpected.length > 0) {
|
|
15113
|
+
issues.push(issue2("schema", code, label, `unexpected properties: ${unexpected.join(", ")}`));
|
|
15114
|
+
return false;
|
|
15115
|
+
}
|
|
15116
|
+
return true;
|
|
15117
|
+
}
|
|
15118
|
+
function required(value, keys, label, issues) {
|
|
15119
|
+
for (const key of keys) {
|
|
15120
|
+
if (!own(value, key)) issues.push(issue2("schema", "schema_required", label, `missing ${key}`));
|
|
15121
|
+
}
|
|
15122
|
+
}
|
|
15123
|
+
function validateOrigin(origin, label, issues) {
|
|
15124
|
+
if (!closedObject(origin, ORIGIN_KEYS, `${label}.origin`, issues)) return;
|
|
15125
|
+
required(origin, ORIGIN_KEYS, `${label}.origin`, issues);
|
|
15126
|
+
if (origin.source !== void 0 && !sourcePath(origin.source)) {
|
|
15127
|
+
issues.push(issue2("schema", "origin_source_invalid", `${label}.origin`, "origin source must be a contained relative path"));
|
|
15128
|
+
}
|
|
15129
|
+
if (origin.sha256 !== void 0 && (typeof origin.sha256 !== "string" || !SHA256.test(origin.sha256))) {
|
|
15130
|
+
issues.push(issue2("schema", "origin_hash_invalid", `${label}.origin`, "origin sha256 must be a 64-character hex digest"));
|
|
15131
|
+
}
|
|
15132
|
+
}
|
|
15133
|
+
function validateModelShape(document, id, label, issues) {
|
|
15134
|
+
if (!closedObject(document, MODEL_KEYS, label, issues)) return;
|
|
15135
|
+
required(document, ["schema", "harness", "model", "vendor", "status", "permissions"], label, issues);
|
|
15136
|
+
if (document.schema !== void 0 && document.schema !== POLICY_DRAFT_MODEL_SCHEMA) {
|
|
15137
|
+
issues.push(issue2("schema", "schema_identity_mismatch", label, `expected ${POLICY_DRAFT_MODEL_SCHEMA}, received ${String(document.schema)}`));
|
|
15138
|
+
}
|
|
15139
|
+
if (document.id !== void 0 && document.id !== id) {
|
|
15140
|
+
issues.push(issue2("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
|
|
15141
|
+
}
|
|
15142
|
+
if (document.harness !== void 0 && !identifier(document.harness)) {
|
|
15143
|
+
issues.push(issue2("schema", "schema_pattern", label, "harness must be an identifier"));
|
|
15144
|
+
}
|
|
15145
|
+
if (document.model !== void 0 && (typeof document.model !== "string" || codePointCount(document.model) < 1 || codePointCount(document.model) > 200)) {
|
|
15146
|
+
issues.push(issue2("schema", "schema_pattern", label, "model must be a nonempty string"));
|
|
15147
|
+
}
|
|
15148
|
+
if (document.vendor !== void 0 && !nonemptyToken(document.vendor, 64)) {
|
|
15149
|
+
issues.push(issue2("schema", "schema_pattern", label, "vendor must be a nonempty token"));
|
|
15150
|
+
}
|
|
15151
|
+
if (document.status !== void 0 && !POLICY_DRAFT_STATUSES.includes(document.status)) {
|
|
15152
|
+
issues.push(issue2("schema", "unsupported_status", label, "status must be admitted, candidate, or retired"));
|
|
15153
|
+
}
|
|
15154
|
+
if (document.permissions !== void 0 && !uniqueStringList(document.permissions, POLICY_DRAFT_PERMISSIONS, { min: 1, max: 2 })) {
|
|
15155
|
+
issues.push(issue2("schema", "schema_enum", label, "permissions must be unique edit and/or read-only"));
|
|
15156
|
+
}
|
|
15157
|
+
if (document.harness === "pi" && !own(document, "origin")) {
|
|
15158
|
+
issues.push(issue2("schema", "schema_required", label, "Pi routes require origin source and sha256"));
|
|
15159
|
+
}
|
|
15160
|
+
if (own(document, "origin")) validateOrigin(document.origin, label, issues);
|
|
15161
|
+
if (document.thinking !== void 0 && (typeof document.thinking !== "string" || document.thinking.length < 1 || document.thinking.length > 64)) {
|
|
15162
|
+
issues.push(issue2("schema", "schema_pattern", label, "thinking must be a short string"));
|
|
15163
|
+
}
|
|
15164
|
+
if (document.tags !== void 0 && !identifierList(document.tags)) {
|
|
15165
|
+
issues.push(issue2("schema", "schema_pattern", label, "tags must be unique identifiers"));
|
|
15166
|
+
}
|
|
15167
|
+
if (document.capabilities !== void 0 && !identifierList(document.capabilities)) {
|
|
15168
|
+
issues.push(issue2("schema", "schema_pattern", label, "capabilities must be unique identifiers"));
|
|
15169
|
+
}
|
|
15170
|
+
if (document.priority !== void 0 && (!Number.isInteger(document.priority) || document.priority < -1e4 || document.priority > 1e4)) {
|
|
15171
|
+
issues.push(issue2("schema", "schema_type", label, "priority must be an integer in range"));
|
|
15172
|
+
}
|
|
15173
|
+
if (document.fallbacks !== void 0) {
|
|
15174
|
+
if (!Array.isArray(document.fallbacks) || document.fallbacks.length > 8) {
|
|
15175
|
+
issues.push(issue2("schema", "schema_type", label, "fallbacks must be a bounded array"));
|
|
15176
|
+
} else {
|
|
15177
|
+
for (const [index, fallback] of document.fallbacks.entries()) {
|
|
15178
|
+
if (!isObject(fallback)) {
|
|
15179
|
+
issues.push(issue2("schema", "root_not_object", `${label}.fallbacks[${index}]`, "fallback must be a mapping"));
|
|
15180
|
+
continue;
|
|
15181
|
+
}
|
|
15182
|
+
const keys = Object.keys(fallback);
|
|
15183
|
+
const profile = keys.length === 1 && own(fallback, "profile") && identifier(fallback.profile);
|
|
15184
|
+
const tag = own(fallback, "tag") && identifier(fallback.tag) && extraKeys(fallback, ["tag", "capabilities"]).length === 0 && (fallback.capabilities === void 0 || identifierList(fallback.capabilities));
|
|
15185
|
+
const provider = own(fallback, "provider") && own(fallback, "model") && extraKeys(fallback, ["provider", "model"]).length === 0 && identifier(fallback.provider) && typeof fallback.model === "string" && codePointCount(fallback.model) >= 1 && codePointCount(fallback.model) <= 200;
|
|
15186
|
+
if (!profile && !tag && !provider) {
|
|
15187
|
+
issues.push(issue2("schema", "schema_oneOf", `${label}.fallbacks[${index}]`, "fallback does not match a retained v1 selector"));
|
|
15188
|
+
}
|
|
15189
|
+
}
|
|
15190
|
+
}
|
|
15191
|
+
}
|
|
15192
|
+
if (document.limits !== void 0) {
|
|
15193
|
+
if (!closedObject(document.limits, LIMIT_KEYS, `${label}.limits`, issues)) return;
|
|
15194
|
+
for (const key of ["contextTokens", "outputTokens"]) {
|
|
15195
|
+
if (document.limits[key] !== void 0 && (!Number.isInteger(document.limits[key]) || document.limits[key] < 1)) {
|
|
15196
|
+
issues.push(issue2("schema", "schema_type", `${label}.limits`, `${key} must be a positive integer`));
|
|
15197
|
+
}
|
|
15198
|
+
}
|
|
15199
|
+
if (document.limits.timeoutMs !== void 0 && (!Number.isInteger(document.limits.timeoutMs) || document.limits.timeoutMs < 0 || document.limits.timeoutMs > 31536e6)) {
|
|
15200
|
+
issues.push(issue2("schema", "schema_type", `${label}.limits`, "timeoutMs must be a non-negative integer between 0 and 31536000000"));
|
|
15201
|
+
}
|
|
15202
|
+
}
|
|
15203
|
+
}
|
|
15204
|
+
function validateTemplateList(value, label, issues) {
|
|
15205
|
+
if (!Array.isArray(value)) {
|
|
15206
|
+
issues.push(issue2("schema", "schema_type", label, "must be an array"));
|
|
15207
|
+
return;
|
|
15208
|
+
}
|
|
15209
|
+
for (const [index, entry] of value.entries()) {
|
|
15210
|
+
const path = `${label}[${index}]`;
|
|
15211
|
+
if (!closedObject(entry, TEMPLATE_KEYS, path, issues)) continue;
|
|
15212
|
+
if (typeof entry.template !== "string" || !entry.template) {
|
|
15213
|
+
issues.push(issue2("schema", "schema_required", path, "template is required"));
|
|
15214
|
+
}
|
|
15215
|
+
if (entry.schema !== void 0 && typeof entry.schema !== "string") {
|
|
15216
|
+
issues.push(issue2("schema", "schema_type", path, "schema must be a string"));
|
|
15217
|
+
}
|
|
15218
|
+
}
|
|
15219
|
+
}
|
|
15220
|
+
function validateRoleShape(document, id, label, issues) {
|
|
15221
|
+
if (!closedObject(document, ROLE_KEYS, label, issues)) return;
|
|
15222
|
+
required(document, ["schema", "purpose", "permission", "description", "roster"], label, issues);
|
|
15223
|
+
if (document.schema !== void 0 && document.schema !== POLICY_DRAFT_ROLE_SCHEMA) {
|
|
15224
|
+
issues.push(issue2("schema", "schema_identity_mismatch", label, `expected ${POLICY_DRAFT_ROLE_SCHEMA}, received ${String(document.schema)}`));
|
|
15225
|
+
}
|
|
15226
|
+
if (document.id !== void 0 && document.id !== id) {
|
|
15227
|
+
issues.push(issue2("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
|
|
15228
|
+
}
|
|
15229
|
+
if (document.extends !== void 0 && !identifier(document.extends)) {
|
|
15230
|
+
issues.push(issue2("schema", "schema_pattern", label, "extends must be a role identifier"));
|
|
15231
|
+
}
|
|
15232
|
+
if (document.purpose !== void 0 && !POLICY_DRAFT_PURPOSES.includes(document.purpose)) {
|
|
15233
|
+
issues.push(issue2("schema", "unsupported_purpose", label, "purpose must be a runner role label"));
|
|
15234
|
+
}
|
|
15235
|
+
if (document.permission !== void 0 && !POLICY_DRAFT_PERMISSIONS.includes(document.permission)) {
|
|
15236
|
+
issues.push(issue2("schema", "schema_enum", label, "permission must be edit or read-only"));
|
|
15237
|
+
}
|
|
15238
|
+
if (document.description !== void 0 && (typeof document.description !== "string" || document.description.length < 1 || document.description.length > 2e3)) {
|
|
15239
|
+
issues.push(issue2("schema", "schema_type", label, "description must be a bounded string"));
|
|
15240
|
+
}
|
|
15241
|
+
if (document.roster !== void 0) {
|
|
15242
|
+
if (!Array.isArray(document.roster) || document.roster.length > 32) {
|
|
15243
|
+
issues.push(issue2("schema", "schema_type", label, "roster must be a bounded array"));
|
|
15244
|
+
} else {
|
|
15245
|
+
const seenRoutes = /* @__PURE__ */ new Set();
|
|
15246
|
+
for (const [index, entry] of document.roster.entries()) {
|
|
15247
|
+
const path = `${label}.roster[${index}]`;
|
|
15248
|
+
if (!closedObject(entry, ROSTER_ENTRY_KEYS, path, issues)) continue;
|
|
15249
|
+
if (!routeId(entry.route)) {
|
|
15250
|
+
issues.push(issue2("schema", "schema_pattern", path, "roster route must be a route identity"));
|
|
15251
|
+
continue;
|
|
15252
|
+
}
|
|
15253
|
+
if (seenRoutes.has(entry.route)) {
|
|
15254
|
+
issues.push(issue2("schema", "duplicate_route_id", path, `duplicate roster route ${entry.route}`));
|
|
15255
|
+
}
|
|
15256
|
+
seenRoutes.add(entry.route);
|
|
15257
|
+
if (entry.effort !== void 0 && !EFFORTS.includes(entry.effort)) {
|
|
15258
|
+
issues.push(issue2("schema", "schema_enum", path, "effort is not a known helper effort"));
|
|
15259
|
+
}
|
|
15260
|
+
if (entry.mode !== void 0 && !MODES.includes(entry.mode)) {
|
|
15261
|
+
issues.push(issue2("schema", "schema_enum", path, "mode must be headless, interactive, or either"));
|
|
15262
|
+
}
|
|
15263
|
+
}
|
|
15264
|
+
}
|
|
15265
|
+
}
|
|
15266
|
+
if (document.skills !== void 0) {
|
|
15267
|
+
if (!Array.isArray(document.skills) || document.skills.length > 64 || document.skills.some((item) => typeof item !== "string" || item.length < 1 || item.length > 128)) {
|
|
15268
|
+
issues.push(issue2("schema", "schema_type", label, "skills must be bounded strings"));
|
|
15269
|
+
}
|
|
15270
|
+
}
|
|
15271
|
+
if (document.tools !== void 0) {
|
|
15272
|
+
if (!closedObject(document.tools, TOOL_KEYS, `${label}.tools`, issues)) return;
|
|
15273
|
+
if (document.tools.preset !== void 0 && typeof document.tools.preset !== "string") {
|
|
15274
|
+
issues.push(issue2("schema", "schema_type", `${label}.tools`, "preset must be a string"));
|
|
15275
|
+
}
|
|
15276
|
+
for (const key of ["allow", "deny"]) {
|
|
15277
|
+
if (document.tools[key] !== void 0 && (!Array.isArray(document.tools[key]) || document.tools[key].some((item) => typeof item !== "string"))) {
|
|
15278
|
+
issues.push(issue2("schema", "schema_type", `${label}.tools`, `${key} must be a string array`));
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
}
|
|
15282
|
+
if (document.produces !== void 0) validateTemplateList(document.produces, `${label}.produces`, issues);
|
|
15283
|
+
if (document.consumes !== void 0) validateTemplateList(document.consumes, `${label}.consumes`, issues);
|
|
15284
|
+
if (document.policy !== void 0) {
|
|
15285
|
+
if (!closedObject(document.policy, POLICY_KEYS, `${label}.policy`, issues)) return;
|
|
15286
|
+
if (document.policy.vendorIndependenceRequired !== void 0 && typeof document.policy.vendorIndependenceRequired !== "boolean") {
|
|
15287
|
+
issues.push(issue2("schema", "schema_type", `${label}.policy`, "vendorIndependenceRequired must be boolean"));
|
|
15288
|
+
}
|
|
15289
|
+
if (document.policy.requiresGateVerification !== void 0 && typeof document.policy.requiresGateVerification !== "boolean") {
|
|
15290
|
+
issues.push(issue2("schema", "schema_type", `${label}.policy`, "requiresGateVerification must be boolean"));
|
|
15291
|
+
}
|
|
15292
|
+
if (document.policy.maxTransitions !== void 0 && (!Number.isInteger(document.policy.maxTransitions) || document.policy.maxTransitions < 1)) {
|
|
15293
|
+
issues.push(issue2("schema", "schema_type", `${label}.policy`, "maxTransitions must be a positive integer"));
|
|
15294
|
+
}
|
|
15295
|
+
if (document.policy.fallback !== void 0) {
|
|
15296
|
+
const fallback = document.policy.fallback;
|
|
15297
|
+
const path = `${label}.policy.fallback`;
|
|
15298
|
+
if (!closedObject(fallback, FALLBACK_KEYS, path, issues)) return;
|
|
15299
|
+
if (fallback.onError !== void 0) {
|
|
15300
|
+
if (!Array.isArray(fallback.onError) || new Set(fallback.onError).size !== fallback.onError.length || fallback.onError.some((item) => !FALLBACK_ERRORS.includes(item))) {
|
|
15301
|
+
issues.push(issue2("schema", "schema_enum", path, "onError must be unique rate_limit, transport, or provider_unavailable values"));
|
|
15302
|
+
}
|
|
15303
|
+
}
|
|
15304
|
+
if (fallback.maxSwitches !== void 0 && (!Number.isInteger(fallback.maxSwitches) || fallback.maxSwitches < 0)) {
|
|
15305
|
+
issues.push(issue2("schema", "schema_type", path, "maxSwitches must be an integer >= 0"));
|
|
15306
|
+
}
|
|
15307
|
+
if (fallback.revert !== void 0 && !FALLBACK_REVERT.includes(fallback.revert)) {
|
|
15308
|
+
issues.push(issue2("schema", "schema_enum", path, "revert must be next_run or never"));
|
|
15309
|
+
}
|
|
15310
|
+
}
|
|
15311
|
+
}
|
|
15312
|
+
}
|
|
15313
|
+
function subset(values, allowed) {
|
|
15314
|
+
return values.every((value) => allowed.includes(value));
|
|
15315
|
+
}
|
|
15316
|
+
function validateModelSemantics(model, label, options, evidence, issues) {
|
|
15317
|
+
const aliases = options.vendorAliases ?? {};
|
|
15318
|
+
const ceiling = options.ceilings[model.harness];
|
|
15319
|
+
if (!ceiling) {
|
|
15320
|
+
issues.push(issue2("semantic", "unsupported_harness", label, `no code-owned ceiling for harness ${String(model.harness)}`));
|
|
15321
|
+
return;
|
|
15322
|
+
}
|
|
15323
|
+
const vendor = canonicalVendor(model.vendor, aliases);
|
|
15324
|
+
if (options.piAllowedProviders.includes(vendor)) {
|
|
15325
|
+
issues.push(issue2("semantic", "vendor_aggregator", label, "vendor must name the model vendor, not a billing aggregator"));
|
|
15326
|
+
}
|
|
15327
|
+
if (!subset(model.permissions, ceiling.permissions)) {
|
|
15328
|
+
issues.push(issue2("semantic", "permission_escalation", label, "route permissions exceed the harness ceiling"));
|
|
15329
|
+
}
|
|
15330
|
+
if (Array.isArray(ceiling.models) && ceiling.models.length > 0 && !ceiling.models.includes(model.model)) {
|
|
15331
|
+
issues.push(issue2("semantic", "model_not_in_ceiling", label, "exact model is not listed on the harness ceiling"));
|
|
15332
|
+
}
|
|
15333
|
+
if (model.harness === "pi") {
|
|
15334
|
+
const parts = typeof model.model === "string" ? model.model.split("/") : [];
|
|
15335
|
+
const nativeVendors = isObject(options.piNativeVendorProviders) ? options.piNativeVendorProviders : {};
|
|
15336
|
+
const provider = parts[0];
|
|
15337
|
+
const owned = own(nativeVendors, provider) ? canonicalVendor(nativeVendors[provider], aliases) : "";
|
|
15338
|
+
const minParts = owned ? 2 : 3;
|
|
15339
|
+
const maxParts = owned ? 2 : Number.POSITIVE_INFINITY;
|
|
15340
|
+
if (parts.length < minParts || parts.length > maxParts || parts.some((part) => !part) || !options.piAllowedProviders.includes(provider) || owned && !/^gemini-[a-z0-9.-]+$/.test(parts[1])) {
|
|
15341
|
+
issues.push(issue2("semantic", "unsupported_pi_model", label, "Pi model must use an allowlisted aggregator prefix"));
|
|
15342
|
+
} else if (owned) {
|
|
15343
|
+
if (owned !== vendor) {
|
|
15344
|
+
issues.push(issue2("semantic", "pi_native_vendor_forbidden", label, "native vendor cannot use Pi"));
|
|
15345
|
+
}
|
|
15346
|
+
} else {
|
|
15347
|
+
const prefix = canonicalVendor(parts[1], aliases);
|
|
15348
|
+
if (options.nativePiBrakeProviders.includes(prefix) || options.nativePiBrakeProviders.includes(vendor)) {
|
|
15349
|
+
issues.push(issue2("semantic", "pi_native_vendor_forbidden", label, "native vendor cannot use Pi"));
|
|
15350
|
+
}
|
|
15351
|
+
}
|
|
15352
|
+
} else if (vendor !== ceiling.provider || typeof model.model === "string" && model.model.includes("/")) {
|
|
15353
|
+
issues.push(issue2("semantic", "native_vendor_mismatch", label, "native route vendor or model does not match the harness ceiling"));
|
|
15354
|
+
}
|
|
15355
|
+
const origin = model.origin;
|
|
15356
|
+
if (isObject(origin) && typeof origin.source === "string" && typeof origin.sha256 === "string") {
|
|
15357
|
+
if (!own(evidence, origin.source)) {
|
|
15358
|
+
issues.push(issue2("semantic", "origin_evidence_missing", label, `missing evidence bytes for ${origin.source}`));
|
|
15359
|
+
} else if (sha256Bytes(evidence[origin.source]) !== origin.sha256) {
|
|
15360
|
+
issues.push(issue2("semantic", "origin_hash_mismatch", label, "origin evidence hash does not match supplied bytes"));
|
|
15361
|
+
}
|
|
15362
|
+
}
|
|
15363
|
+
}
|
|
15364
|
+
function admittedRoute(model) {
|
|
15365
|
+
return model.status === "admitted";
|
|
15366
|
+
}
|
|
15367
|
+
function validateRoleSemantics(role, id, label, models, options, issues) {
|
|
15368
|
+
if (!Array.isArray(role.roster)) return;
|
|
15369
|
+
for (const [index, entry] of role.roster.entries()) {
|
|
15370
|
+
const path = `${label}.roster[${index}]`;
|
|
15371
|
+
const model = models.get(entry.route);
|
|
15372
|
+
if (!model) {
|
|
15373
|
+
issues.push(issue2("reference", "route_unknown", path, `roster references unknown route ${entry.route}`));
|
|
15374
|
+
continue;
|
|
15375
|
+
}
|
|
15376
|
+
const ceiling = options.ceilings[model.harness];
|
|
15377
|
+
if (!ceiling) continue;
|
|
15378
|
+
if (Array.isArray(ceiling.roles) && ceiling.roles.length > 0 && !ceiling.roles.includes(role.purpose)) {
|
|
15379
|
+
issues.push(issue2("semantic", "purpose_ceiling_mismatch", path, `purpose ${role.purpose} is not allowed on harness ${model.harness}`));
|
|
15380
|
+
}
|
|
15381
|
+
if (!model.permissions.includes(role.permission)) {
|
|
15382
|
+
issues.push(issue2("semantic", "permission_escalation", path, "role permission is not granted by the route"));
|
|
15383
|
+
}
|
|
15384
|
+
if (!ceiling.permissions.includes(role.permission)) {
|
|
15385
|
+
issues.push(issue2("semantic", "permission_escalation", path, "role permission exceeds the harness ceiling"));
|
|
15386
|
+
}
|
|
15387
|
+
if (Array.isArray(ceiling.efforts) && ceiling.efforts.length > 0 && entry.effort !== void 0 && !ceiling.efforts.includes(entry.effort)) {
|
|
15388
|
+
issues.push(issue2("semantic", "effort_ceiling_mismatch", path, "effort is not allowed on the harness ceiling"));
|
|
15389
|
+
}
|
|
15390
|
+
if (!admittedRoute(model)) {
|
|
15391
|
+
issues.push(issue2("semantic", "route_not_admitted", path, `route ${entry.route} is ${model.status} and is not treated as admitted`));
|
|
15392
|
+
}
|
|
15393
|
+
if (model.harness === "pi") {
|
|
15394
|
+
if (role.permission === "edit" && !["writer", "experiment"].includes(role.purpose)) {
|
|
15395
|
+
issues.push(issue2("semantic", "permission_escalation", path, "Pi critic/planner cannot edit"));
|
|
15396
|
+
}
|
|
15397
|
+
if (role.purpose === "writer" && (model.permissions.length !== 1 || model.permissions[0] !== "edit")) {
|
|
15398
|
+
issues.push(issue2("semantic", "permission_escalation", path, "Pi writer requires edit permission only"));
|
|
15399
|
+
}
|
|
15400
|
+
}
|
|
15401
|
+
}
|
|
15402
|
+
if (id === role.purpose && CRITIC_PURPOSES.includes(role.purpose)) {
|
|
15403
|
+
const admitted = role.roster.map((entry) => models.get(entry.route)).filter((model) => model && admittedRoute(model));
|
|
15404
|
+
if (admitted.length !== 1) {
|
|
15405
|
+
issues.push(issue2("semantic", "critic_route_count", label, "required critic purpose files must list exactly one admitted route"));
|
|
15406
|
+
} else if (admitted[0].permissions.length !== 1 || admitted[0].permissions[0] !== "read-only" || role.permission !== "read-only") {
|
|
15407
|
+
issues.push(issue2("semantic", "permission_escalation", label, "critic purpose files must be read-only"));
|
|
15408
|
+
}
|
|
15409
|
+
}
|
|
15410
|
+
}
|
|
15411
|
+
function validateExtends(roles, issues) {
|
|
15412
|
+
for (const [id, role] of roles) {
|
|
15413
|
+
if (role.extends === void 0) continue;
|
|
15414
|
+
const label = `roles/${id}`;
|
|
15415
|
+
if (!roles.has(role.extends)) {
|
|
15416
|
+
issues.push(issue2("reference", "role_extends_unknown", label, `extends names unknown role ${role.extends}`));
|
|
15417
|
+
continue;
|
|
15418
|
+
}
|
|
15419
|
+
const seen = /* @__PURE__ */ new Set([id]);
|
|
15420
|
+
let current = role.extends;
|
|
15421
|
+
while (typeof current === "string" && current.length > 0) {
|
|
15422
|
+
if (seen.has(current)) {
|
|
15423
|
+
issues.push(issue2("semantic", "role_extends_cycle", label, `extends cycle: ${[...seen, current].join(" -> ")}`));
|
|
15424
|
+
break;
|
|
15425
|
+
}
|
|
15426
|
+
seen.add(current);
|
|
15427
|
+
current = roles.get(current)?.extends;
|
|
15428
|
+
}
|
|
15429
|
+
}
|
|
15430
|
+
}
|
|
15431
|
+
function validateCriticVendors(roles, models, options, issues) {
|
|
15432
|
+
const aliases = options.vendorAliases ?? {};
|
|
15433
|
+
const critics = [];
|
|
15434
|
+
for (const purpose of CRITIC_PURPOSES) {
|
|
15435
|
+
const role = roles.get(purpose);
|
|
15436
|
+
if (!role || role.purpose !== purpose) continue;
|
|
15437
|
+
const admitted = (role.roster ?? []).map((entry) => models.get(entry.route)).filter((model) => model && admittedRoute(model));
|
|
15438
|
+
if (admitted.length === 1) critics.push({ purpose, vendor: canonicalVendor(admitted[0].vendor, aliases) });
|
|
15439
|
+
}
|
|
15440
|
+
if (critics.length === 2 && critics[0].vendor === critics[1].vendor) {
|
|
15441
|
+
issues.push(issue2("semantic", "critic_vendor_collision", "roles/reviewer-arch", "required critics must have independent vendors"));
|
|
15442
|
+
}
|
|
15443
|
+
const writer = roles.get("writer");
|
|
15444
|
+
if (writer && critics.length > 0) {
|
|
15445
|
+
for (const entry of writer.roster ?? []) {
|
|
15446
|
+
const model = models.get(entry.route);
|
|
15447
|
+
if (!model || !admittedRoute(model)) continue;
|
|
15448
|
+
const vendor = canonicalVendor(model.vendor, aliases);
|
|
15449
|
+
if (critics.some((critic) => critic.vendor === vendor)) {
|
|
15450
|
+
issues.push(issue2("semantic", "writer_critic_vendor_collision", "roles/writer", "writer and critics must have independent vendors"));
|
|
15451
|
+
}
|
|
15452
|
+
}
|
|
15453
|
+
}
|
|
15454
|
+
}
|
|
15455
|
+
function validatePolicyDraft(input, options) {
|
|
15456
|
+
const issues = [];
|
|
15457
|
+
if (!isObject(input)) {
|
|
15458
|
+
return { ok: false, issues: [issue2("schema", "root_not_object", "<draft>", "draft input must be a mapping")] };
|
|
15459
|
+
}
|
|
15460
|
+
const unexpected = extraKeys(input, ["models", "roles", "evidence"]);
|
|
15461
|
+
if (unexpected.length > 0) {
|
|
15462
|
+
issues.push(issue2("schema", "schema_additionalProperties", "<draft>", `unexpected properties: ${unexpected.join(", ")}`));
|
|
15463
|
+
}
|
|
15464
|
+
if (!isObject(options) || !isObject(options.ceilings) || !Array.isArray(options.nativePiBrakeProviders) || !Array.isArray(options.piAllowedProviders)) {
|
|
15465
|
+
issues.push(issue2("semantic", "ceilings_required", "<options>", "code-owned ceilings and vendor brakes must be supplied explicitly"));
|
|
15466
|
+
return { ok: false, issues: sortIssues2(issues) };
|
|
15467
|
+
}
|
|
15468
|
+
const evidence = isObject(input.evidence) ? input.evidence : {};
|
|
15469
|
+
if (input.evidence !== void 0 && !isObject(input.evidence)) {
|
|
15470
|
+
issues.push(issue2("schema", "root_not_object", "evidence", "evidence must be a mapping of source path to bytes"));
|
|
15471
|
+
}
|
|
15472
|
+
const modelDocs = collectDocuments(input.models, "models", issues);
|
|
15473
|
+
const roleDocs = collectDocuments(input.roles, "roles", issues);
|
|
15474
|
+
for (const entry of modelDocs) validateModelShape(entry.document, entry.id, entry.label, issues);
|
|
15475
|
+
for (const entry of roleDocs) validateRoleShape(entry.document, entry.id, entry.label, issues);
|
|
15476
|
+
if (issues.length > 0) return { ok: false, issues: sortIssues2(issues) };
|
|
15477
|
+
const models = /* @__PURE__ */ new Map();
|
|
15478
|
+
for (const entry of modelDocs) {
|
|
15479
|
+
validateModelSemantics(entry.document, entry.label, options, evidence, issues);
|
|
15480
|
+
const normalized = cloneJson(entry.document);
|
|
15481
|
+
normalized.id = entry.id;
|
|
15482
|
+
models.set(entry.id, normalized);
|
|
15483
|
+
}
|
|
15484
|
+
const roles = /* @__PURE__ */ new Map();
|
|
15485
|
+
for (const entry of roleDocs) {
|
|
15486
|
+
validateRoleSemantics(entry.document, entry.id, entry.label, models, options, issues);
|
|
15487
|
+
const normalized = cloneJson(entry.document);
|
|
15488
|
+
normalized.id = entry.id;
|
|
15489
|
+
roles.set(entry.id, normalized);
|
|
15490
|
+
}
|
|
15491
|
+
validateExtends(roles, issues);
|
|
15492
|
+
validateCriticVendors(roles, models, options, issues);
|
|
15493
|
+
if (issues.length > 0) return { ok: false, issues: sortIssues2(issues) };
|
|
15494
|
+
const data = freezeDeep({
|
|
15495
|
+
models: Object.fromEntries([...models.entries()].sort(([left], [right]) => compareCodeUnits2(left, right))),
|
|
15496
|
+
roles: Object.fromEntries([...roles.entries()].sort(([left], [right]) => compareCodeUnits2(left, right)))
|
|
15497
|
+
});
|
|
15498
|
+
return { ok: true, data };
|
|
15499
|
+
}
|
|
15500
|
+
|
|
14917
15501
|
// plugins/kxm/src/template.ts
|
|
14918
15502
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
14919
|
-
import { createHash } from "node:crypto";
|
|
15503
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
14920
15504
|
var KXM_TEMPLATE_ID = "builtin-minimal";
|
|
14921
15505
|
var KXM_TEMPLATE_PROVENANCE_PATH = ".kxm/template-provenance.yaml";
|
|
14922
15506
|
var CURRENT_KXM_TEMPLATE_VARIANT = "v4-registry";
|
|
14923
15507
|
var SUPPORTED_KXM_TEMPLATE_VARIANTS = ["v1", "v2", "v3-policy", "v4-registry"];
|
|
14924
|
-
function
|
|
15508
|
+
function compareCodeUnits3(left, right) {
|
|
14925
15509
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
14926
15510
|
}
|
|
14927
15511
|
function canonicalJson(value) {
|
|
@@ -14929,10 +15513,10 @@ function canonicalJson(value) {
|
|
|
14929
15513
|
return JSON.stringify(value);
|
|
14930
15514
|
}
|
|
14931
15515
|
if (Array.isArray(value)) return `[${value.map((candidate) => canonicalJson(candidate)).join(",")}]`;
|
|
14932
|
-
return `{${Object.keys(value).sort(
|
|
15516
|
+
return `{${Object.keys(value).sort(compareCodeUnits3).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
14933
15517
|
}
|
|
14934
15518
|
function kxmContentSha256(input) {
|
|
14935
|
-
return `sha256:${
|
|
15519
|
+
return `sha256:${createHash2("sha256").update(input).digest("hex")}`;
|
|
14936
15520
|
}
|
|
14937
15521
|
function authorityProjection(value) {
|
|
14938
15522
|
const projected = structuredClone(value);
|
|
@@ -15077,8 +15661,41 @@ function coreTemplate(projectId, projectName, variant) {
|
|
|
15077
15661
|
schema: "kxm.routes.v2",
|
|
15078
15662
|
updatedAt: "2026-09-23T00:00:00.000Z",
|
|
15079
15663
|
admitted: ["anthropic/fable", "xai/grok-4.6"],
|
|
15080
|
-
disabled: []
|
|
15081
|
-
|
|
15664
|
+
disabled: []
|
|
15665
|
+
});
|
|
15666
|
+
files.set(".kxm/models/grok-default.yaml", {
|
|
15667
|
+
schema: "kxm.model.v2",
|
|
15668
|
+
id: "grok-default",
|
|
15669
|
+
harness: "grok",
|
|
15670
|
+
model: "grok-4.6",
|
|
15671
|
+
vendor: "xai",
|
|
15672
|
+
status: "admitted",
|
|
15673
|
+
permissions: ["edit"]
|
|
15674
|
+
});
|
|
15675
|
+
files.set(".kxm/models/fable-default.yaml", {
|
|
15676
|
+
schema: "kxm.model.v2",
|
|
15677
|
+
id: "fable-default",
|
|
15678
|
+
harness: "claude",
|
|
15679
|
+
model: "fable",
|
|
15680
|
+
vendor: "anthropic",
|
|
15681
|
+
status: "admitted",
|
|
15682
|
+
permissions: ["read-only"]
|
|
15683
|
+
});
|
|
15684
|
+
files.set(".kxm/roles/writer.yaml", {
|
|
15685
|
+
schema: "kxm.role.v2",
|
|
15686
|
+
id: "writer",
|
|
15687
|
+
purpose: "writer",
|
|
15688
|
+
permission: "edit",
|
|
15689
|
+
description: "Primary implementation agent.",
|
|
15690
|
+
roster: [{ route: "grok-default" }]
|
|
15691
|
+
});
|
|
15692
|
+
files.set(".kxm/roles/planner.yaml", {
|
|
15693
|
+
schema: "kxm.role.v2",
|
|
15694
|
+
id: "planner",
|
|
15695
|
+
purpose: "planner",
|
|
15696
|
+
permission: "read-only",
|
|
15697
|
+
description: "Plans the change before implementation.",
|
|
15698
|
+
roster: [{ route: "fable-default" }]
|
|
15082
15699
|
});
|
|
15083
15700
|
files.set(".kxm/gates.yaml", {
|
|
15084
15701
|
schema: "kxm.gate-registry.v1",
|
|
@@ -15107,7 +15724,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
|
|
|
15107
15724
|
authoritySha256: kxmAuthoritySha256(value),
|
|
15108
15725
|
bytes: bytes.byteLength
|
|
15109
15726
|
};
|
|
15110
|
-
}).sort((left, right) =>
|
|
15727
|
+
}).sort((left, right) => compareCodeUnits3(left.path, right.path));
|
|
15111
15728
|
const templateRevision = provenanceRevision(records);
|
|
15112
15729
|
const provenance = {
|
|
15113
15730
|
schema: "kxm.template-provenance.v1",
|
|
@@ -15121,7 +15738,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
|
|
|
15121
15738
|
KXM_TEMPLATE_PROVENANCE_PATH,
|
|
15122
15739
|
Buffer.from((0, import_yaml2.stringify)(provenance, { lineWidth: 0 }), "utf8")
|
|
15123
15740
|
]);
|
|
15124
|
-
fileEntries.sort(([left], [right]) =>
|
|
15741
|
+
fileEntries.sort(([left], [right]) => compareCodeUnits3(left, right));
|
|
15125
15742
|
const files = new Map(fileEntries);
|
|
15126
15743
|
return { projectId, projectName, templateRevision, provenance, files, values };
|
|
15127
15744
|
}
|
|
@@ -15986,7 +16603,7 @@ function resolveDispatchStatus(entry, detected, authenticated, issues) {
|
|
|
15986
16603
|
return { status: "no", supported: false, reason: "not_authenticated" };
|
|
15987
16604
|
}
|
|
15988
16605
|
if (authenticated === null) {
|
|
15989
|
-
const authIssue = issues.find((
|
|
16606
|
+
const authIssue = issues.find((issue4) => issue4 === "auth_context_required" || issue4 === "auth_unknown" || issue4 === "auth_unparsed" || issue4.startsWith("auth_"));
|
|
15990
16607
|
return { status: "no", supported: false, reason: authIssue ?? issues[0] ?? "auth_unknown" };
|
|
15991
16608
|
}
|
|
15992
16609
|
return { status: "yes", supported: true };
|
|
@@ -16501,8 +17118,8 @@ function formatHarnessUpdate(steps) {
|
|
|
16501
17118
|
var KxmConfigError = class extends Error {
|
|
16502
17119
|
issues;
|
|
16503
17120
|
constructor(issues) {
|
|
16504
|
-
const sorted =
|
|
16505
|
-
super(sorted.map((
|
|
17121
|
+
const sorted = sortIssues3(issues);
|
|
17122
|
+
super(sorted.map((issue4) => `${issue4.file}: ${issue4.code}: ${issue4.message}`).join("\n"));
|
|
16506
17123
|
this.name = "KxmConfigError";
|
|
16507
17124
|
this.issues = sorted;
|
|
16508
17125
|
}
|
|
@@ -16515,12 +17132,13 @@ var RESOURCE_SCHEMA = Object.freeze({
|
|
|
16515
17132
|
project: { identity: "kxm.project.v1", file: "project.schema.json" },
|
|
16516
17133
|
repository: { identity: "kxm.repository.v1", file: "repository.schema.json" },
|
|
16517
17134
|
agent: { identity: "kxm.agent.v1", file: "agent.schema.json" },
|
|
16518
|
-
model: { identity: "kxm.model.
|
|
17135
|
+
model: { identity: "kxm.model.v2", file: "model.schema.json" },
|
|
17136
|
+
role: { identity: "kxm.role.v2", file: "role.schema.json" },
|
|
16519
17137
|
environment: { identity: "kxm.environment.v1", file: "environment.schema.json" },
|
|
16520
17138
|
workflow: { identity: "kxm.workflow.v1", file: "workflow.schema.json" },
|
|
16521
17139
|
"gate-registry": { identity: "kxm.gate-registry.v1", file: "gate-registry.schema.json" }
|
|
16522
17140
|
});
|
|
16523
|
-
var
|
|
17141
|
+
var IDENTIFIER2 = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
16524
17142
|
var WINDOWS_RESERVED = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³]|conin\$|conout\$|clock\$)$/i;
|
|
16525
17143
|
var BUILTIN_EXECUTORS = ["local", "ssh", "exe-dev"];
|
|
16526
17144
|
var BUILTIN_TOOL_PRESETS = ["coordinator", "read-only", "workspace-writer", "tests-writer"];
|
|
@@ -16532,17 +17150,17 @@ var SECRET_VALUE_PATTERNS = [
|
|
|
16532
17150
|
/\bAKIA[A-Z0-9]{16}\b/,
|
|
16533
17151
|
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/
|
|
16534
17152
|
];
|
|
16535
|
-
function
|
|
17153
|
+
function compareCodeUnits4(left, right) {
|
|
16536
17154
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
16537
17155
|
}
|
|
16538
|
-
function
|
|
16539
|
-
return [...issues].sort((left, right) =>
|
|
17156
|
+
function sortIssues3(issues) {
|
|
17157
|
+
return [...issues].sort((left, right) => compareCodeUnits4(left.file, right.file) || compareCodeUnits4(left.phase, right.phase) || compareCodeUnits4(left.code, right.code) || compareCodeUnits4(left.message, right.message));
|
|
16540
17158
|
}
|
|
16541
|
-
function
|
|
17159
|
+
function issue3(phase, code, file, message) {
|
|
16542
17160
|
return { phase, code, file, message };
|
|
16543
17161
|
}
|
|
16544
17162
|
function fail2(phase, code, file, message) {
|
|
16545
|
-
throw new KxmConfigError([
|
|
17163
|
+
throw new KxmConfigError([issue3(phase, code, file, message)]);
|
|
16546
17164
|
}
|
|
16547
17165
|
function isJsonObject2(value) {
|
|
16548
17166
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -16635,7 +17253,7 @@ var KxmSchemaRegistry = class {
|
|
|
16635
17253
|
validate(kind, value, file) {
|
|
16636
17254
|
const definition = RESOURCE_SCHEMA[kind];
|
|
16637
17255
|
if (value.schema !== definition.identity) {
|
|
16638
|
-
return [
|
|
17256
|
+
return [issue3("schema", "schema_identity_mismatch", file, `expected ${definition.identity}, received ${String(value.schema)}`)];
|
|
16639
17257
|
}
|
|
16640
17258
|
const validator = this.validators.get(kind);
|
|
16641
17259
|
if (!validator) throw new Error(`missing KXM validator for ${kind}`);
|
|
@@ -16659,7 +17277,7 @@ var KxmSchemaRegistry = class {
|
|
|
16659
17277
|
}
|
|
16660
17278
|
validateAuxiliary(value, file, identity, validator) {
|
|
16661
17279
|
if (value.schema !== identity) {
|
|
16662
|
-
return [
|
|
17280
|
+
return [issue3("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
|
|
16663
17281
|
}
|
|
16664
17282
|
if (validator(value)) return [];
|
|
16665
17283
|
return (validator.errors ?? []).map((error) => schemaIssue(file, error));
|
|
@@ -16669,7 +17287,7 @@ var cachedRunEventRegistry;
|
|
|
16669
17287
|
function validateRunEvent(value, file) {
|
|
16670
17288
|
const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
|
|
16671
17289
|
if (!registry.runEventValidator(value)) {
|
|
16672
|
-
throw new KxmConfigError([
|
|
17290
|
+
throw new KxmConfigError([issue3(
|
|
16673
17291
|
"schema",
|
|
16674
17292
|
"run_event_invalid",
|
|
16675
17293
|
file,
|
|
@@ -16685,7 +17303,7 @@ function syncEventSchemaErrors(value) {
|
|
|
16685
17303
|
function validateDriveReceipt(value, file) {
|
|
16686
17304
|
const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
|
|
16687
17305
|
if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.drive-receipt.v1") {
|
|
16688
|
-
throw new KxmConfigError([
|
|
17306
|
+
throw new KxmConfigError([issue3(
|
|
16689
17307
|
"schema",
|
|
16690
17308
|
"drive_receipt_invalid",
|
|
16691
17309
|
file,
|
|
@@ -16693,7 +17311,7 @@ function validateDriveReceipt(value, file) {
|
|
|
16693
17311
|
)]);
|
|
16694
17312
|
}
|
|
16695
17313
|
if (!registry.driveReceiptValidator(value)) {
|
|
16696
|
-
throw new KxmConfigError([
|
|
17314
|
+
throw new KxmConfigError([issue3(
|
|
16697
17315
|
"schema",
|
|
16698
17316
|
"drive_receipt_invalid",
|
|
16699
17317
|
file,
|
|
@@ -16704,10 +17322,10 @@ function validateDriveReceipt(value, file) {
|
|
|
16704
17322
|
function validateCoordinator(value, file) {
|
|
16705
17323
|
const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
|
|
16706
17324
|
if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.coordinator.v1") {
|
|
16707
|
-
throw new KxmConfigError([
|
|
17325
|
+
throw new KxmConfigError([issue3("schema", "coordinator_invalid", file, "expected kxm.coordinator.v1")]);
|
|
16708
17326
|
}
|
|
16709
17327
|
if (!registry.coordinatorValidator(value)) {
|
|
16710
|
-
throw new KxmConfigError([
|
|
17328
|
+
throw new KxmConfigError([issue3(
|
|
16711
17329
|
"schema",
|
|
16712
17330
|
"coordinator_invalid",
|
|
16713
17331
|
file,
|
|
@@ -16718,10 +17336,10 @@ function validateCoordinator(value, file) {
|
|
|
16718
17336
|
function validateIntakeMessage(value, file) {
|
|
16719
17337
|
const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
|
|
16720
17338
|
if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.intake-message.v1") {
|
|
16721
|
-
throw new KxmConfigError([
|
|
17339
|
+
throw new KxmConfigError([issue3("schema", "intake_message_invalid", file, "expected kxm.intake-message.v1")]);
|
|
16722
17340
|
}
|
|
16723
17341
|
if (!registry.intakeMessageValidator(value)) {
|
|
16724
|
-
throw new KxmConfigError([
|
|
17342
|
+
throw new KxmConfigError([issue3(
|
|
16725
17343
|
"schema",
|
|
16726
17344
|
"intake_message_invalid",
|
|
16727
17345
|
file,
|
|
@@ -16732,7 +17350,7 @@ function validateIntakeMessage(value, file) {
|
|
|
16732
17350
|
function schemaIssue(file, error) {
|
|
16733
17351
|
const location = error.instancePath || "/";
|
|
16734
17352
|
const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
|
|
16735
|
-
return
|
|
17353
|
+
return issue3("schema", `schema_${error.keyword}`, file, `${location} ${error.message ?? "is invalid"}${suffix}`);
|
|
16736
17354
|
}
|
|
16737
17355
|
function canonicalHostPath(path) {
|
|
16738
17356
|
const resolved = resolve(path);
|
|
@@ -16758,7 +17376,7 @@ function portableBindingIssue(root, pathHint, repositoryId) {
|
|
|
16758
17376
|
if (!existsSync3(current)) break;
|
|
16759
17377
|
const stat = lstatSync(current);
|
|
16760
17378
|
if (stat.isSymbolicLink()) {
|
|
16761
|
-
return
|
|
17379
|
+
return issue3("path", "repository_binding_path_link", ".kxm/project.yaml", `portable pathHint for ${repositoryId} traverses a symbolic link or junction`);
|
|
16762
17380
|
}
|
|
16763
17381
|
}
|
|
16764
17382
|
const binding = resolve(root, ...pathHint.split("/"));
|
|
@@ -16766,7 +17384,7 @@ function portableBindingIssue(root, pathHint, repositoryId) {
|
|
|
16766
17384
|
const realRoot = canonicalHostPath(root);
|
|
16767
17385
|
const realBinding = canonicalHostPath(binding);
|
|
16768
17386
|
if (!containedHostPath(realRoot, realBinding)) {
|
|
16769
|
-
return
|
|
17387
|
+
return issue3("path", "repository_binding_outside_project", ".kxm/project.yaml", `portable pathHint for ${repositoryId} resolves outside the authoritative project root`);
|
|
16770
17388
|
}
|
|
16771
17389
|
}
|
|
16772
17390
|
return void 0;
|
|
@@ -16781,7 +17399,7 @@ function portablePath(path) {
|
|
|
16781
17399
|
return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== ".." && !WINDOWS_RESERVED.test((segment.split(".")[0] ?? segment).replace(/[ .]+$/g, "")) && !segment.endsWith(".") && !segment.endsWith(" ") && !segment.includes(":"));
|
|
16782
17400
|
}
|
|
16783
17401
|
function kxmResourceIdentifier(id) {
|
|
16784
|
-
return id.length <= 64 &&
|
|
17402
|
+
return id.length <= 64 && IDENTIFIER2.test(id) && !WINDOWS_RESERVED.test(id);
|
|
16785
17403
|
}
|
|
16786
17404
|
function resourceIdentifier(id) {
|
|
16787
17405
|
return kxmResourceIdentifier(id);
|
|
@@ -16859,7 +17477,7 @@ function readTemplateProvenance(registry, root) {
|
|
|
16859
17477
|
fail2("path", "template_provenance_path_invalid", label, `managed path ${path} is not a portable project-configuration path`);
|
|
16860
17478
|
}
|
|
16861
17479
|
if (seen.has(folded)) fail2("path", "template_provenance_path_collision", label, `managed path ${path} collides after case folding`);
|
|
16862
|
-
if (prior &&
|
|
17480
|
+
if (prior && compareCodeUnits4(prior, path) >= 0) fail2("semantic", "template_provenance_order_invalid", label, "managed file entries must use strict code-unit order");
|
|
16863
17481
|
seen.add(folded);
|
|
16864
17482
|
prior = path;
|
|
16865
17483
|
}
|
|
@@ -16876,27 +17494,27 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
|
|
|
16876
17494
|
fail2("path", "resource_directory_invalid", displayPath(root, directory), "resource directory must be a regular directory");
|
|
16877
17495
|
}
|
|
16878
17496
|
const issues = [];
|
|
16879
|
-
const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
|
|
17497
|
+
const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) => compareCodeUnits4(left.name, right.name));
|
|
16880
17498
|
for (const entry of entries) {
|
|
16881
17499
|
if (entry.isDirectory()) {
|
|
16882
|
-
issues.push(
|
|
17500
|
+
issues.push(issue3("path", "nested_resource_directory", displayPath(root, join2(directory, entry.name)), "nested resource directories are not allowed"));
|
|
16883
17501
|
continue;
|
|
16884
17502
|
}
|
|
16885
17503
|
if (!entry.name.toLowerCase().endsWith(".yaml") && !entry.name.toLowerCase().endsWith(".yml")) continue;
|
|
16886
17504
|
if (!entry.isFile() || extname(entry.name) !== ".yaml") {
|
|
16887
|
-
issues.push(
|
|
17505
|
+
issues.push(issue3("path", "resource_filename_invalid", displayPath(root, join2(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
|
|
16888
17506
|
continue;
|
|
16889
17507
|
}
|
|
16890
17508
|
const id = basename(entry.name, ".yaml");
|
|
16891
17509
|
if (kind === "model" && id === "inventory") continue;
|
|
16892
17510
|
if (id === replacedId) continue;
|
|
16893
17511
|
if (!resourceIdentifier(id)) {
|
|
16894
|
-
issues.push(
|
|
17512
|
+
issues.push(issue3("path", "resource_id_invalid", displayPath(root, join2(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
|
|
16895
17513
|
continue;
|
|
16896
17514
|
}
|
|
16897
17515
|
const collision = [...resources.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
|
|
16898
17516
|
if (collision) {
|
|
16899
|
-
issues.push(
|
|
17517
|
+
issues.push(issue3("path", "resource_id_collision", displayPath(root, join2(directory, entry.name)), `${id} case-folds to existing ${collision}`));
|
|
16900
17518
|
continue;
|
|
16901
17519
|
}
|
|
16902
17520
|
try {
|
|
@@ -16931,7 +17549,7 @@ function mapResource(resource, map, issues) {
|
|
|
16931
17549
|
if (!id) return;
|
|
16932
17550
|
const collision = [...map.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
|
|
16933
17551
|
if (collision) {
|
|
16934
|
-
issues.push(
|
|
17552
|
+
issues.push(issue3("path", "resource_id_collision", resource.logicalPath, `${id} case-folds to existing ${collision}`));
|
|
16935
17553
|
return;
|
|
16936
17554
|
}
|
|
16937
17555
|
map.set(id, resource);
|
|
@@ -16955,7 +17573,7 @@ function validatePortablePaths(resource, issues) {
|
|
|
16955
17573
|
}
|
|
16956
17574
|
for (const candidate of candidates) {
|
|
16957
17575
|
if (!portablePath(candidate.path)) {
|
|
16958
|
-
issues.push(
|
|
17576
|
+
issues.push(issue3("path", "portable_path_invalid", resource.logicalPath, `${candidate.label} is not a normalized forward-slash relative path: ${candidate.path}`));
|
|
16959
17577
|
}
|
|
16960
17578
|
}
|
|
16961
17579
|
}
|
|
@@ -16965,14 +17583,14 @@ function selectorCandidates(selectorValue, models, file, label, issues) {
|
|
|
16965
17583
|
const profile = stringValue(selector.profile);
|
|
16966
17584
|
if (profile) {
|
|
16967
17585
|
const model = models.get(profile);
|
|
16968
|
-
if (!model) issues.push(
|
|
17586
|
+
if (!model) issues.push(issue3("reference", "model_profile_unknown", file, `${label} references unknown model profile ${profile}`));
|
|
16969
17587
|
return model ? [model] : [];
|
|
16970
17588
|
}
|
|
16971
17589
|
const tag = stringValue(selector.tag);
|
|
16972
17590
|
if (!tag) return [];
|
|
16973
17591
|
const capabilities = new Set(names(selector.capabilities));
|
|
16974
17592
|
const candidates = [...models.values()].filter((model) => names(model.value.tags).includes(tag) && [...capabilities].every((capability) => names(model.value.capabilities).includes(capability)));
|
|
16975
|
-
if (candidates.length === 0) issues.push(
|
|
17593
|
+
if (candidates.length === 0) issues.push(issue3("reference", "model_tag_unresolved", file, `${label} selector tag ${tag} has no matching profile`));
|
|
16976
17594
|
return candidates;
|
|
16977
17595
|
}
|
|
16978
17596
|
function modelCandidates(selectorValue, models) {
|
|
@@ -16985,7 +17603,7 @@ function modelCandidates(selectorValue, models) {
|
|
|
16985
17603
|
return [{
|
|
16986
17604
|
key: `profile:${profile}`,
|
|
16987
17605
|
profile,
|
|
16988
|
-
...stringValue(resource.value.provider) ? { provider: stringValue(resource.value.provider) } : {},
|
|
17606
|
+
...stringValue(resource.value.provider) || stringValue(resource.value.vendor) ? { provider: stringValue(resource.value.provider) ?? stringValue(resource.value.vendor) } : {},
|
|
16989
17607
|
...stringValue(resource.value.model) ? { model: stringValue(resource.value.model) } : {}
|
|
16990
17608
|
}];
|
|
16991
17609
|
}
|
|
@@ -16995,7 +17613,7 @@ function modelCandidates(selectorValue, models) {
|
|
|
16995
17613
|
return [...models.values()].filter((resource) => names(resource.value.tags).includes(tag) && [...capabilities].every((capability) => names(resource.value.capabilities).includes(capability))).map((resource) => ({
|
|
16996
17614
|
key: `profile:${resource.id ?? "unknown"}`,
|
|
16997
17615
|
...resource.id ? { profile: resource.id } : {},
|
|
16998
|
-
...stringValue(resource.value.provider) ? { provider: stringValue(resource.value.provider) } : {},
|
|
17616
|
+
...stringValue(resource.value.provider) || stringValue(resource.value.vendor) ? { provider: stringValue(resource.value.provider) ?? stringValue(resource.value.vendor) } : {},
|
|
16999
17617
|
...stringValue(resource.value.model) ? { model: stringValue(resource.value.model) } : {}
|
|
17000
17618
|
}));
|
|
17001
17619
|
}
|
|
@@ -17027,13 +17645,13 @@ function validateEnvironment(resource, issues) {
|
|
|
17027
17645
|
const secret = objectValue(candidate);
|
|
17028
17646
|
const name = secret && stringValue(secret.name);
|
|
17029
17647
|
if (!name) continue;
|
|
17030
|
-
if (secretNames.has(name)) issues.push(
|
|
17648
|
+
if (secretNames.has(name)) issues.push(issue3("semantic", "secret_name_duplicate", resource.logicalPath, `secret environment name ${name} is duplicated`));
|
|
17031
17649
|
secretNames.add(name);
|
|
17032
|
-
if (Object.hasOwn(values, name)) issues.push(
|
|
17650
|
+
if (Object.hasOwn(values, name)) issues.push(issue3("semantic", "environment_name_conflict", resource.logicalPath, `${name} appears in both values and secrets`));
|
|
17033
17651
|
}
|
|
17034
17652
|
for (const [name, value] of Object.entries(values)) {
|
|
17035
17653
|
if (typeof value === "string" && SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
|
|
17036
|
-
issues.push(
|
|
17654
|
+
issues.push(issue3("semantic", "probable_secret_value", resource.logicalPath, `${name} resembles a credential and must be represented by secrets[].ref`));
|
|
17037
17655
|
}
|
|
17038
17656
|
}
|
|
17039
17657
|
}
|
|
@@ -17045,29 +17663,29 @@ function validateToolScope(agent, step, file, stepId, issues) {
|
|
|
17045
17663
|
if (!requested) return;
|
|
17046
17664
|
const ceiling = objectValue(agent.value.tools);
|
|
17047
17665
|
if (!ceiling) {
|
|
17048
|
-
issues.push(
|
|
17666
|
+
issues.push(issue3("semantic", "tool_scope_expansion", file, `${stepId} requests tools but agent ${agent.id ?? "unknown"} declares no tool ceiling`));
|
|
17049
17667
|
return;
|
|
17050
17668
|
}
|
|
17051
17669
|
const requestedPreset = stringValue(requested.preset);
|
|
17052
17670
|
const ceilingPreset = stringValue(ceiling.preset);
|
|
17053
17671
|
if (requestedPreset !== ceilingPreset) {
|
|
17054
|
-
issues.push(
|
|
17672
|
+
issues.push(issue3("semantic", "tool_scope_expansion", file, `${stepId} tool preset ${String(requestedPreset)} does not preserve agent ${agent.id ?? "unknown"} preset ${String(ceilingPreset)}`));
|
|
17055
17673
|
}
|
|
17056
17674
|
const allowed = new Set(names(ceiling.allow));
|
|
17057
17675
|
const denied = new Set(names(ceiling.deny));
|
|
17058
17676
|
for (const tool of names(requested.allow)) {
|
|
17059
|
-
if (!allowed.has(tool) || denied.has(tool)) issues.push(
|
|
17677
|
+
if (!allowed.has(tool) || denied.has(tool)) issues.push(issue3("semantic", "tool_scope_expansion", file, `${stepId} requests tool ${tool} beyond agent ${agent.id ?? "unknown"} explicit allowlist`));
|
|
17060
17678
|
}
|
|
17061
17679
|
const requestedDenied = new Set(names(requested.deny));
|
|
17062
17680
|
for (const tool of denied) {
|
|
17063
|
-
if (!requestedDenied.has(tool)) issues.push(
|
|
17681
|
+
if (!requestedDenied.has(tool)) issues.push(issue3("semantic", "tool_scope_expansion", file, `${stepId} drops agent ${agent.id ?? "unknown"} denial for ${tool}`));
|
|
17064
17682
|
}
|
|
17065
17683
|
}
|
|
17066
17684
|
function validateToolPolicy(resource, policy, label, issues) {
|
|
17067
17685
|
if (!policy) return;
|
|
17068
17686
|
const allowed = new Set(names(policy.allow));
|
|
17069
17687
|
for (const tool of names(policy.deny)) {
|
|
17070
|
-
if (allowed.has(tool)) issues.push(
|
|
17688
|
+
if (allowed.has(tool)) issues.push(issue3("semantic", "tool_policy_contradiction", resource.logicalPath, `${label} both allows and denies ${tool}`));
|
|
17071
17689
|
}
|
|
17072
17690
|
}
|
|
17073
17691
|
function validateAgentScope(agent, step, repositories, file, stepId, issues) {
|
|
@@ -17078,14 +17696,14 @@ function validateAgentScope(agent, step, repositories, file, stepId, issues) {
|
|
|
17078
17696
|
if (!repositories.has(repositoryId)) continue;
|
|
17079
17697
|
const ceiling = agentRepositories[repositoryId] ?? defaultAccess ?? "none";
|
|
17080
17698
|
if (accessRank(requested) > accessRank(ceiling)) {
|
|
17081
|
-
issues.push(
|
|
17699
|
+
issues.push(issue3("semantic", "repository_scope_expansion", file, `${stepId} requests ${String(requested)} access to ${repositoryId} beyond agent ${agent.id ?? "unknown"} ceiling ${String(ceiling)}`));
|
|
17082
17700
|
}
|
|
17083
17701
|
}
|
|
17084
17702
|
const grants = new Set(valuesOf(agent.value, "secrets").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate)).map((candidate) => stringValue(candidate.ref)).filter((candidate) => Boolean(candidate)));
|
|
17085
17703
|
for (const candidate of valuesOf(step, "secrets")) {
|
|
17086
17704
|
const reference = stringValue(objectValue(candidate)?.ref);
|
|
17087
17705
|
if (reference && !grants.has(reference)) {
|
|
17088
|
-
issues.push(
|
|
17706
|
+
issues.push(issue3("semantic", "secret_scope_expansion", file, `${stepId} requests secret ${reference} beyond agent ${agent.id ?? "unknown"} ceiling`));
|
|
17089
17707
|
}
|
|
17090
17708
|
}
|
|
17091
17709
|
}
|
|
@@ -17100,7 +17718,7 @@ function gateOutcomeImpossible(step, stepId, file) {
|
|
|
17100
17718
|
const impossible = declared.filter((outcome) => !produced.includes(outcome) && outcome !== "implementation_failure" && outcome !== "repro_missing");
|
|
17101
17719
|
const missing = produced.filter((outcome) => !declared.includes(outcome));
|
|
17102
17720
|
if (impossible.length === 0 || missing.length === 0) return void 0;
|
|
17103
|
-
return
|
|
17721
|
+
return issue3("semantic", "gate_outcome_impossible", file, `${stepId} declares ${impossible.join(", ")}, which a gate step with expect ${expect} never produces; it settles on ${produced.join(" or ")}, so declare ${missing.join(" and ")}`);
|
|
17104
17722
|
}
|
|
17105
17723
|
function transition(value) {
|
|
17106
17724
|
if (typeof value === "string") return { target: value };
|
|
@@ -17116,13 +17734,13 @@ function transition(value) {
|
|
|
17116
17734
|
function validateWorkflow(workflow, agents, models, repositories, gates, issues) {
|
|
17117
17735
|
const file = workflow.logicalPath;
|
|
17118
17736
|
const coordinator = stringValue(workflow.value.coordinator) ?? "coordinator";
|
|
17119
|
-
if (!agents.has(coordinator)) issues.push(
|
|
17737
|
+
if (!agents.has(coordinator)) issues.push(issue3("reference", "coordinator_unknown", file, `coordinator ${coordinator} does not exist`));
|
|
17120
17738
|
const steps = valuesOf(workflow.value, "steps").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
17121
17739
|
const stepIndex = /* @__PURE__ */ new Map();
|
|
17122
17740
|
for (const [index, step] of steps.entries()) {
|
|
17123
17741
|
const id = stringValue(step.id);
|
|
17124
17742
|
if (!id) continue;
|
|
17125
|
-
if (stepIndex.has(id)) issues.push(
|
|
17743
|
+
if (stepIndex.has(id)) issues.push(issue3("semantic", "step_id_duplicate", file, `step ${id} is duplicated`));
|
|
17126
17744
|
else stepIndex.set(id, index);
|
|
17127
17745
|
}
|
|
17128
17746
|
for (const field of ["reproOracle", "planHash"]) {
|
|
@@ -17132,16 +17750,16 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17132
17750
|
const evidenceKey = stringValue(oracle.evidenceKey);
|
|
17133
17751
|
const index = stageId === void 0 ? void 0 : stepIndex.get(stageId);
|
|
17134
17752
|
if (index === void 0) {
|
|
17135
|
-
issues.push(
|
|
17753
|
+
issues.push(issue3("reference", "oracle_stage_unknown", file, `${field} references unknown stage ${String(stageId)}`));
|
|
17136
17754
|
continue;
|
|
17137
17755
|
}
|
|
17138
17756
|
const evidence = new Set(valuesOf(steps[index] ?? {}, "requiredEvidence").map((candidate) => stringValue(objectValue(candidate)?.key)).filter((candidate) => Boolean(candidate)));
|
|
17139
|
-
if (!evidenceKey || !evidence.has(evidenceKey)) issues.push(
|
|
17757
|
+
if (!evidenceKey || !evidence.has(evidenceKey)) issues.push(issue3("reference", "oracle_evidence_unknown", file, `${field} references undeclared evidence ${String(evidenceKey)} in ${stageId}`));
|
|
17140
17758
|
}
|
|
17141
17759
|
const planStages = new Set(names(workflow.value.requirePlanHash));
|
|
17142
|
-
if (planStages.size > 0 && !objectValue(workflow.value.planHash)) issues.push(
|
|
17760
|
+
if (planStages.size > 0 && !objectValue(workflow.value.planHash)) issues.push(issue3("semantic", "plan_hash_missing", file, "requirePlanHash requires planHash"));
|
|
17143
17761
|
for (const stageId of planStages) {
|
|
17144
|
-
if (!stepIndex.has(stageId)) issues.push(
|
|
17762
|
+
if (!stepIndex.has(stageId)) issues.push(issue3("reference", "plan_hash_stage_unknown", file, `requirePlanHash references unknown stage ${stageId}`));
|
|
17145
17763
|
}
|
|
17146
17764
|
const adjacency = steps.map(() => /* @__PURE__ */ new Set());
|
|
17147
17765
|
const terminalReachable = /* @__PURE__ */ new Set();
|
|
@@ -17153,23 +17771,23 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17153
17771
|
validateToolPolicy(workflow, objectValue(step.tools), `${stepId}.tools`, issues);
|
|
17154
17772
|
const primaryAgentId = stringValue(step.agent);
|
|
17155
17773
|
if ((kind === "agent" || kind === "moa") && primaryAgentId && !agents.has(primaryAgentId)) {
|
|
17156
|
-
issues.push(
|
|
17774
|
+
issues.push(issue3("reference", "agent_unknown", file, `${stepId} references unknown agent ${primaryAgentId}`));
|
|
17157
17775
|
}
|
|
17158
17776
|
const gate = stringValue(step.gate);
|
|
17159
17777
|
if (kind === "gate" && gate) {
|
|
17160
|
-
if (!gates) issues.push(
|
|
17161
|
-
else if (!gates.has(gate)) issues.push(
|
|
17778
|
+
if (!gates) issues.push(issue3("reference", "gate_registry_missing", file, `${stepId} requires .kxm/gates.yaml`));
|
|
17779
|
+
else if (!gates.has(gate)) issues.push(issue3("reference", "gate_unknown", file, `${stepId} references unregistered gate ${gate}`));
|
|
17162
17780
|
}
|
|
17163
17781
|
if (step.expect !== void 0 && (kind !== "gate" || !["pass", "fail"].includes(String(step.expect)))) {
|
|
17164
|
-
issues.push(
|
|
17782
|
+
issues.push(issue3("semantic", "gate_expect_invalid", file, `${stepId} expect is gate-only pass or fail`));
|
|
17165
17783
|
}
|
|
17166
17784
|
if (kind === "gate" && Object.keys(objectValue(step.on) ?? {}).some((outcome) => outcome === "implementation_failure" || outcome === "repro_missing")) {
|
|
17167
|
-
issues.push(
|
|
17785
|
+
issues.push(issue3("semantic", "gate_outcome_renamed", file, `${stepId} must use implementation-failure and repro-missing`));
|
|
17168
17786
|
}
|
|
17169
17787
|
const impossibleOutcome = kind === "gate" ? gateOutcomeImpossible(step, stepId, file) : void 0;
|
|
17170
17788
|
if (impossibleOutcome) issues.push(impossibleOutcome);
|
|
17171
17789
|
for (const repositoryId of Object.keys(objectValue(step.repositories) ?? {})) {
|
|
17172
|
-
if (!repositories.has(repositoryId)) issues.push(
|
|
17790
|
+
if (!repositories.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
|
|
17173
17791
|
}
|
|
17174
17792
|
selectorCandidates(step.model, models, file, `${stepId}.model`, issues);
|
|
17175
17793
|
const assignment = objectValue(step.assignments);
|
|
@@ -17177,7 +17795,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17177
17795
|
const allowedAgents = declaredAgents.length > 0 ? declaredAgents : primaryAgentId ? [primaryAgentId] : [];
|
|
17178
17796
|
for (const agentId of allowedAgents) {
|
|
17179
17797
|
const agent = agents.get(agentId);
|
|
17180
|
-
if (!agent) issues.push(
|
|
17798
|
+
if (!agent) issues.push(issue3("reference", "assignment_agent_unknown", file, `${stepId} allows unknown agent ${agentId}`));
|
|
17181
17799
|
else {
|
|
17182
17800
|
validateAgentScope(agent, step, repositories, file, stepId, issues);
|
|
17183
17801
|
if (step.model !== void 0 && agent.value.model !== void 0) {
|
|
@@ -17185,37 +17803,37 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17185
17803
|
const ceilingModels = modelCandidates(agent.value.model, models);
|
|
17186
17804
|
const effectiveModels = requestedModels.filter((requested) => ceilingModels.some((ceiling) => compatibleModel(requested, ceiling)));
|
|
17187
17805
|
if (requestedModels.length > 0 && effectiveModels.length === 0) {
|
|
17188
|
-
issues.push(
|
|
17806
|
+
issues.push(issue3("semantic", "model_selector_incompatible", file, `${stepId} model selector has no candidate within agent ${agentId} ceiling`));
|
|
17189
17807
|
}
|
|
17190
17808
|
}
|
|
17191
17809
|
}
|
|
17192
17810
|
}
|
|
17193
17811
|
if (primaryAgentId && allowedAgents.length > 0 && !allowedAgents.includes(primaryAgentId)) {
|
|
17194
|
-
issues.push(
|
|
17812
|
+
issues.push(issue3("semantic", "primary_agent_ineligible", file, `${stepId} primary agent ${primaryAgentId} is not in allowedAgents`));
|
|
17195
17813
|
}
|
|
17196
17814
|
const minimum = numberValue(assignment?.minimum, 1);
|
|
17197
17815
|
const target = numberValue(assignment?.target, minimum);
|
|
17198
17816
|
const maximum = numberValue(assignment?.maximum, target);
|
|
17199
17817
|
const maxParallel = numberValue(assignment?.maxParallel, maximum);
|
|
17200
|
-
if (!(minimum <= target && target <= maximum)) issues.push(
|
|
17201
|
-
if (maxParallel > maximum) issues.push(
|
|
17818
|
+
if (!(minimum <= target && target <= maximum)) issues.push(issue3("semantic", "assignment_bounds_invalid", file, `${stepId} must satisfy minimum <= target <= maximum`));
|
|
17819
|
+
if (maxParallel > maximum) issues.push(issue3("semantic", "assignment_parallelism_invalid", file, `${stepId} maxParallel exceeds maximum`));
|
|
17202
17820
|
if (assignment && allowedAgents.length > 0 && maximum > allowedAgents.length && kind === "moa") {
|
|
17203
|
-
issues.push(
|
|
17821
|
+
issues.push(issue3("semantic", "assignment_pool_too_small", file, `${stepId} maximum ${maximum} exceeds ${allowedAgents.length} eligible agents`));
|
|
17204
17822
|
}
|
|
17205
17823
|
const maxWriteRepositories = assignment && typeof assignment.maxWriteRepositories === "number" ? assignment.maxWriteRepositories : void 0;
|
|
17206
17824
|
if (maxWriteRepositories !== void 0) {
|
|
17207
17825
|
const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
|
|
17208
|
-
if (maxWriteRepositories > writable) issues.push(
|
|
17826
|
+
if (maxWriteRepositories > writable) issues.push(issue3("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
|
|
17209
17827
|
}
|
|
17210
17828
|
const join23 = objectValue(step.join);
|
|
17211
17829
|
const minimumPassed = join23 && typeof join23.minimumPassed === "number" ? join23.minimumPassed : void 0;
|
|
17212
|
-
if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(
|
|
17830
|
+
if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue3("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
|
|
17213
17831
|
const distinctBy = names(assignment?.distinctBy);
|
|
17214
17832
|
if (distinctBy.length > 0) {
|
|
17215
17833
|
const candidates = diversityCandidates(step.model, allowedAgents, agents, models);
|
|
17216
17834
|
for (const dimension of distinctBy) {
|
|
17217
17835
|
const distinct = new Set(candidates.map((candidate) => candidate[dimension]).filter((value) => Boolean(value)));
|
|
17218
|
-
if (distinct.size < target) issues.push(
|
|
17836
|
+
if (distinct.size < target) issues.push(issue3("semantic", "model_diversity_impossible", file, `${stepId} requires ${target} distinct ${dimension} values but resolves ${distinct.size}`));
|
|
17219
17837
|
}
|
|
17220
17838
|
}
|
|
17221
17839
|
const evidenceKeys = /* @__PURE__ */ new Set();
|
|
@@ -17224,29 +17842,29 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17224
17842
|
if (!requirement) continue;
|
|
17225
17843
|
const key = stringValue(requirement.key);
|
|
17226
17844
|
if (key) {
|
|
17227
|
-
if (evidenceKeys.has(key)) issues.push(
|
|
17845
|
+
if (evidenceKeys.has(key)) issues.push(issue3("semantic", "evidence_key_duplicate", file, `${stepId} evidence key ${key} is duplicated`));
|
|
17228
17846
|
evidenceKeys.add(key);
|
|
17229
17847
|
}
|
|
17230
17848
|
const policy = objectValue(requirement.producerPolicy);
|
|
17231
17849
|
if (!policy) continue;
|
|
17232
17850
|
const eligible = names(policy.eligibleAgents);
|
|
17233
17851
|
const producerMinimum = numberValue(policy.minimumProducers, 1);
|
|
17234
|
-
if (producerMinimum > target || producerMinimum > eligible.length) issues.push(
|
|
17852
|
+
if (producerMinimum > target || producerMinimum > eligible.length) issues.push(issue3("semantic", "producer_minimum_impossible", file, `${stepId}.${String(key)} producer minimum exceeds its eligible or target pool`));
|
|
17235
17853
|
for (const agentId of eligible) {
|
|
17236
|
-
if (!agents.has(agentId)) issues.push(
|
|
17237
|
-
if (allowedAgents.length > 0 && !allowedAgents.includes(agentId)) issues.push(
|
|
17854
|
+
if (!agents.has(agentId)) issues.push(issue3("reference", "producer_agent_unknown", file, `${stepId}.${String(key)} references unknown producer ${agentId}`));
|
|
17855
|
+
if (allowedAgents.length > 0 && !allowedAgents.includes(agentId)) issues.push(issue3("semantic", "producer_agent_ineligible", file, `${stepId}.${String(key)} producer ${agentId} is outside allowedAgents`));
|
|
17238
17856
|
}
|
|
17239
17857
|
const degradation = objectValue(policy.degradation);
|
|
17240
|
-
if (degradation && numberValue(degradation.minimumProducers, 1) >= producerMinimum) issues.push(
|
|
17858
|
+
if (degradation && numberValue(degradation.minimumProducers, 1) >= producerMinimum) issues.push(issue3("semantic", "producer_degradation_invalid", file, `${stepId}.${String(key)} degradation must lower minimumProducers`));
|
|
17241
17859
|
}
|
|
17242
17860
|
const writes = Object.values(objectValue(step.repositories) ?? {}).some((access) => access === "write");
|
|
17243
17861
|
const planHashStage = stringValue(objectValue(workflow.value.planHash)?.stageId);
|
|
17244
17862
|
const planHashIndex = planHashStage === void 0 ? void 0 : stepIndex.get(planHashStage);
|
|
17245
17863
|
if (writes && planHashIndex !== void 0 && index > planHashIndex && !planStages.has(stepId)) {
|
|
17246
|
-
issues.push(
|
|
17864
|
+
issues.push(issue3("semantic", "mutation_missing_plan_hash", file, `${stepId} mutates after the approved-plan stage but is absent from requirePlanHash`));
|
|
17247
17865
|
}
|
|
17248
17866
|
const outcomes = objectValue(step.on) ?? {};
|
|
17249
|
-
if (Object.keys(outcomes).length === 0) issues.push(
|
|
17867
|
+
if (Object.keys(outcomes).length === 0) issues.push(issue3("semantic", "step_transitions_missing", file, `${stepId} has no declared transitions`));
|
|
17250
17868
|
for (const [outcome, rawTransition] of Object.entries(outcomes)) {
|
|
17251
17869
|
const parsed = transition(rawTransition);
|
|
17252
17870
|
if (parsed.target === "$terminal") {
|
|
@@ -17256,29 +17874,29 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17256
17874
|
}
|
|
17257
17875
|
const targetIndex = parsed.target === void 0 ? void 0 : stepIndex.get(parsed.target);
|
|
17258
17876
|
if (targetIndex === void 0) {
|
|
17259
|
-
issues.push(
|
|
17877
|
+
issues.push(issue3("reference", "transition_target_unknown", file, `${stepId}.${outcome} references unknown target ${String(parsed.target)}`));
|
|
17260
17878
|
continue;
|
|
17261
17879
|
}
|
|
17262
17880
|
adjacency[index]?.add(targetIndex);
|
|
17263
17881
|
if (targetIndex <= index) {
|
|
17264
17882
|
hasBackEdge = true;
|
|
17265
|
-
if (parsed.maxTransitions === void 0) issues.push(
|
|
17883
|
+
if (parsed.maxTransitions === void 0) issues.push(issue3("semantic", "back_edge_unbounded", file, `${stepId}.${outcome} back-edge requires maxTransitions`));
|
|
17266
17884
|
}
|
|
17267
17885
|
}
|
|
17268
17886
|
}
|
|
17269
17887
|
if (hasBackEdge && typeof objectValue(workflow.value.limits)?.maxTransitions !== "number") {
|
|
17270
|
-
issues.push(
|
|
17888
|
+
issues.push(issue3("semantic", "workflow_cycle_unbounded", file, "workflow with back-edges requires limits.maxTransitions"));
|
|
17271
17889
|
}
|
|
17272
17890
|
const readyIndex = stepIndex.get("ready");
|
|
17273
17891
|
const verifyIndex = stepIndex.get("verify");
|
|
17274
17892
|
if (readyIndex !== void 0 && verifyIndex !== void 0) {
|
|
17275
17893
|
const verifyTargets = adjacency[verifyIndex] ?? /* @__PURE__ */ new Set();
|
|
17276
17894
|
if (!verifyTargets.has(readyIndex)) {
|
|
17277
|
-
issues.push(
|
|
17895
|
+
issues.push(issue3("semantic", "verify_must_precede_ready", file, "verify must transition to ready"));
|
|
17278
17896
|
}
|
|
17279
17897
|
for (const [srcIndex, targets] of adjacency.entries()) {
|
|
17280
17898
|
if (targets.has(readyIndex) && srcIndex !== verifyIndex) {
|
|
17281
|
-
issues.push(
|
|
17899
|
+
issues.push(issue3("semantic", "verify_must_precede_ready", file, `step ${String(steps[srcIndex]?.id)} transitions to ready bypassing verify`));
|
|
17282
17900
|
}
|
|
17283
17901
|
}
|
|
17284
17902
|
}
|
|
@@ -17293,7 +17911,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17293
17911
|
queue.push(target);
|
|
17294
17912
|
}
|
|
17295
17913
|
}
|
|
17296
|
-
for (const [index, step] of steps.entries()) if (!reachable.has(index)) issues.push(
|
|
17914
|
+
for (const [index, step] of steps.entries()) if (!reachable.has(index)) issues.push(issue3("semantic", "step_unreachable", file, `step ${String(step.id)} is unreachable from the first step`));
|
|
17297
17915
|
for (const [requiredIndex, requiredStep] of steps.entries()) {
|
|
17298
17916
|
if (requiredStep.kind !== "gate" && requiredStep.kind !== "approval") continue;
|
|
17299
17917
|
const reachableWithoutRequired = /* @__PURE__ */ new Set();
|
|
@@ -17309,7 +17927,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17309
17927
|
}
|
|
17310
17928
|
}
|
|
17311
17929
|
if ([...completedTerminalSources].some((source) => source !== requiredIndex && reachableWithoutRequired.has(source))) {
|
|
17312
|
-
issues.push(
|
|
17930
|
+
issues.push(issue3("semantic", "required_step_bypass", file, `a completed path can bypass required ${String(requiredStep.id)}`));
|
|
17313
17931
|
}
|
|
17314
17932
|
}
|
|
17315
17933
|
const canTerminate = new Set(terminalReachable);
|
|
@@ -17323,7 +17941,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17323
17941
|
}
|
|
17324
17942
|
}
|
|
17325
17943
|
}
|
|
17326
|
-
for (const index of reachable) if (!canTerminate.has(index)) issues.push(
|
|
17944
|
+
for (const index of reachable) if (!canTerminate.has(index)) issues.push(issue3("semantic", "step_cannot_terminate", file, `step ${String(steps[index]?.id)} has no path to a terminal transition`));
|
|
17327
17945
|
}
|
|
17328
17946
|
}
|
|
17329
17947
|
function validateModelReferences(models, issues) {
|
|
@@ -17336,7 +17954,7 @@ function validateModelReferences(models, issues) {
|
|
|
17336
17954
|
const visited = /* @__PURE__ */ new Set();
|
|
17337
17955
|
const walk = (id, path) => {
|
|
17338
17956
|
if (visiting.has(id)) {
|
|
17339
|
-
issues.push(
|
|
17957
|
+
issues.push(issue3("semantic", "model_fallback_cycle", models.get(id)?.logicalPath ?? ".kxm/models", `fallback profile cycle: ${[...path, id].join(" -> ")}`));
|
|
17340
17958
|
return;
|
|
17341
17959
|
}
|
|
17342
17960
|
if (visited.has(id)) return;
|
|
@@ -17364,17 +17982,17 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17364
17982
|
if (!id) continue;
|
|
17365
17983
|
const folded = id.toLocaleLowerCase("en-US");
|
|
17366
17984
|
const previous = foldedRepositories.get(folded);
|
|
17367
|
-
if (previous) issues.push(
|
|
17985
|
+
if (previous) issues.push(issue3("path", "repository_id_collision", project.logicalPath, `${id} case-folds to existing ${previous}`));
|
|
17368
17986
|
else foldedRepositories.set(folded, id);
|
|
17369
17987
|
repositoryIds.add(id);
|
|
17370
17988
|
if (entry.role === "control") controls += 1;
|
|
17371
17989
|
}
|
|
17372
|
-
if (controls !== 1) issues.push(
|
|
17990
|
+
if (controls !== 1) issues.push(issue3("semantic", "control_repository_count", project.logicalPath, `expected exactly one control repository, received ${controls}`));
|
|
17373
17991
|
for (const repository of repositories.values()) {
|
|
17374
|
-
if (repository.value.projectId !== project.value.id) issues.push(
|
|
17992
|
+
if (repository.value.projectId !== project.value.id) issues.push(issue3("reference", "repository_project_mismatch", repository.logicalPath, "projectId does not match the authoritative project"));
|
|
17375
17993
|
const id = stringValue(repository.value.repositoryId);
|
|
17376
|
-
if (!id || !repositoryIds.has(id)) issues.push(
|
|
17377
|
-
if (repository.id !== id) issues.push(
|
|
17994
|
+
if (!id || !repositoryIds.has(id)) issues.push(issue3("reference", "repository_definition_unknown", repository.logicalPath, `repositoryId ${String(id)} is not bound by project.yaml`));
|
|
17995
|
+
if (repository.id !== id) issues.push(issue3("reference", "repository_binding_identity_mismatch", repository.logicalPath, `binding ${String(repository.id)} declares repositoryId ${String(id)}`));
|
|
17378
17996
|
}
|
|
17379
17997
|
for (const resource of [project, ...environments]) validatePortablePaths(resource, issues);
|
|
17380
17998
|
for (const environment of environments) validateEnvironment(environment, issues);
|
|
@@ -17384,25 +18002,25 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17384
18002
|
const definition = objectValue(value);
|
|
17385
18003
|
const executable = Array.isArray(definition?.argv) ? definition.argv[0] : void 0;
|
|
17386
18004
|
if (definition?.kind === "command" && typeof executable === "string" && (executable === "." || executable === ".." || executable.includes("\\") || !executable.startsWith("/") && executable.includes("/"))) {
|
|
17387
|
-
issues.push(
|
|
18005
|
+
issues.push(issue3("semantic", "gate_executable_invalid", ".kxm/gates.yaml", `${id} argv[0] must be a bare executable or absolute POSIX path`));
|
|
17388
18006
|
}
|
|
17389
18007
|
}
|
|
17390
18008
|
const presets = new Set(options.registeredToolPresets ?? BUILTIN_TOOL_PRESETS);
|
|
17391
18009
|
const harnesses = new Set(options.registeredHarnesses ?? BUILTIN_HARNESS_IDS);
|
|
17392
18010
|
const defaultExecutor = stringValue(project.value.defaultExecutor);
|
|
17393
|
-
if (defaultExecutor && !executors.has(defaultExecutor)) issues.push(
|
|
18011
|
+
if (defaultExecutor && !executors.has(defaultExecutor)) issues.push(issue3("reference", "executor_unknown", project.logicalPath, `defaultExecutor ${defaultExecutor} is not registered`));
|
|
17394
18012
|
const defaultHarness = stringValue(project.value.defaultHarness) ?? DEFAULT_HARNESS;
|
|
17395
|
-
if (!harnesses.has(defaultHarness)) issues.push(
|
|
18013
|
+
if (!harnesses.has(defaultHarness)) issues.push(issue3("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
|
|
17396
18014
|
for (const agent of agents.values()) {
|
|
17397
18015
|
validateToolPolicy(agent, objectValue(agent.value.tools), "tools", issues);
|
|
17398
18016
|
const executor = stringValue(agent.value.executor);
|
|
17399
|
-
if (executor && !executors.has(executor)) issues.push(
|
|
18017
|
+
if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
|
|
17400
18018
|
const harness = stringValue(agent.value.harness) ?? defaultHarness;
|
|
17401
|
-
if (!harnesses.has(harness)) issues.push(
|
|
18019
|
+
if (!harnesses.has(harness)) issues.push(issue3("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
|
|
17402
18020
|
const preset = stringValue(objectValue(agent.value.tools)?.preset);
|
|
17403
|
-
if (preset && !presets.has(preset)) issues.push(
|
|
18021
|
+
if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
|
|
17404
18022
|
for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
|
|
17405
|
-
if (!repositoryIds.has(repositoryId)) issues.push(
|
|
18023
|
+
if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
|
|
17406
18024
|
}
|
|
17407
18025
|
const candidates = selectorCandidates(agent.value.model, models, agent.logicalPath, "model", issues);
|
|
17408
18026
|
const declaredHarness = stringValue(agent.value.harness);
|
|
@@ -17412,14 +18030,14 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17412
18030
|
const candidateModel = stringValue(candidate.value.model);
|
|
17413
18031
|
const validation = validateHarnessModelPair(declaredHarness, { provider: candidateProvider, model: candidateModel });
|
|
17414
18032
|
if (!validation.valid) {
|
|
17415
|
-
issues.push(
|
|
18033
|
+
issues.push(issue3("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
|
|
17416
18034
|
}
|
|
17417
18035
|
}
|
|
17418
18036
|
}
|
|
17419
18037
|
}
|
|
17420
18038
|
validateModelReferences(models, issues);
|
|
17421
18039
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
17422
|
-
if (!workflows.has(defaultWorkflow)) issues.push(
|
|
18040
|
+
if (!workflows.has(defaultWorkflow)) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
17423
18041
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
17424
18042
|
if (projectRoot) {
|
|
17425
18043
|
const writerRolePath = join2(projectRoot, ".kxm", "roles", "writer.yaml");
|
|
@@ -17429,7 +18047,27 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17429
18047
|
const rawRole = parseRestrictedYaml2(writerRole ?? readFileSync(writerRolePath, "utf8"));
|
|
17430
18048
|
const roleObj = objectValue(rawRole);
|
|
17431
18049
|
const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
|
|
17432
|
-
const enabledRosterModels = rosterEntries.
|
|
18050
|
+
const enabledRosterModels = rosterEntries.flatMap((entry) => {
|
|
18051
|
+
const direct = stringValue(entry.model);
|
|
18052
|
+
const route = stringValue(entry.route);
|
|
18053
|
+
const named = direct ? [direct] : [];
|
|
18054
|
+
if (route) {
|
|
18055
|
+
const modelFile = join2(projectRoot, ".kxm", "models", `${route}.yaml`);
|
|
18056
|
+
if (existsSync3(modelFile)) {
|
|
18057
|
+
try {
|
|
18058
|
+
const modelDoc = objectValue(parseRestrictedYaml2(readFileSync(modelFile, "utf8"), modelFile));
|
|
18059
|
+
const model = modelDoc ? stringValue(modelDoc.model) : void 0;
|
|
18060
|
+
const vendor = modelDoc ? stringValue(modelDoc.vendor) : void 0;
|
|
18061
|
+
const harness = modelDoc ? stringValue(modelDoc.harness) : void 0;
|
|
18062
|
+
if (model) named.push(model);
|
|
18063
|
+
if (vendor && model) named.push(`${vendor}/${model}`);
|
|
18064
|
+
if (harness && model) named.push(`${harness}/${model}`);
|
|
18065
|
+
} catch {
|
|
18066
|
+
}
|
|
18067
|
+
}
|
|
18068
|
+
}
|
|
18069
|
+
return named;
|
|
18070
|
+
});
|
|
17433
18071
|
const agentModelObj = objectValue(implementerAgent.value.model);
|
|
17434
18072
|
const agentModelStr = stringValue(implementerAgent.value.model);
|
|
17435
18073
|
const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
|
|
@@ -17438,7 +18076,7 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17438
18076
|
if (enabledRosterModels.length > 0 && canonicalAgentModel) {
|
|
17439
18077
|
const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
|
|
17440
18078
|
if (!matches) {
|
|
17441
|
-
issues.push(
|
|
18079
|
+
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}`));
|
|
17442
18080
|
}
|
|
17443
18081
|
}
|
|
17444
18082
|
} catch (error) {
|
|
@@ -17448,7 +18086,83 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17448
18086
|
}
|
|
17449
18087
|
}
|
|
17450
18088
|
}
|
|
17451
|
-
|
|
18089
|
+
if (projectRoot && existsSync3(join2(projectRoot, ".kxm", "roster.yaml"))) {
|
|
18090
|
+
issues.push(...developerRolePolicyIssues(projectRoot));
|
|
18091
|
+
}
|
|
18092
|
+
return sortIssues3(issues);
|
|
18093
|
+
}
|
|
18094
|
+
function developerCeilings() {
|
|
18095
|
+
const script = join2(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
|
|
18096
|
+
let loaded;
|
|
18097
|
+
try {
|
|
18098
|
+
loaded = spawnSync2(process.execPath, ["--input-type=module", "-e", `const m = await import(${JSON.stringify(pathToFileURL(script).href)}); process.stdout.write(JSON.stringify({ROUTES:m.ROUTES,NATIVE_PI_BRAKE_PROVIDERS:m.NATIVE_PI_BRAKE_PROVIDERS,PI_ALLOWED_PROVIDERS:m.PI_ALLOWED_PROVIDERS,PI_NATIVE_VENDOR_PROVIDERS:m.PI_NATIVE_VENDOR_PROVIDERS}))`], {
|
|
18099
|
+
encoding: "utf8",
|
|
18100
|
+
timeout: 15e3
|
|
18101
|
+
});
|
|
18102
|
+
} catch (error) {
|
|
18103
|
+
return { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
18104
|
+
}
|
|
18105
|
+
if (loaded.error) {
|
|
18106
|
+
const code = loaded.error.code;
|
|
18107
|
+
const detail = code === "ETIMEDOUT" || loaded.signal ? `timed out after 15s (${loaded.error.message})` : loaded.error.message;
|
|
18108
|
+
return { ok: false, detail };
|
|
18109
|
+
}
|
|
18110
|
+
if (loaded.status !== 0 || !loaded.stdout) {
|
|
18111
|
+
const stderr = (loaded.stderr ?? "").trim();
|
|
18112
|
+
return { ok: false, detail: stderr || `scripts/harness-run.mjs exited ${loaded.status}` };
|
|
18113
|
+
}
|
|
18114
|
+
try {
|
|
18115
|
+
return { ok: true, ...JSON.parse(loaded.stdout) };
|
|
18116
|
+
} catch (error) {
|
|
18117
|
+
return { ok: false, detail: `ceilings JSON parse failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
18118
|
+
}
|
|
18119
|
+
}
|
|
18120
|
+
function developerRolePolicyIssues(projectRoot) {
|
|
18121
|
+
const rolesDir = join2(projectRoot, ".kxm", "roles");
|
|
18122
|
+
const modelsDir = join2(projectRoot, ".kxm", "models");
|
|
18123
|
+
const roles = {};
|
|
18124
|
+
const models = {};
|
|
18125
|
+
const evidence = {};
|
|
18126
|
+
const parseIssues = [];
|
|
18127
|
+
const readMap = (dir, into, skipInventory) => {
|
|
18128
|
+
if (!existsSync3(dir)) return;
|
|
18129
|
+
for (const name of readdirSync(dir)) {
|
|
18130
|
+
if (!name.endsWith(".yaml")) continue;
|
|
18131
|
+
const id = basename(name, ".yaml");
|
|
18132
|
+
if (skipInventory && id === "inventory") continue;
|
|
18133
|
+
try {
|
|
18134
|
+
const value = parseRestrictedYaml2(readFileSync(join2(dir, name), "utf8"), `${dir}/${name}`);
|
|
18135
|
+
into[id] = value;
|
|
18136
|
+
} catch (error) {
|
|
18137
|
+
if (error instanceof KxmConfigError) {
|
|
18138
|
+
parseIssues.push(...error.issues);
|
|
18139
|
+
continue;
|
|
18140
|
+
}
|
|
18141
|
+
}
|
|
18142
|
+
}
|
|
18143
|
+
};
|
|
18144
|
+
readMap(rolesDir, roles, false);
|
|
18145
|
+
readMap(modelsDir, models, true);
|
|
18146
|
+
for (const model of Object.values(models)) {
|
|
18147
|
+
const origin = objectValue(model.origin);
|
|
18148
|
+
const source = origin ? stringValue(origin.source) : void 0;
|
|
18149
|
+
if (!source || evidence[source] !== void 0) continue;
|
|
18150
|
+
const evidenceFile = isAbsolute(source) ? source : join2(projectRoot, source);
|
|
18151
|
+
if (existsSync3(evidenceFile)) evidence[source] = readFileSync(evidenceFile, "utf8");
|
|
18152
|
+
}
|
|
18153
|
+
const ceilings = developerCeilings();
|
|
18154
|
+
if (!ceilings.ok) {
|
|
18155
|
+
return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/roster.yaml", `developer ceilings could not be loaded: ${ceilings.detail}`)];
|
|
18156
|
+
}
|
|
18157
|
+
const result = validatePolicyDraft({ models, roles, evidence }, {
|
|
18158
|
+
ceilings: ceilings.ROUTES,
|
|
18159
|
+
nativePiBrakeProviders: ceilings.NATIVE_PI_BRAKE_PROVIDERS,
|
|
18160
|
+
piAllowedProviders: ceilings.PI_ALLOWED_PROVIDERS,
|
|
18161
|
+
piNativeVendorProviders: ceilings.PI_NATIVE_VENDOR_PROVIDERS,
|
|
18162
|
+
vendorAliases: { "x-ai": "xai", moonshotai: "moonshot", "google-ai": "google", qwen: "alibaba" }
|
|
18163
|
+
});
|
|
18164
|
+
if (result.ok) return parseIssues;
|
|
18165
|
+
return [...parseIssues, ...result.issues.map((entry) => issue3(entry.phase, entry.code, entry.file, entry.message))];
|
|
17452
18166
|
}
|
|
17453
18167
|
function kxmCanonicalJson(value) {
|
|
17454
18168
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
@@ -17459,8 +18173,8 @@ function canonicalize(value) {
|
|
|
17459
18173
|
return kxmCanonicalJson(value);
|
|
17460
18174
|
}
|
|
17461
18175
|
function bundleRevision(resources) {
|
|
17462
|
-
const hash =
|
|
17463
|
-
for (const resource of [...resources].sort((left, right) =>
|
|
18176
|
+
const hash = createHash3("sha256");
|
|
18177
|
+
for (const resource of [...resources].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath))) {
|
|
17464
18178
|
hash.update(resource.logicalPath, "utf8");
|
|
17465
18179
|
hash.update("\0", "utf8");
|
|
17466
18180
|
hash.update(canonicalize(resource.value), "utf8");
|
|
@@ -17486,7 +18200,7 @@ function discoverGitRoot(start = process.cwd()) {
|
|
|
17486
18200
|
}
|
|
17487
18201
|
function assertNoRegisteredGates(options) {
|
|
17488
18202
|
if ("registeredGates" in options) {
|
|
17489
|
-
throw new KxmConfigError([
|
|
18203
|
+
throw new KxmConfigError([issue3("semantic", "registered_gates_removed", ".kxm/gates.yaml", "registeredGates was removed; declare gates in .kxm/gates.yaml")]);
|
|
17490
18204
|
}
|
|
17491
18205
|
}
|
|
17492
18206
|
function loadKxmProject(projectRoot, options = {}) {
|
|
@@ -17499,7 +18213,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17499
18213
|
const root = resolve(projectRoot);
|
|
17500
18214
|
const legacyPresent = legacyConfigFilesAt(root);
|
|
17501
18215
|
if (legacyPresent.length > 0) {
|
|
17502
|
-
throw new KxmConfigError(legacyPresent.map((file) =>
|
|
18216
|
+
throw new KxmConfigError(legacyPresent.map((file) => issue3(
|
|
17503
18217
|
"semantic",
|
|
17504
18218
|
"legacy_state_unsupported",
|
|
17505
18219
|
file,
|
|
@@ -17514,20 +18228,25 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17514
18228
|
if (templateProvenance && templateProvenance.inputs && typeof templateProvenance.inputs === "object" && !Array.isArray(templateProvenance.inputs)) {
|
|
17515
18229
|
const provenanceProjectId = stringValue(templateProvenance.inputs.projectId);
|
|
17516
18230
|
if (provenanceProjectId !== stringValue(project.value.id)) {
|
|
17517
|
-
earlyIssues.push(
|
|
18231
|
+
earlyIssues.push(issue3("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
|
|
17518
18232
|
}
|
|
17519
18233
|
}
|
|
17520
18234
|
const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((candidate2) => stringValue(objectValue(candidate2)?.id)).filter((candidate2) => candidate2 !== void 0));
|
|
17521
|
-
for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(
|
|
18235
|
+
for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(compareCodeUnits4)) {
|
|
17522
18236
|
if (!resourceIdentifier(repositoryId)) {
|
|
17523
|
-
earlyIssues.push(
|
|
18237
|
+
earlyIssues.push(issue3("path", "repository_binding_id_invalid", ".kxm/project.yaml", `host-local binding identity ${repositoryId} is invalid`));
|
|
17524
18238
|
} else if (!declaredRepositoryIds.has(repositoryId)) {
|
|
17525
|
-
earlyIssues.push(
|
|
18239
|
+
earlyIssues.push(issue3("reference", "repository_binding_unknown", ".kxm/project.yaml", `host-local binding references unknown repository ${repositoryId}`));
|
|
17526
18240
|
}
|
|
17527
18241
|
}
|
|
17528
18242
|
if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
|
|
17529
18243
|
const agents = listNamedResources(registry, root, join2(root, ".kxm", "agents"), ".kxm/agents", "agent");
|
|
17530
18244
|
const models = listNamedResources(registry, root, join2(root, ".kxm", "models"), ".kxm/models", "model");
|
|
18245
|
+
const roles = listNamedResources(registry, root, join2(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
|
|
18246
|
+
if (writerRole !== void 0) {
|
|
18247
|
+
const logicalPath = ".kxm/roles/writer.yaml";
|
|
18248
|
+
roles.set("writer", { kind: "role", id: "writer", file: join2(root, logicalPath), logicalPath, value: resourceValue(registry, writerRole, logicalPath, "role") });
|
|
18249
|
+
}
|
|
17531
18250
|
const workflows = listNamedResources(registry, root, join2(root, ".kxm", "workflows"), ".kxm/workflows", "workflow", workflowCandidate?.id);
|
|
17532
18251
|
if (workflowCandidate) {
|
|
17533
18252
|
const { id, document } = workflowCandidate;
|
|
@@ -17557,20 +18276,20 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17557
18276
|
const repositoryId = entry && stringValue(entry.id);
|
|
17558
18277
|
const pathHint = entry && stringValue(entry.pathHint);
|
|
17559
18278
|
const role = entry && stringValue(entry.role);
|
|
17560
|
-
const
|
|
18279
|
+
const required2 = entry?.required !== false;
|
|
17561
18280
|
if (!repositoryId) continue;
|
|
17562
18281
|
const hasLocalBinding = Object.hasOwn(options.repositoryBindings ?? {}, repositoryId);
|
|
17563
18282
|
const localBinding = options.repositoryBindings?.[repositoryId];
|
|
17564
18283
|
if (hasLocalBinding && (!localBinding || !isAbsolute(localBinding))) {
|
|
17565
|
-
loadIssues.push(
|
|
18284
|
+
loadIssues.push(issue3("path", "repository_binding_not_absolute", ".kxm/project.yaml", `host-local binding for ${repositoryId} must be absolute`));
|
|
17566
18285
|
continue;
|
|
17567
18286
|
}
|
|
17568
18287
|
if (role === "control" && localBinding && !sameHostPath(localBinding, root)) {
|
|
17569
|
-
loadIssues.push(
|
|
18288
|
+
loadIssues.push(issue3("path", "control_repository_binding_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must bind to the authoritative project root`));
|
|
17570
18289
|
continue;
|
|
17571
18290
|
}
|
|
17572
18291
|
if (role === "control" && pathHint !== void 0 && pathHint !== ".") {
|
|
17573
|
-
loadIssues.push(
|
|
18292
|
+
loadIssues.push(issue3("path", "control_repository_path_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must use pathHint .`));
|
|
17574
18293
|
continue;
|
|
17575
18294
|
}
|
|
17576
18295
|
if (!hasLocalBinding && role !== "control" && pathHint && portablePath(pathHint)) {
|
|
@@ -17582,28 +18301,28 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17582
18301
|
}
|
|
17583
18302
|
const binding = hasLocalBinding && localBinding ? resolve(localBinding) : role === "control" ? root : pathHint && portablePath(pathHint) ? resolve(root, ...pathHint.split("/")) : void 0;
|
|
17584
18303
|
if (!binding) {
|
|
17585
|
-
if (
|
|
18304
|
+
if (required2) loadIssues.push(issue3("discovery", "repository_binding_missing", ".kxm/project.yaml", `required repository ${repositoryId} has no portable pathHint or host-local binding`));
|
|
17586
18305
|
continue;
|
|
17587
18306
|
}
|
|
17588
18307
|
if (!existsSync3(binding)) {
|
|
17589
|
-
if (
|
|
18308
|
+
if (required2 || hasLocalBinding) loadIssues.push(issue3("discovery", "repository_binding_unavailable", ".kxm/project.yaml", `${hasLocalBinding ? "explicit" : "required"} repository binding ${repositoryId} is unavailable`));
|
|
17590
18309
|
continue;
|
|
17591
18310
|
}
|
|
17592
18311
|
const bindingStat = lstatSync(binding);
|
|
17593
18312
|
if (bindingStat.isSymbolicLink() || !bindingStat.isDirectory()) {
|
|
17594
|
-
loadIssues.push(
|
|
18313
|
+
loadIssues.push(issue3("path", "repository_binding_invalid", ".kxm/project.yaml", `repository binding ${repositoryId} must be a regular directory`));
|
|
17595
18314
|
continue;
|
|
17596
18315
|
}
|
|
17597
18316
|
if (role === "member") {
|
|
17598
18317
|
const repositoryGitRoot = discoverGitRoot(binding);
|
|
17599
18318
|
if (!repositoryGitRoot || !sameHostPath(repositoryGitRoot, binding)) {
|
|
17600
|
-
loadIssues.push(
|
|
18319
|
+
loadIssues.push(issue3("discovery", "repository_git_root_invalid", ".kxm/project.yaml", `member repository ${repositoryId} must bind to its authoritative Git worktree root`));
|
|
17601
18320
|
continue;
|
|
17602
18321
|
}
|
|
17603
18322
|
}
|
|
17604
18323
|
const memberLegacy = binding === root ? [] : legacyConfigFilesAt(binding);
|
|
17605
18324
|
if (memberLegacy.length > 0) {
|
|
17606
|
-
throw new KxmConfigError(memberLegacy.map((file) =>
|
|
18325
|
+
throw new KxmConfigError(memberLegacy.map((file) => issue3(
|
|
17607
18326
|
"semantic",
|
|
17608
18327
|
"legacy_state_unsupported",
|
|
17609
18328
|
`${repositoryId}/${file}`,
|
|
@@ -17613,7 +18332,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17613
18332
|
const foldedBinding = canonicalHostPath(binding).toLocaleLowerCase("en-US");
|
|
17614
18333
|
const priorBinding = seenBindings.get(foldedBinding);
|
|
17615
18334
|
if (priorBinding && priorBinding !== repositoryId) {
|
|
17616
|
-
loadIssues.push(
|
|
18335
|
+
loadIssues.push(issue3("path", "repository_binding_collision", ".kxm/project.yaml", `${repositoryId} and ${priorBinding} use the same repository binding`));
|
|
17617
18336
|
continue;
|
|
17618
18337
|
}
|
|
17619
18338
|
seenBindings.set(foldedBinding, repositoryId);
|
|
@@ -17623,7 +18342,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17623
18342
|
try {
|
|
17624
18343
|
const resource = readResource(registry, root, repositoryFile, `.kxm/repositories/${repositoryId}/repo.yaml`, "repository", repositoryId, binding);
|
|
17625
18344
|
if (resource.value.repositoryId !== repositoryId) {
|
|
17626
|
-
loadIssues.push(
|
|
18345
|
+
loadIssues.push(issue3("reference", "repository_binding_identity_mismatch", resource.logicalPath, `binding ${repositoryId} declares repositoryId ${String(resource.value.repositoryId)}`));
|
|
17627
18346
|
} else {
|
|
17628
18347
|
mapResource(resource, repositories, loadIssues);
|
|
17629
18348
|
definitionValidated = repositories.get(repositoryId) === resource;
|
|
@@ -17632,13 +18351,13 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17632
18351
|
if (error instanceof KxmConfigError) loadIssues.push(...error.issues);
|
|
17633
18352
|
else throw error;
|
|
17634
18353
|
}
|
|
17635
|
-
} else if (
|
|
17636
|
-
loadIssues.push(
|
|
18354
|
+
} else if (required2 || hasLocalBinding) {
|
|
18355
|
+
loadIssues.push(issue3("discovery", "repository_definition_missing", `.kxm/repositories/${repositoryId}/repo.yaml`, `${hasLocalBinding ? "explicit" : "required"} repository ${repositoryId} has no repo.yaml`));
|
|
17637
18356
|
}
|
|
17638
18357
|
const environmentFile = join2(binding, ".kxm", "repo", "env.yaml");
|
|
17639
18358
|
if (existsSync3(environmentFile)) {
|
|
17640
18359
|
if (!definitionValidated) {
|
|
17641
|
-
loadIssues.push(
|
|
18360
|
+
loadIssues.push(issue3("reference", "repository_environment_without_definition", `.kxm/repositories/${repositoryId}/env.yaml`, `repository ${repositoryId} environment requires a validated matching repo.yaml`));
|
|
17642
18361
|
} else {
|
|
17643
18362
|
try {
|
|
17644
18363
|
environments.push(readResource(registry, root, environmentFile, `.kxm/repositories/${repositoryId}/env.yaml`, "environment", void 0, binding));
|
|
@@ -17652,13 +18371,14 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
17652
18371
|
if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
|
|
17653
18372
|
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
|
|
17654
18373
|
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
17655
|
-
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) =>
|
|
18374
|
+
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...roles.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath));
|
|
17656
18375
|
return {
|
|
17657
18376
|
projectRoot: root,
|
|
17658
18377
|
project,
|
|
17659
18378
|
repositories,
|
|
17660
18379
|
agents,
|
|
17661
18380
|
models,
|
|
18381
|
+
roles,
|
|
17662
18382
|
workflows,
|
|
17663
18383
|
environments,
|
|
17664
18384
|
...gateRegistry ? { gateRegistry } : {},
|
|
@@ -17685,7 +18405,7 @@ function legacyConfigFilesAt(root) {
|
|
|
17685
18405
|
return files;
|
|
17686
18406
|
}
|
|
17687
18407
|
if (stat.isDirectory()) {
|
|
17688
|
-
for (const entry of readdirSync(workflowsDir, { withFileTypes: true }).filter((candidate) => (candidate.isFile() || candidate.isSymbolicLink()) && candidate.name.endsWith(".json")).sort((left, right) =>
|
|
18408
|
+
for (const entry of readdirSync(workflowsDir, { withFileTypes: true }).filter((candidate) => (candidate.isFile() || candidate.isSymbolicLink()) && candidate.name.endsWith(".json")).sort((left, right) => compareCodeUnits4(left.name, right.name))) {
|
|
17689
18409
|
files.push(`.kxm/config/workflows/${entry.name}`);
|
|
17690
18410
|
}
|
|
17691
18411
|
}
|
|
@@ -17694,7 +18414,7 @@ function legacyConfigFilesAt(root) {
|
|
|
17694
18414
|
}
|
|
17695
18415
|
|
|
17696
18416
|
// plugins/kxm/src/runtime-store.ts
|
|
17697
|
-
import { createHash as
|
|
18417
|
+
import { createHash as createHash7, randomUUID } from "node:crypto";
|
|
17698
18418
|
import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
17699
18419
|
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
17700
18420
|
|
|
@@ -17702,7 +18422,7 @@ import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
|
17702
18422
|
import { existsSync as existsSync4 } from "node:fs";
|
|
17703
18423
|
import { createRequire } from "node:module";
|
|
17704
18424
|
import { resolve as resolve2 } from "node:path";
|
|
17705
|
-
import { pathToFileURL } from "node:url";
|
|
18425
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
17706
18426
|
var requireFromHere = createRequire(import.meta.url);
|
|
17707
18427
|
function loadNative() {
|
|
17708
18428
|
try {
|
|
@@ -17741,13 +18461,13 @@ var DatabaseSync = class {
|
|
|
17741
18461
|
};
|
|
17742
18462
|
function openReadOnlyDatabase(path) {
|
|
17743
18463
|
if (existsSync4(`${path}-wal`)) return new DatabaseSync(path, { readOnly: true });
|
|
17744
|
-
const uri =
|
|
18464
|
+
const uri = pathToFileURL2(resolve2(path));
|
|
17745
18465
|
uri.searchParams.set("immutable", "1");
|
|
17746
18466
|
return new DatabaseSync(uri.href, { readOnly: true });
|
|
17747
18467
|
}
|
|
17748
18468
|
|
|
17749
18469
|
// plugins/kxm/src/runtime-paths.ts
|
|
17750
|
-
import { createHash as
|
|
18470
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
17751
18471
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
17752
18472
|
import { join as join4, resolve as resolve4 } from "node:path";
|
|
17753
18473
|
|
|
@@ -17801,14 +18521,14 @@ function projectRuntimeKey(projectRoot) {
|
|
|
17801
18521
|
canonical = resolve4(projectRoot);
|
|
17802
18522
|
}
|
|
17803
18523
|
const folded = process.platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
|
|
17804
|
-
return
|
|
18524
|
+
return createHash4("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
|
|
17805
18525
|
}
|
|
17806
18526
|
function kxmProjectRunEventsPath(projectRoot, env) {
|
|
17807
18527
|
return join4(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
|
|
17808
18528
|
}
|
|
17809
18529
|
|
|
17810
18530
|
// plugins/kxm/src/sync-transform.ts
|
|
17811
|
-
import { createHash as
|
|
18531
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
17812
18532
|
|
|
17813
18533
|
// plugins/kxm/src/redact.ts
|
|
17814
18534
|
var SECRET_PATTERNS = [
|
|
@@ -17847,7 +18567,7 @@ var KXM_DEFAULT_SYNC_POLICY = Object.freeze({
|
|
|
17847
18567
|
diffs: false,
|
|
17848
18568
|
environmentValues: false
|
|
17849
18569
|
});
|
|
17850
|
-
var KXM_DEFAULT_SYNC_POLICY_REVISION = `sha256:${
|
|
18570
|
+
var KXM_DEFAULT_SYNC_POLICY_REVISION = `sha256:${createHash5("sha256").update(kxmCanonicalJson(KXM_DEFAULT_SYNC_POLICY), "utf8").digest("hex")}`;
|
|
17851
18571
|
var MIN_REGISTERED_SECRET_CHARS = 4;
|
|
17852
18572
|
var REDACTED = "[redacted]";
|
|
17853
18573
|
var PATH_REDACTED = "[path]";
|
|
@@ -18139,7 +18859,7 @@ function kxmSyncEventBytes(event) {
|
|
|
18139
18859
|
}
|
|
18140
18860
|
|
|
18141
18861
|
// plugins/kxm/src/database.ts
|
|
18142
|
-
import { createHash as
|
|
18862
|
+
import { createHash as createHash6, randomBytes } from "node:crypto";
|
|
18143
18863
|
import {
|
|
18144
18864
|
chmodSync,
|
|
18145
18865
|
copyFileSync,
|
|
@@ -18153,8 +18873,8 @@ import {
|
|
|
18153
18873
|
} from "node:fs";
|
|
18154
18874
|
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute3, join as join5, relative as relative3, resolve as resolve5, sep as sep3 } from "node:path";
|
|
18155
18875
|
function databaseError(code, file, message) {
|
|
18156
|
-
const
|
|
18157
|
-
return new KxmConfigError([
|
|
18876
|
+
const issue4 = { phase: "semantic", code, file, message };
|
|
18877
|
+
return new KxmConfigError([issue4]);
|
|
18158
18878
|
}
|
|
18159
18879
|
function checkedParent(path, description) {
|
|
18160
18880
|
const parent = dirname4(path);
|
|
@@ -18377,10 +19097,10 @@ function checkIntegrity(database) {
|
|
|
18377
19097
|
}
|
|
18378
19098
|
function fileSha256(filePath) {
|
|
18379
19099
|
const bytes = readFileSync2(filePath);
|
|
18380
|
-
return `sha256:${
|
|
19100
|
+
return `sha256:${createHash6("sha256").update(bytes).digest("hex")}`;
|
|
18381
19101
|
}
|
|
18382
|
-
function backupDatabaseFile(
|
|
18383
|
-
const resolvedSource = resolve5(
|
|
19102
|
+
function backupDatabaseFile(sourcePath2, targetPath, storeId) {
|
|
19103
|
+
const resolvedSource = resolve5(sourcePath2);
|
|
18384
19104
|
const resolvedTarget = resolve5(targetPath);
|
|
18385
19105
|
const sourceStat = lstatSync2(resolvedSource, { throwIfNoEntry: false });
|
|
18386
19106
|
if (!sourceStat || !sourceStat.isFile() || sourceStat.isSymbolicLink()) {
|
|
@@ -18505,9 +19225,9 @@ function kxmBackupCeiling(storeId) {
|
|
|
18505
19225
|
if (storeId.startsWith("events:")) return KXM_BACKUP_CEILINGS.events;
|
|
18506
19226
|
return KXM_BACKUP_CEILINGS[storeId] ?? KXM_BACKUP_CEILINGS["hub-store"];
|
|
18507
19227
|
}
|
|
18508
|
-
function pushStore(stores, storeId,
|
|
18509
|
-
if (stores.some((store) => store.storeId === storeId || store.sourcePath ===
|
|
18510
|
-
stores.push({ storeId, sourcePath, maxSupportedVersion: kxmBackupCeiling(storeId) });
|
|
19228
|
+
function pushStore(stores, storeId, sourcePath2) {
|
|
19229
|
+
if (stores.some((store) => store.storeId === storeId || store.sourcePath === sourcePath2)) return;
|
|
19230
|
+
stores.push({ storeId, sourcePath: sourcePath2, maxSupportedVersion: kxmBackupCeiling(storeId) });
|
|
18511
19231
|
}
|
|
18512
19232
|
function pushEventStore(stores, files, paths, key) {
|
|
18513
19233
|
const dbPath = join5(paths.projectsDir, key, "run-events.db");
|
|
@@ -18575,8 +19295,8 @@ function discoverBackupSources(projectRoot, options = {}) {
|
|
|
18575
19295
|
const stateRoot = discoverUserRuntime(runtimeProjectKey, options, stores, files);
|
|
18576
19296
|
return { stores, files, runtimeProjectKey, ...stateRoot !== void 0 ? { stateRoot } : {} };
|
|
18577
19297
|
}
|
|
18578
|
-
function backupPlainFile(
|
|
18579
|
-
const resolvedSource = resolve5(
|
|
19298
|
+
function backupPlainFile(sourcePath2, targetPath, id) {
|
|
19299
|
+
const resolvedSource = resolve5(sourcePath2);
|
|
18580
19300
|
const sourceStat = lstatSync2(resolvedSource, { throwIfNoEntry: false });
|
|
18581
19301
|
if (!sourceStat || !sourceStat.isFile() || sourceStat.isSymbolicLink()) {
|
|
18582
19302
|
throw databaseError("runtime_path_invalid", resolvedSource, `backup file ${resolvedSource} must be a regular file, not a link or directory`);
|
|
@@ -18596,8 +19316,8 @@ function backupPlainFile(sourcePath, targetPath, id) {
|
|
|
18596
19316
|
bytes: sourceStat.size
|
|
18597
19317
|
};
|
|
18598
19318
|
}
|
|
18599
|
-
function backupFilename(
|
|
18600
|
-
let filename = basename2(
|
|
19319
|
+
function backupFilename(sourcePath2, id, used) {
|
|
19320
|
+
let filename = basename2(sourcePath2);
|
|
18601
19321
|
if (used.has(filename)) {
|
|
18602
19322
|
filename = `${id.replace(/[^a-zA-Z0-9_.-]/g, "_")}-${filename}`;
|
|
18603
19323
|
}
|
|
@@ -18705,7 +19425,7 @@ function createBackup(options = {}) {
|
|
|
18705
19425
|
...omitted.length > 0 ? { omitted } : {}
|
|
18706
19426
|
};
|
|
18707
19427
|
const manifestJson = JSON.stringify(manifest, null, 2) + "\n";
|
|
18708
|
-
const manifestSha256 = `sha256:${
|
|
19428
|
+
const manifestSha256 = `sha256:${createHash6("sha256").update(manifestJson, "utf8").digest("hex")}`;
|
|
18709
19429
|
manifest.manifestSha256 = manifestSha256;
|
|
18710
19430
|
const finalJson = JSON.stringify(manifest, null, 2) + "\n";
|
|
18711
19431
|
const manifestPath = join5(outDir, "manifest.json");
|
|
@@ -18718,22 +19438,22 @@ function pathUnder(root, path) {
|
|
|
18718
19438
|
if (rel === "" || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute3(rel)) return void 0;
|
|
18719
19439
|
return rel;
|
|
18720
19440
|
}
|
|
18721
|
-
function restoreTarget(manifest,
|
|
18722
|
-
const stateRel = pathUnder(manifest.stateRoot,
|
|
18723
|
-
const projectRel = pathUnder(manifest.projectRoot,
|
|
19441
|
+
function restoreTarget(manifest, sourcePath2, id, options, currentStateRoot) {
|
|
19442
|
+
const stateRel = pathUnder(manifest.stateRoot, sourcePath2);
|
|
19443
|
+
const projectRel = pathUnder(manifest.projectRoot, sourcePath2);
|
|
18724
19444
|
if (stateRel !== void 0 && (projectRel === void 0 || stateRel.length <= projectRel.length)) {
|
|
18725
19445
|
const ownEvents = manifest.runtimeProjectKey !== void 0 ? join5("runtime", "projects", manifest.runtimeProjectKey, "run-events.db") : void 0;
|
|
18726
|
-
const
|
|
18727
|
-
if (
|
|
19446
|
+
const own2 = ownEvents !== void 0 && (stateRel === ownEvents || stateRel === `${ownEvents}.run-prompts.json`);
|
|
19447
|
+
if (own2 && !options.allProjects && options.projectRoot !== void 0) {
|
|
18728
19448
|
const eventsPath = kxmProjectRunEventsPath(resolve5(options.projectRoot), options.env ?? process.env);
|
|
18729
19449
|
return { targetPath: stateRel === ownEvents ? eventsPath : `${eventsPath}.run-prompts.json`, machineWide: false };
|
|
18730
19450
|
}
|
|
18731
|
-
return { targetPath: join5(currentStateRoot(), stateRel), machineWide: !
|
|
19451
|
+
return { targetPath: join5(currentStateRoot(), stateRel), machineWide: !own2 };
|
|
18732
19452
|
}
|
|
18733
19453
|
if (projectRel !== void 0) {
|
|
18734
|
-
return { targetPath: options.projectRoot ? join5(resolve5(options.projectRoot), projectRel) :
|
|
19454
|
+
return { targetPath: options.projectRoot ? join5(resolve5(options.projectRoot), projectRel) : sourcePath2, machineWide: false };
|
|
18735
19455
|
}
|
|
18736
|
-
return { targetPath:
|
|
19456
|
+
return { targetPath: sourcePath2, machineWide: id !== "hub-store" };
|
|
18737
19457
|
}
|
|
18738
19458
|
function planRestore(manifestPathOrDir, options = {}) {
|
|
18739
19459
|
let manifestPath = resolve5(manifestPathOrDir);
|
|
@@ -19069,7 +19789,7 @@ var KXM_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
|
|
|
19069
19789
|
var KXM_ABSENT_MEMORY_REVISION = "ctxrev_absent";
|
|
19070
19790
|
function hashKxmDriveLog(events) {
|
|
19071
19791
|
const body = events.map((event) => `${event.eventId}:${event.sequence}`).join("\n");
|
|
19072
|
-
return `sha256:${
|
|
19792
|
+
return `sha256:${createHash7("sha256").update(body, "utf8").digest("hex")}`;
|
|
19073
19793
|
}
|
|
19074
19794
|
function verifyKxmDriveReceipt(receipt, events, foldedStatus, binding) {
|
|
19075
19795
|
const reasons = [];
|
|
@@ -20224,7 +20944,7 @@ function newKxmObservationId() {
|
|
|
20224
20944
|
function newKxmEvidenceId() {
|
|
20225
20945
|
return `gev_${randomUUID().replaceAll("-", "")}`;
|
|
20226
20946
|
}
|
|
20227
|
-
var
|
|
20947
|
+
var SHA2562 = /^sha256:[a-f0-9]{64}$/;
|
|
20228
20948
|
var OPAQUE = /^[a-z][a-z0-9]{1,15}_[A-Za-z0-9][A-Za-z0-9_-]{5,127}$/;
|
|
20229
20949
|
var FORBIDDEN_TEXT_KEYS = /* @__PURE__ */ new Set(["stdout", "stderr", "error", "errorText", "stdoutText", "stderrText", "errorMessage", "message"]);
|
|
20230
20950
|
var GATE_ATTEMPT_KEYS = [
|
|
@@ -20303,7 +21023,7 @@ function gateRowContentHash(table, row) {
|
|
|
20303
21023
|
const copy = { ...row };
|
|
20304
21024
|
delete copy.contentHash;
|
|
20305
21025
|
void table;
|
|
20306
|
-
return `sha256:${
|
|
21026
|
+
return `sha256:${createHash7("sha256").update(kxmCanonicalJson(copy), "utf8").digest("hex")}`;
|
|
20307
21027
|
}
|
|
20308
21028
|
function assertClosedGateObservation(kind, row, id = "observation") {
|
|
20309
21029
|
if (row.completeness === "incomplete") return;
|
|
@@ -20355,7 +21075,7 @@ function assertId(value, prefix, id, label) {
|
|
|
20355
21075
|
}
|
|
20356
21076
|
}
|
|
20357
21077
|
function assertSha(value, id, label) {
|
|
20358
|
-
if (typeof value !== "string" || !
|
|
21078
|
+
if (typeof value !== "string" || !SHA2562.test(value)) throw gateRowInvalid(id, `${label} is not a sha256 hash`);
|
|
20359
21079
|
}
|
|
20360
21080
|
function assertFlag(value, id, label) {
|
|
20361
21081
|
if (value !== 0 && value !== 1) throw gateRowInvalid(id, `${label} must be 0 or 1`);
|
|
@@ -21741,7 +22461,7 @@ function isTerminalRunStatus(status) {
|
|
|
21741
22461
|
}
|
|
21742
22462
|
|
|
21743
22463
|
// plugins/kxm/src/engine-plan.ts
|
|
21744
|
-
import { createHash as
|
|
22464
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
21745
22465
|
|
|
21746
22466
|
// plugins/kxm/src/engine-compile.ts
|
|
21747
22467
|
var KXM_COMPILED_WORKFLOW_SCHEMA = "kxm.compiled-workflow.v1";
|
|
@@ -21757,8 +22477,8 @@ var BANNED_RUNTIME_HINT = "not yet supported";
|
|
|
21757
22477
|
var KxmEngineCompileError = class extends Error {
|
|
21758
22478
|
issues;
|
|
21759
22479
|
constructor(issues) {
|
|
21760
|
-
const sorted =
|
|
21761
|
-
super(sorted.map((
|
|
22480
|
+
const sorted = sortIssues4(issues);
|
|
22481
|
+
super(sorted.map((issue4) => `${issue4.workflowId}: ${issue4.code}: ${issue4.message}`).join("\n"));
|
|
21762
22482
|
this.name = "KxmEngineCompileError";
|
|
21763
22483
|
this.issues = sorted;
|
|
21764
22484
|
}
|
|
@@ -21894,7 +22614,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
21894
22614
|
const join23 = compileJoin(step, id, sink);
|
|
21895
22615
|
const requiredEvidence = compileEvidence(step, id, sink);
|
|
21896
22616
|
const transitions = compileTransitions(step, id, index, stepIndex, sink);
|
|
21897
|
-
const outcomes = Object.keys(transitions).sort(
|
|
22617
|
+
const outcomes = Object.keys(transitions).sort(compareCodeUnits5);
|
|
21898
22618
|
const orderedTransitions = /* @__PURE__ */ Object.create(null);
|
|
21899
22619
|
for (const outcome of outcomes) {
|
|
21900
22620
|
const transition2 = transitions[outcome];
|
|
@@ -22224,26 +22944,26 @@ function deepFreeze(value) {
|
|
|
22224
22944
|
}
|
|
22225
22945
|
return Object.freeze(value);
|
|
22226
22946
|
}
|
|
22227
|
-
function
|
|
22947
|
+
function compareCodeUnits5(left, right) {
|
|
22228
22948
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
22229
22949
|
}
|
|
22230
22950
|
function compareOptional(left, right) {
|
|
22231
22951
|
if (left === right) return 0;
|
|
22232
22952
|
if (left === void 0) return -1;
|
|
22233
22953
|
if (right === void 0) return 1;
|
|
22234
|
-
return
|
|
22954
|
+
return compareCodeUnits5(left, right);
|
|
22235
22955
|
}
|
|
22236
|
-
function
|
|
22956
|
+
function sortIssues4(issues) {
|
|
22237
22957
|
return [...issues].sort((left, right) => {
|
|
22238
22958
|
const byStep = compareOptional(left.stepId, right.stepId);
|
|
22239
22959
|
if (byStep !== 0) return byStep;
|
|
22240
22960
|
const byOutcome = compareOptional(left.outcome, right.outcome);
|
|
22241
22961
|
if (byOutcome !== 0) return byOutcome;
|
|
22242
|
-
return
|
|
22962
|
+
return compareCodeUnits5(left.code, right.code);
|
|
22243
22963
|
});
|
|
22244
22964
|
}
|
|
22245
|
-
function pushIssue(sink,
|
|
22246
|
-
sink.issues.push({ workflowId: sink.workflowId, ...
|
|
22965
|
+
function pushIssue(sink, issue4) {
|
|
22966
|
+
sink.issues.push({ workflowId: sink.workflowId, ...issue4 });
|
|
22247
22967
|
}
|
|
22248
22968
|
|
|
22249
22969
|
// plugins/kxm/src/engine-plan.ts
|
|
@@ -22288,7 +23008,7 @@ var JOIN_OPTIONAL = ["minimumPassed", "cancelRemaining"];
|
|
|
22288
23008
|
var EVIDENCE_REQUIRED = ["key", "kind", "minimum", "reusableAcrossAttempts"];
|
|
22289
23009
|
var EVIDENCE_OPTIONAL = ["producerPolicy"];
|
|
22290
23010
|
function kxmSha256(input) {
|
|
22291
|
-
return `sha256:${
|
|
23011
|
+
return `sha256:${createHash8("sha256").update(input, "utf8").digest("hex")}`;
|
|
22292
23012
|
}
|
|
22293
23013
|
function kxmStepAskSha256(plan, stepId, agentId) {
|
|
22294
23014
|
const step = Object.hasOwn(plan.steps, stepId) ? plan.steps[stepId] : void 0;
|
|
@@ -22459,9 +23179,9 @@ function parseGates(raw, plan, runId) {
|
|
|
22459
23179
|
}
|
|
22460
23180
|
return { registry, definitions, controlRoot };
|
|
22461
23181
|
}
|
|
22462
|
-
function parseRegistryPin(raw, runId,
|
|
23182
|
+
function parseRegistryPin(raw, runId, required2) {
|
|
22463
23183
|
if (raw === null) {
|
|
22464
|
-
if (
|
|
23184
|
+
if (required2) throw runtimeError("run_plan_corrupt", runId, "gate steps require a non-null registry pin");
|
|
22465
23185
|
return null;
|
|
22466
23186
|
}
|
|
22467
23187
|
const value = asObject(raw, runId, "gates.registry");
|
|
@@ -22644,7 +23364,7 @@ function parseStep(raw, stepId, runId) {
|
|
|
22644
23364
|
repositories,
|
|
22645
23365
|
...value.tools !== void 0 ? { tools: asJsonObject(value.tools, runId, `${stepId}.tools`) } : {},
|
|
22646
23366
|
secrets: asObjectArray(value.secrets, runId, `${stepId}.secrets`).map((entry) => ({ ...entry })),
|
|
22647
|
-
...value.model !== void 0 ? { model:
|
|
23367
|
+
...value.model !== void 0 ? { model: cloneJson2(value.model) } : {},
|
|
22648
23368
|
requiredEvidence: asUnknownArray(value.requiredEvidence, runId, `${stepId}.requiredEvidence`).map((entry, index) => parseEvidence(entry, stepId, index, runId)),
|
|
22649
23369
|
outcomes,
|
|
22650
23370
|
transitions,
|
|
@@ -22782,9 +23502,9 @@ function asObject(value, runId, label) {
|
|
|
22782
23502
|
}
|
|
22783
23503
|
return value;
|
|
22784
23504
|
}
|
|
22785
|
-
function assertExactKeys(value,
|
|
22786
|
-
const allowed = /* @__PURE__ */ new Set([...
|
|
22787
|
-
for (const key of
|
|
23505
|
+
function assertExactKeys(value, required2, optional, runId, label) {
|
|
23506
|
+
const allowed = /* @__PURE__ */ new Set([...required2, ...optional]);
|
|
23507
|
+
for (const key of required2) {
|
|
22788
23508
|
if (!(key in value)) throw runtimeError("run_plan_corrupt", runId, `${label} is missing ${key}`);
|
|
22789
23509
|
}
|
|
22790
23510
|
for (const key of Object.keys(value)) {
|
|
@@ -22853,9 +23573,9 @@ function asJsonObject(value, runId, label) {
|
|
|
22853
23573
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
22854
23574
|
throw runtimeError("run_plan_corrupt", runId, `${label} is not an object`);
|
|
22855
23575
|
}
|
|
22856
|
-
return
|
|
23576
|
+
return cloneJson2(value);
|
|
22857
23577
|
}
|
|
22858
|
-
function
|
|
23578
|
+
function cloneJson2(value) {
|
|
22859
23579
|
return JSON.parse(JSON.stringify(value));
|
|
22860
23580
|
}
|
|
22861
23581
|
function sameSet(left, right) {
|
|
@@ -22964,8 +23684,8 @@ function verifyKxmGateEvidence(store, run, envelope2, events, state) {
|
|
|
22964
23684
|
throw runtimeError("gate_evidence_hash_mismatch", run.runId, "evidence outcome does not match the observation and expect");
|
|
22965
23685
|
}
|
|
22966
23686
|
const step = envelope2.plan.steps[row.stepId];
|
|
22967
|
-
const
|
|
22968
|
-
const expectedKey =
|
|
23687
|
+
const required2 = step?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
|
|
23688
|
+
const expectedKey = required2.length === 1 ? required2[0].key : null;
|
|
22969
23689
|
if (row.evidenceKey !== expectedKey) {
|
|
22970
23690
|
throw runtimeError("gate_evidence_unexpected", run.runId, "evidence_key does not match the declared requiredEvidence key");
|
|
22971
23691
|
}
|
|
@@ -23360,11 +24080,11 @@ function pump(storePath, owner) {
|
|
|
23360
24080
|
}
|
|
23361
24081
|
|
|
23362
24082
|
// plugins/kxm/src/runtime-service.ts
|
|
23363
|
-
function
|
|
24083
|
+
function compareCodeUnits6(left, right) {
|
|
23364
24084
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
23365
24085
|
}
|
|
23366
24086
|
function sha256Of(input) {
|
|
23367
|
-
return `sha256:${
|
|
24087
|
+
return `sha256:${createHash9("sha256").update(input, "utf8").digest("hex")}`;
|
|
23368
24088
|
}
|
|
23369
24089
|
function objectValue3(value) {
|
|
23370
24090
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -23412,9 +24132,9 @@ function collectMemoryFiles(dir, baseDir, ignoreSubdirs = /* @__PURE__ */ new Se
|
|
|
23412
24132
|
return results;
|
|
23413
24133
|
}
|
|
23414
24134
|
function computeKxmMemoryRevision(bundle, options = {}) {
|
|
23415
|
-
const hash =
|
|
24135
|
+
const hash = createHash9("sha256");
|
|
23416
24136
|
const memoryDir = options.memoryDir ?? join7(bundle.projectRoot, ".kxm", "memory");
|
|
23417
|
-
const memoryFiles = collectMemoryFiles(memoryDir, memoryDir, /* @__PURE__ */ new Set(["candidates"])).sort((left, right) =>
|
|
24137
|
+
const memoryFiles = collectMemoryFiles(memoryDir, memoryDir, /* @__PURE__ */ new Set(["candidates"])).sort((left, right) => compareCodeUnits6(left.relPath, right.relPath));
|
|
23418
24138
|
for (const file of memoryFiles) {
|
|
23419
24139
|
hash.update(`memory:${file.relPath}\0`, "utf8");
|
|
23420
24140
|
hash.update(readFileSync4(file.fullPath));
|
|
@@ -23422,14 +24142,14 @@ function computeKxmMemoryRevision(bundle, options = {}) {
|
|
|
23422
24142
|
}
|
|
23423
24143
|
const skillsDir = options.skillsDir ?? join7(bundle.projectRoot, ".kxm", "skills");
|
|
23424
24144
|
const promotedSkillsDir = join7(skillsDir, "promoted");
|
|
23425
|
-
const skillFiles = collectMemoryFiles(promotedSkillsDir, promotedSkillsDir).sort((left, right) =>
|
|
24145
|
+
const skillFiles = collectMemoryFiles(promotedSkillsDir, promotedSkillsDir).sort((left, right) => compareCodeUnits6(left.relPath, right.relPath));
|
|
23426
24146
|
for (const file of skillFiles) {
|
|
23427
24147
|
hash.update(`skill:${file.relPath}\0`, "utf8");
|
|
23428
24148
|
hash.update(readFileSync4(file.fullPath));
|
|
23429
24149
|
hash.update("\0", "utf8");
|
|
23430
24150
|
}
|
|
23431
24151
|
if (options.promotedState && options.promotedState.length > 0) {
|
|
23432
|
-
const currentItems = options.promotedState.filter((item) => !item.status || item.status === "current").slice().sort((left, right) =>
|
|
24152
|
+
const currentItems = options.promotedState.filter((item) => !item.status || item.status === "current").slice().sort((left, right) => compareCodeUnits6(left.id, right.id));
|
|
23433
24153
|
for (const item of currentItems) {
|
|
23434
24154
|
hash.update(`state:${item.id}\0`, "utf8");
|
|
23435
24155
|
hash.update(kxmCanonicalJson(item), "utf8");
|
|
@@ -23577,7 +24297,7 @@ function acceptKxmRun(context, bundle, request, options = {}) {
|
|
|
23577
24297
|
}
|
|
23578
24298
|
const revisions = kxmPolicyRevisions(bundle, options);
|
|
23579
24299
|
const memoryRevision = options.memoryRevision ?? revisions.memoryRevision;
|
|
23580
|
-
const promptSha256 = `sha256:${
|
|
24300
|
+
const promptSha256 = `sha256:${createHash9("sha256").update(request.prompt, "utf8").digest("hex")}`;
|
|
23581
24301
|
return context.eventStore.transaction(() => {
|
|
23582
24302
|
const prior = context.eventStore.command(commandId);
|
|
23583
24303
|
if (prior) {
|
|
@@ -23859,13 +24579,13 @@ function assertKxmConfigError(error) {
|
|
|
23859
24579
|
|
|
23860
24580
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
23861
24581
|
import { spawn as spawn3 } from "node:child_process";
|
|
23862
|
-
import { createHash as
|
|
24582
|
+
import { createHash as createHash16, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
23863
24583
|
import { chmodSync as chmodSync2, existsSync as existsSync16, lstatSync as lstatSync6, mkdirSync as mkdirSync9, readFileSync as readFileSync13, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
|
|
23864
24584
|
import { createServer } from "node:http";
|
|
23865
24585
|
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join18 } from "node:path";
|
|
23866
24586
|
|
|
23867
24587
|
// plugins/kxm/src/oneshot-evidence.ts
|
|
23868
|
-
import { createHash as
|
|
24588
|
+
import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
|
|
23869
24589
|
import { lstatSync as lstatSync4, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
23870
24590
|
import { lstat, mkdir, open, rename } from "node:fs/promises";
|
|
23871
24591
|
import { join as join8 } from "node:path";
|
|
@@ -23874,7 +24594,7 @@ var RETIRED_ONESHOT_EVIDENCE_SCHEMAS = /* @__PURE__ */ new Set(["kxm.oneshot-evi
|
|
|
23874
24594
|
var TEXT_LIMIT = 4 * 1024 * 1024;
|
|
23875
24595
|
var ARGV_LIMIT = 64 * 1024;
|
|
23876
24596
|
var RECORD_LIMIT = 16 * 1024 * 1024;
|
|
23877
|
-
var digest = (value) => `sha256:${
|
|
24597
|
+
var digest = (value) => `sha256:${createHash10("sha256").update(value).digest("hex")}`;
|
|
23878
24598
|
function parseOneShotEvidenceRecord(bytes) {
|
|
23879
24599
|
let parsed;
|
|
23880
24600
|
try {
|
|
@@ -24016,7 +24736,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
24016
24736
|
|
|
24017
24737
|
// plugins/kxm/src/prices.ts
|
|
24018
24738
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
24019
|
-
import { createHash as
|
|
24739
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
24020
24740
|
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
24021
24741
|
import { join as join9 } from "node:path";
|
|
24022
24742
|
|
|
@@ -24079,7 +24799,7 @@ function hashPriceCatalog(catalog) {
|
|
|
24079
24799
|
}))
|
|
24080
24800
|
})).sort((a, b) => a.id.localeCompare(b.id))
|
|
24081
24801
|
};
|
|
24082
|
-
return
|
|
24802
|
+
return createHash11("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
|
|
24083
24803
|
}
|
|
24084
24804
|
function parsePriceCatalog(text) {
|
|
24085
24805
|
const parsed = (0, import_yaml3.parse)(text);
|
|
@@ -24127,9 +24847,9 @@ function parsePriceCatalog(text) {
|
|
|
24127
24847
|
throw new Error(`tier at index ${tIndex} for model ${m.id} must be an object`);
|
|
24128
24848
|
}
|
|
24129
24849
|
const t = tItem;
|
|
24130
|
-
const rate = (field, short,
|
|
24850
|
+
const rate = (field, short, required2 = false) => {
|
|
24131
24851
|
const value = t[field] !== void 0 ? t[field] : t[short];
|
|
24132
|
-
if (value == null && !
|
|
24852
|
+
if (value == null && !required2) return null;
|
|
24133
24853
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
24134
24854
|
throw new Error(`tier ${tIndex} for model ${m.id} requires finite non-negative ${field}`);
|
|
24135
24855
|
}
|
|
@@ -24191,22 +24911,30 @@ function loadPriceCatalogForEstimate(options) {
|
|
|
24191
24911
|
}
|
|
24192
24912
|
|
|
24193
24913
|
// plugins/kxm/src/engine.ts
|
|
24194
|
-
var
|
|
24195
|
-
import { createHash as
|
|
24914
|
+
var import_yaml9 = __toESM(require_dist(), 1);
|
|
24915
|
+
import { createHash as createHash15, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
24196
24916
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
24197
24917
|
import { join as join15 } from "node:path";
|
|
24198
24918
|
|
|
24199
24919
|
// plugins/kxm/src/routes.ts
|
|
24200
|
-
var
|
|
24920
|
+
var import_yaml6 = __toESM(require_dist(), 1);
|
|
24201
24921
|
import { existsSync as existsSync9, readFileSync as readFileSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readdirSync as readdirSync5 } from "node:fs";
|
|
24202
24922
|
import { join as join10 } from "node:path";
|
|
24923
|
+
|
|
24924
|
+
// plugins/kxm/src/role.ts
|
|
24925
|
+
var import_yaml5 = __toESM(require_dist(), 1);
|
|
24926
|
+
|
|
24927
|
+
// plugins/kxm/src/config.ts
|
|
24928
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
24929
|
+
|
|
24930
|
+
// plugins/kxm/src/routes.ts
|
|
24203
24931
|
var RETIRED_POLICY = ".kxm/producers.yaml";
|
|
24204
24932
|
var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
|
|
24205
24933
|
function loadRoutePolicy(root) {
|
|
24206
24934
|
if (existsSync9(join10(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
|
|
24207
24935
|
const path = join10(root, ".kxm", "routes.yaml");
|
|
24208
24936
|
if (!existsSync9(path)) return empty();
|
|
24209
|
-
const value = (0,
|
|
24937
|
+
const value = (0, import_yaml6.parse)(readFileSync7(path, "utf8"));
|
|
24210
24938
|
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
24211
24939
|
return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x) => typeof x === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x) => typeof x === "string") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v]) => Array.isArray(v)).map(([k, v]) => [k, v.filter((x) => typeof x === "string")])) : {} };
|
|
24212
24940
|
}
|
|
@@ -24216,8 +24944,8 @@ function listRoleBindings(root) {
|
|
|
24216
24944
|
if (!existsSync9(dir)) return result;
|
|
24217
24945
|
for (const file of readdirSync5(dir).filter((name) => name.endsWith(".yaml"))) {
|
|
24218
24946
|
const role = file.slice(0, -5);
|
|
24219
|
-
const value = (0,
|
|
24220
|
-
result[role] = (value.roster ?? []).map((entry) => entry.
|
|
24947
|
+
const value = (0, import_yaml6.parse)(readFileSync7(join10(dir, file), "utf8"));
|
|
24948
|
+
result[role] = (value.roster ?? []).map((entry) => entry.route).filter((route) => typeof route === "string");
|
|
24221
24949
|
}
|
|
24222
24950
|
return result;
|
|
24223
24951
|
}
|
|
@@ -24639,8 +25367,8 @@ function oneOf(value, field, allowed) {
|
|
|
24639
25367
|
}
|
|
24640
25368
|
return value;
|
|
24641
25369
|
}
|
|
24642
|
-
function idRefs(value, field,
|
|
24643
|
-
if (value === void 0 || value === null) return
|
|
25370
|
+
function idRefs(value, field, required2 = false) {
|
|
25371
|
+
if (value === void 0 || value === null) return required2 ? [] : void 0;
|
|
24644
25372
|
if (!Array.isArray(value)) {
|
|
24645
25373
|
throw new ProtocolError(400, `${field} must be an array of identifiers`, "invalid_context_field");
|
|
24646
25374
|
}
|
|
@@ -25175,7 +25903,7 @@ function memoryRecordToContextItem(record2, project) {
|
|
|
25175
25903
|
}
|
|
25176
25904
|
|
|
25177
25905
|
// plugins/kxm/src/memory.ts
|
|
25178
|
-
var
|
|
25906
|
+
var import_yaml7 = __toESM(require_dist(), 1);
|
|
25179
25907
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
25180
25908
|
import { extname as extname2, join as join11, resolve as resolve7 } from "node:path";
|
|
25181
25909
|
var MEMORY_SCHEMA = "kxm.memory.v1";
|
|
@@ -25207,7 +25935,7 @@ function parseFrontmatter(content) {
|
|
|
25207
25935
|
}
|
|
25208
25936
|
function parseMemoryRecord(raw, filename = "memory.md") {
|
|
25209
25937
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
25210
|
-
const data = (0,
|
|
25938
|
+
const data = (0, import_yaml7.parse)(frontmatter);
|
|
25211
25939
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
25212
25940
|
throw new Error(`invalid YAML frontmatter in ${filename}`);
|
|
25213
25941
|
}
|
|
@@ -25303,8 +26031,8 @@ function loadAuthoredMemory(repoRoot) {
|
|
|
25303
26031
|
}
|
|
25304
26032
|
|
|
25305
26033
|
// plugins/kxm/src/skills.ts
|
|
25306
|
-
var
|
|
25307
|
-
import { createHash as
|
|
26034
|
+
var import_yaml8 = __toESM(require_dist(), 1);
|
|
26035
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
25308
26036
|
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25309
26037
|
import { dirname as dirname7, join as join12 } from "node:path";
|
|
25310
26038
|
var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
|
|
@@ -25329,7 +26057,7 @@ var SkillLifecycleError = class extends Error {
|
|
|
25329
26057
|
}
|
|
25330
26058
|
};
|
|
25331
26059
|
function skillContentSha256(content) {
|
|
25332
|
-
return
|
|
26060
|
+
return createHash12("sha256").update(content, "utf8").digest("hex");
|
|
25333
26061
|
}
|
|
25334
26062
|
function skillIdFor(name, contentSha256) {
|
|
25335
26063
|
const slug = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -25347,7 +26075,7 @@ function parseSkillFrontmatter(content) {
|
|
|
25347
26075
|
return { frontmatter: null, body: content };
|
|
25348
26076
|
}
|
|
25349
26077
|
try {
|
|
25350
|
-
const parsed = (0,
|
|
26078
|
+
const parsed = (0, import_yaml8.parse)(rawFm);
|
|
25351
26079
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
25352
26080
|
return { frontmatter: parsed, body: rawBody };
|
|
25353
26081
|
}
|
|
@@ -25972,7 +26700,7 @@ function evaluateArtifactsGate(context, definition) {
|
|
|
25972
26700
|
|
|
25973
26701
|
// plugins/kxm/src/engine-command.ts
|
|
25974
26702
|
import { spawn as spawn2 } from "node:child_process";
|
|
25975
|
-
import { createHash as
|
|
26703
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
25976
26704
|
|
|
25977
26705
|
// plugins/kxm/src/safety-integrity.ts
|
|
25978
26706
|
var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
|
|
@@ -26020,7 +26748,7 @@ function createCommandObserver(input) {
|
|
|
26020
26748
|
return new CommandObserver(input);
|
|
26021
26749
|
}
|
|
26022
26750
|
var StreamDigest = class {
|
|
26023
|
-
hash =
|
|
26751
|
+
hash = createHash13("sha256");
|
|
26024
26752
|
bytes = 0;
|
|
26025
26753
|
complete = 0;
|
|
26026
26754
|
digest() {
|
|
@@ -26760,8 +27488,8 @@ function appendEvaluatedTail(seq, envelope2, ids, stepId, outcome) {
|
|
|
26760
27488
|
}
|
|
26761
27489
|
}
|
|
26762
27490
|
function evidenceKeyFor(envelope2, stepId) {
|
|
26763
|
-
const
|
|
26764
|
-
return
|
|
27491
|
+
const required2 = envelope2.plan.steps[stepId]?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
|
|
27492
|
+
return required2.length === 1 ? required2[0].key : null;
|
|
26765
27493
|
}
|
|
26766
27494
|
function placeholderReceipt(observationId, kind, now) {
|
|
26767
27495
|
return {
|
|
@@ -26867,7 +27595,7 @@ function hashFromStore(context, runId) {
|
|
|
26867
27595
|
}
|
|
26868
27596
|
|
|
26869
27597
|
// plugins/kxm/src/routing.ts
|
|
26870
|
-
import { createHash as
|
|
27598
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
26871
27599
|
var ROUTING_RECORD_V2_SCHEMA = "kxm.routing-record.v2";
|
|
26872
27600
|
var BEHAVIORAL_HASH_VERSION = 1;
|
|
26873
27601
|
var MAX_PROVIDER_METADATA_FIELDS = 32;
|
|
@@ -26890,7 +27618,7 @@ function behavioralConfigHash(input) {
|
|
|
26890
27618
|
workflowDefinitionSha256: input.workflowDefinitionSha256?.trim() ?? null,
|
|
26891
27619
|
verifierConfigSha256: input.verifierConfigSha256?.trim() ?? null
|
|
26892
27620
|
};
|
|
26893
|
-
return
|
|
27621
|
+
return createHash14("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
|
|
26894
27622
|
}
|
|
26895
27623
|
function boundedInt(value, field) {
|
|
26896
27624
|
if (value === void 0 || value === null) return void 0;
|
|
@@ -27066,7 +27794,7 @@ function requireTrustedProducer(producer) {
|
|
|
27066
27794
|
}
|
|
27067
27795
|
}
|
|
27068
27796
|
function hashCapabilitySecret(secret) {
|
|
27069
|
-
return `sha256:${
|
|
27797
|
+
return `sha256:${createHash15("sha256").update(`${CAPABILITY_PREFIX}${secret}`, "utf8").digest("hex")}`;
|
|
27070
27798
|
}
|
|
27071
27799
|
function mintKxmCapabilitySecret() {
|
|
27072
27800
|
const secret = `kxmcap_${randomBytes2(32).toString("base64url")}`;
|
|
@@ -27199,7 +27927,7 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
|
|
|
27199
27927
|
return latest;
|
|
27200
27928
|
}
|
|
27201
27929
|
function newDriveId(runId, token, runtimeId, monotonicNs) {
|
|
27202
|
-
return `drv_${
|
|
27930
|
+
return `drv_${createHash15("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
|
|
27203
27931
|
}
|
|
27204
27932
|
function driveErrorSummary(error) {
|
|
27205
27933
|
const raw = error instanceof KxmConfigError ? error.issues[0]?.code : void 0;
|
|
@@ -27813,6 +28541,26 @@ function commitCommandUncertainty(context, prepared, token, reason, observation,
|
|
|
27813
28541
|
}
|
|
27814
28542
|
}
|
|
27815
28543
|
var kxmPanelDispatchSeams = {};
|
|
28544
|
+
function selectorsNamedByRoutes(projectRoot, routeIds) {
|
|
28545
|
+
const selectors = /* @__PURE__ */ new Set();
|
|
28546
|
+
for (const routeId2 of routeIds) {
|
|
28547
|
+
const file = join15(projectRoot, ".kxm", "models", `${routeId2}.yaml`);
|
|
28548
|
+
if (!existsSync12(file)) continue;
|
|
28549
|
+
let parsed;
|
|
28550
|
+
try {
|
|
28551
|
+
parsed = (0, import_yaml9.parse)(readFileSync10(file, "utf8"));
|
|
28552
|
+
} catch {
|
|
28553
|
+
continue;
|
|
28554
|
+
}
|
|
28555
|
+
const model = typeof parsed.model === "string" ? parsed.model : "";
|
|
28556
|
+
const vendor = typeof parsed.vendor === "string" ? parsed.vendor : "";
|
|
28557
|
+
const harness = typeof parsed.harness === "string" ? parsed.harness : "";
|
|
28558
|
+
if (model) selectors.add(model);
|
|
28559
|
+
if (vendor && model) selectors.add(`${vendor}/${model}`);
|
|
28560
|
+
if (harness && model) selectors.add(`${harness}/${model}`);
|
|
28561
|
+
}
|
|
28562
|
+
return [...selectors];
|
|
28563
|
+
}
|
|
27816
28564
|
function unreconciledPanelAttemptId(state) {
|
|
27817
28565
|
const current = state.currentStep;
|
|
27818
28566
|
if (!current) return void 0;
|
|
@@ -27832,7 +28580,7 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
27832
28580
|
const agentFile = join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
|
|
27833
28581
|
if (existsSync12(agentFile)) {
|
|
27834
28582
|
try {
|
|
27835
|
-
const parsed = (0,
|
|
28583
|
+
const parsed = (0, import_yaml9.parse)(readFileSync10(agentFile, "utf8"));
|
|
27836
28584
|
if (typeof parsed?.model === "string") {
|
|
27837
28585
|
agentModel = parsed.model;
|
|
27838
28586
|
} else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
|
|
@@ -27891,7 +28639,8 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
27891
28639
|
const role = agentId === "implementer" ? "writer" : agentId;
|
|
27892
28640
|
const roleBindings = listRoleBindings(projectRoot);
|
|
27893
28641
|
const roster = roleBindings[role];
|
|
27894
|
-
|
|
28642
|
+
const rosterSelectors = roster ? selectorsNamedByRoutes(projectRoot, roster) : void 0;
|
|
28643
|
+
if (rosterSelectors && !rosterSelectors.includes(selector)) {
|
|
27895
28644
|
return {
|
|
27896
28645
|
error: {
|
|
27897
28646
|
reason: "step_unsupported",
|
|
@@ -28212,7 +28961,7 @@ function birthMember(context, input) {
|
|
|
28212
28961
|
if (promptText === void 0) {
|
|
28213
28962
|
throw runtimeError("run_prompt_mismatch", run.runId, "prompt text missing from accepted run prompt store");
|
|
28214
28963
|
}
|
|
28215
|
-
const promptHash =
|
|
28964
|
+
const promptHash = createHash15("sha256").update(promptText, "utf8").digest("hex");
|
|
28216
28965
|
const expectedHash = run.promptSha256.replace(/^sha256:/, "");
|
|
28217
28966
|
if (promptHash !== expectedHash) {
|
|
28218
28967
|
throw runtimeError("run_prompt_mismatch", run.runId, "prompt text does not match accepted promptSha256");
|
|
@@ -29115,7 +29864,7 @@ function unsupportedStep(plan, step, producerId) {
|
|
|
29115
29864
|
function readYamlRecord(path) {
|
|
29116
29865
|
if (!existsSync12(path)) return void 0;
|
|
29117
29866
|
try {
|
|
29118
|
-
const parsed = (0,
|
|
29867
|
+
const parsed = (0, import_yaml9.parse)(readFileSync10(path, "utf8"));
|
|
29119
29868
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
29120
29869
|
} catch {
|
|
29121
29870
|
return void 0;
|
|
@@ -30353,7 +31102,7 @@ function publishKxmSupervisorToken(paths, token) {
|
|
|
30353
31102
|
}
|
|
30354
31103
|
}
|
|
30355
31104
|
function hashKxmSupervisorToken(token) {
|
|
30356
|
-
return `sha256:${
|
|
31105
|
+
return `sha256:${createHash16("sha256").update(`kxm-runtime-supervisor\0${token}`, "utf8").digest("hex")}`;
|
|
30357
31106
|
}
|
|
30358
31107
|
function readKxmSupervisorToken(paths) {
|
|
30359
31108
|
const file = kxmSupervisorTokenFile(paths);
|
|
@@ -30716,7 +31465,7 @@ async function startKxmRuntimeSupervisor(options = {}) {
|
|
|
30716
31465
|
try {
|
|
30717
31466
|
return await startKxmRuntimeSupervisorInner(paths, options.port, now, env);
|
|
30718
31467
|
} catch (error) {
|
|
30719
|
-
if (!(error instanceof KxmConfigError && error.issues.some((
|
|
31468
|
+
if (!(error instanceof KxmConfigError && error.issues.some((issue4) => issue4.code === "runtime_supervisor_conflict"))) {
|
|
30720
31469
|
recordSupervisorError(paths, error.message);
|
|
30721
31470
|
}
|
|
30722
31471
|
throw error;
|
|
@@ -30734,7 +31483,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
30734
31483
|
const token = randomBytes3(32).toString("hex");
|
|
30735
31484
|
const tokenHash = hashKxmSupervisorToken(token);
|
|
30736
31485
|
const registry = new KxmRuntimeRegistry(paths.registryDb);
|
|
30737
|
-
const runtimeId = `rtm_${
|
|
31486
|
+
const runtimeId = `rtm_${createHash16("sha256").update(`${paths.stateRoot}\0${process.pid}\0${now()}\0${randomBytes3(16).toString("hex")}`, "utf8").digest("hex").slice(0, 24)}`;
|
|
30738
31487
|
const requestedPort = requestedPortOption ?? 0;
|
|
30739
31488
|
let activeRuntimeId = runtimeId;
|
|
30740
31489
|
const contexts = /* @__PURE__ */ new Map();
|
|
@@ -31809,7 +32558,7 @@ function createKxmPiProducer(options = {}) {
|
|
|
31809
32558
|
}
|
|
31810
32559
|
|
|
31811
32560
|
// plugins/kxm/src/improve.ts
|
|
31812
|
-
import { createHash as
|
|
32561
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
31813
32562
|
import { existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "node:fs";
|
|
31814
32563
|
import { join as join20, relative as relative5, resolve as resolve11 } from "node:path";
|
|
31815
32564
|
var CANDIDATE_SCHEMA = "kxm.candidate.v1";
|
|
@@ -32027,7 +32776,7 @@ function groupRoutingRecords(records, options = {}) {
|
|
|
32027
32776
|
}
|
|
32028
32777
|
const candidateKind = isCandidate ? classifyCandidateKind(group.stepId, group.agentRole) : void 0;
|
|
32029
32778
|
const safeSlug = group.stepId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 16) || "step";
|
|
32030
|
-
const keyHash =
|
|
32779
|
+
const keyHash = createHash17("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
|
|
32031
32780
|
const candidateId = isCandidate && candidateKind ? `cand_${candidateKind.replace(/-/g, "_")}_${safeSlug}_${keyHash}` : void 0;
|
|
32032
32781
|
keyed.push({
|
|
32033
32782
|
key: group.key,
|
|
@@ -32639,7 +33388,7 @@ Instructions for Operator:
|
|
|
32639
33388
|
};
|
|
32640
33389
|
|
|
32641
33390
|
// plugins/kxm/src/modes.ts
|
|
32642
|
-
var
|
|
33391
|
+
var import_yaml10 = __toESM(require_dist(), 1);
|
|
32643
33392
|
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
|
|
32644
33393
|
import { join as join21 } from "node:path";
|
|
32645
33394
|
var DEFAULT_MODES_CONFIG = Object.freeze({
|
|
@@ -32710,7 +33459,7 @@ function loadModesConfig(projectRoot) {
|
|
|
32710
33459
|
}
|
|
32711
33460
|
try {
|
|
32712
33461
|
const raw = readFileSync15(modesPath, "utf8");
|
|
32713
|
-
const parsed = (0,
|
|
33462
|
+
const parsed = (0, import_yaml10.parse)(raw);
|
|
32714
33463
|
if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
|
|
32715
33464
|
return {
|
|
32716
33465
|
schema: "kxm.modes.v1",
|