@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.
Files changed (55) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/roles/planner.yaml +6 -7
  3. package/.kxm/roles/reviewer-arch.yaml +6 -7
  4. package/.kxm/roles/reviewer-cli.yaml +6 -7
  5. package/.kxm/roles/writer.yaml +8 -17
  6. package/CHANGELOG.md +17 -0
  7. package/docs/architecture/access.md +2 -4
  8. package/docs/contracts/validation.md +1 -1
  9. package/docs/contributing/harness-routing-internals.md +1 -1
  10. package/docs/contributing/test-matrix.md +1 -1
  11. package/docs/glossary.md +1 -1
  12. package/docs/guides/agent-skills.md +1 -1
  13. package/docs/operations/backup-and-restore.md +2 -2
  14. package/docs/reference/cli-reference.md +31 -74
  15. package/docs/reference/config-reference.md +83 -118
  16. package/docs/reference/harness-routing.md +15 -8
  17. package/examples/project/.kxm/models/critic-claude.yaml +6 -2
  18. package/examples/project/.kxm/models/critic-gemini.yaml +6 -2
  19. package/examples/project/.kxm/models/critic-grok.yaml +6 -2
  20. package/examples/project/.kxm/models/implementation.yaml +6 -2
  21. package/examples/project/.kxm/models/primary.yaml +6 -2
  22. package/examples/project/.kxm/roles/planner.yaml +8 -0
  23. package/examples/project/.kxm/roles/reviewer-arch.yaml +8 -0
  24. package/examples/project/.kxm/roles/reviewer-cli.yaml +8 -0
  25. package/examples/project/.kxm/roles/writer.yaml +8 -0
  26. package/package.json +1 -1
  27. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  28. package/plugins/kxm/dist/cli.js +1759 -1306
  29. package/plugins/kxm/dist/mcp-server.js +1 -1
  30. package/plugins/kxm/dist/runtime-supervisor.js +994 -245
  31. package/plugins/kxm/dist/runtime.js +1018 -269
  32. package/plugins/kxm/dist/server.js +55 -1
  33. package/plugins/kxm/package.json +1 -1
  34. package/plugins/kxm/skills/kxm/SKILL.md +1 -1
  35. package/plugins/kxm/skills/kxm-definitions/SKILL.md +3 -7
  36. package/plugins/kxm/src/autocomplete.ts +1 -1
  37. package/plugins/kxm/src/cli/project.ts +7 -1
  38. package/plugins/kxm/src/cli/roles.ts +46 -150
  39. package/plugins/kxm/src/cli.ts +8 -22
  40. package/plugins/kxm/src/engine.ts +24 -1
  41. package/plugins/kxm/src/init-guide-setup.ts +38 -4
  42. package/plugins/kxm/src/mcp-server.ts +1 -1
  43. package/plugins/kxm/src/permission.ts +11 -0
  44. package/plugins/kxm/src/policy-draft.mjs +56 -16
  45. package/plugins/kxm/src/project-config.ts +121 -10
  46. package/plugins/kxm/src/repair.ts +1 -0
  47. package/plugins/kxm/src/role.ts +40 -440
  48. package/plugins/kxm/src/routes.ts +28 -5
  49. package/plugins/kxm/src/template.ts +34 -1
  50. package/schemas/README.md +2 -1
  51. package/schemas/model.schema.json +111 -13
  52. package/schemas/role.schema.json +61 -31
  53. package/schemas/policy-draft/README.md +0 -17
  54. package/schemas/policy-draft/model.v2.schema.json +0 -140
  55. 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 stringify5(x) {
166
+ function stringify7(x) {
167
167
  return new _Code(safeStringify(x));
168
168
  }
169
- exports.stringify = stringify5;
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
- parse8(serialize(uri, options), options);
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 = parse8(serialize(base, options), options);
3878
- relative6 = parse8(serialize(relative6, options), options);
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 parse8(uri, opts) {
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: parse8
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(required) && required.includes(tagName);
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 stringify5(item, ctx, onComment, onChompKeep) {
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 = stringify5;
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 stringify5 = require_stringify();
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 = stringify5.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
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 = stringify5.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
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 stringify5 = require_stringify();
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 = stringify5.createStringifyContext(ctx.doc, {});
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 stringify5 = require_stringify();
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 stringify6 = flow ? stringifyFlowCollection : stringifyBlockCollection;
9282
- return stringify6(collection, ctx, options);
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 = stringify5.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
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 = stringify5.stringify(item, itemCtx, () => comment = null);
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 stringify5 = require_stringify();
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 = stringify5.createStringifyContext(doc, options);
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 = stringify5.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
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(stringify5.stringify(doc.contents, ctx));
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 stringify5 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
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 = stringify5;
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 parseDocument2(source, options = {}) {
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 parse8(src, reviver, options) {
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 = parseDocument2(src, options);
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 stringify5(value, replacer, options) {
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 = parse8;
14721
+ exports.parse = parse10;
14722
14722
  exports.parseAllDocuments = parseAllDocuments;
14723
- exports.parseDocument = parseDocument2;
14724
- exports.stringify = stringify5;
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 createHash8 } from "node:crypto";
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 createHash2 } from "node:crypto";
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 compareCodeUnits2(left, right) {
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(compareCodeUnits2).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
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:${createHash("sha256").update(input).digest("hex")}`;
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
- roles: { implementer: ["xai/grok-4.6"] }
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) => compareCodeUnits2(left.path, right.path));
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]) => compareCodeUnits2(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((issue3) => issue3 === "auth_context_required" || issue3 === "auth_unknown" || issue3 === "auth_unparsed" || issue3.startsWith("auth_"));
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 = sortIssues2(issues);
16505
- super(sorted.map((issue3) => `${issue3.file}: ${issue3.code}: ${issue3.message}`).join("\n"));
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.v1", file: "model.schema.json" },
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 IDENTIFIER = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
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 compareCodeUnits3(left, right) {
17153
+ function compareCodeUnits4(left, right) {
16536
17154
  return left < right ? -1 : left > right ? 1 : 0;
16537
17155
  }
16538
- function sortIssues2(issues) {
16539
- return [...issues].sort((left, right) => compareCodeUnits3(left.file, right.file) || compareCodeUnits3(left.phase, right.phase) || compareCodeUnits3(left.code, right.code) || compareCodeUnits3(left.message, right.message));
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 issue2(phase, code, file, message) {
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([issue2(phase, code, file, message)]);
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 [issue2("schema", "schema_identity_mismatch", file, `expected ${definition.identity}, received ${String(value.schema)}`)];
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 [issue2("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
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([issue2(
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([issue2(
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([issue2(
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([issue2("schema", "coordinator_invalid", file, "expected kxm.coordinator.v1")]);
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([issue2(
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([issue2("schema", "intake_message_invalid", file, "expected kxm.intake-message.v1")]);
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([issue2(
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 issue2("schema", `schema_${error.keyword}`, file, `${location} ${error.message ?? "is invalid"}${suffix}`);
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 issue2("path", "repository_binding_path_link", ".kxm/project.yaml", `portable pathHint for ${repositoryId} traverses a symbolic link or junction`);
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 issue2("path", "repository_binding_outside_project", ".kxm/project.yaml", `portable pathHint for ${repositoryId} resolves outside the authoritative project root`);
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 && IDENTIFIER.test(id) && !WINDOWS_RESERVED.test(id);
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 && compareCodeUnits3(prior, path) >= 0) fail2("semantic", "template_provenance_order_invalid", label, "managed file entries must use strict code-unit order");
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) => compareCodeUnits3(left.name, right.name));
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(issue2("path", "nested_resource_directory", displayPath(root, join2(directory, entry.name)), "nested resource directories are not allowed"));
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(issue2("path", "resource_filename_invalid", displayPath(root, join2(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
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(issue2("path", "resource_id_invalid", displayPath(root, join2(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
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(issue2("path", "resource_id_collision", displayPath(root, join2(directory, entry.name)), `${id} case-folds to existing ${collision}`));
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(issue2("path", "resource_id_collision", resource.logicalPath, `${id} case-folds to existing ${collision}`));
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(issue2("path", "portable_path_invalid", resource.logicalPath, `${candidate.label} is not a normalized forward-slash relative path: ${candidate.path}`));
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(issue2("reference", "model_profile_unknown", file, `${label} references unknown model profile ${profile}`));
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(issue2("reference", "model_tag_unresolved", file, `${label} selector tag ${tag} has no matching profile`));
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(issue2("semantic", "secret_name_duplicate", resource.logicalPath, `secret environment name ${name} is duplicated`));
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(issue2("semantic", "environment_name_conflict", resource.logicalPath, `${name} appears in both values and secrets`));
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(issue2("semantic", "probable_secret_value", resource.logicalPath, `${name} resembles a credential and must be represented by secrets[].ref`));
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(issue2("semantic", "tool_scope_expansion", file, `${stepId} requests tools but agent ${agent.id ?? "unknown"} declares no tool ceiling`));
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(issue2("semantic", "tool_scope_expansion", file, `${stepId} tool preset ${String(requestedPreset)} does not preserve agent ${agent.id ?? "unknown"} preset ${String(ceilingPreset)}`));
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(issue2("semantic", "tool_scope_expansion", file, `${stepId} requests tool ${tool} beyond agent ${agent.id ?? "unknown"} explicit allowlist`));
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(issue2("semantic", "tool_scope_expansion", file, `${stepId} drops agent ${agent.id ?? "unknown"} denial for ${tool}`));
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(issue2("semantic", "tool_policy_contradiction", resource.logicalPath, `${label} both allows and denies ${tool}`));
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(issue2("semantic", "repository_scope_expansion", file, `${stepId} requests ${String(requested)} access to ${repositoryId} beyond agent ${agent.id ?? "unknown"} ceiling ${String(ceiling)}`));
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(issue2("semantic", "secret_scope_expansion", file, `${stepId} requests secret ${reference} beyond agent ${agent.id ?? "unknown"} ceiling`));
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 issue2("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 ")}`);
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(issue2("reference", "coordinator_unknown", file, `coordinator ${coordinator} does not exist`));
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(issue2("semantic", "step_id_duplicate", file, `step ${id} is duplicated`));
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(issue2("reference", "oracle_stage_unknown", file, `${field} references unknown stage ${String(stageId)}`));
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(issue2("reference", "oracle_evidence_unknown", file, `${field} references undeclared evidence ${String(evidenceKey)} in ${stageId}`));
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(issue2("semantic", "plan_hash_missing", file, "requirePlanHash requires planHash"));
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(issue2("reference", "plan_hash_stage_unknown", file, `requirePlanHash references unknown stage ${stageId}`));
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(issue2("reference", "agent_unknown", file, `${stepId} references unknown agent ${primaryAgentId}`));
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(issue2("reference", "gate_registry_missing", file, `${stepId} requires .kxm/gates.yaml`));
17161
- else if (!gates.has(gate)) issues.push(issue2("reference", "gate_unknown", file, `${stepId} references unregistered gate ${gate}`));
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(issue2("semantic", "gate_expect_invalid", file, `${stepId} expect is gate-only pass or fail`));
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(issue2("semantic", "gate_outcome_renamed", file, `${stepId} must use implementation-failure and repro-missing`));
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(issue2("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
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(issue2("reference", "assignment_agent_unknown", file, `${stepId} allows unknown agent ${agentId}`));
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(issue2("semantic", "model_selector_incompatible", file, `${stepId} model selector has no candidate within agent ${agentId} ceiling`));
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(issue2("semantic", "primary_agent_ineligible", file, `${stepId} primary agent ${primaryAgentId} is not in allowedAgents`));
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(issue2("semantic", "assignment_bounds_invalid", file, `${stepId} must satisfy minimum <= target <= maximum`));
17201
- if (maxParallel > maximum) issues.push(issue2("semantic", "assignment_parallelism_invalid", file, `${stepId} maxParallel exceeds maximum`));
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(issue2("semantic", "assignment_pool_too_small", file, `${stepId} maximum ${maximum} exceeds ${allowedAgents.length} eligible agents`));
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(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
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(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
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(issue2("semantic", "model_diversity_impossible", file, `${stepId} requires ${target} distinct ${dimension} values but resolves ${distinct.size}`));
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(issue2("semantic", "evidence_key_duplicate", file, `${stepId} evidence key ${key} is duplicated`));
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(issue2("semantic", "producer_minimum_impossible", file, `${stepId}.${String(key)} producer minimum exceeds its eligible or target pool`));
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(issue2("reference", "producer_agent_unknown", file, `${stepId}.${String(key)} references unknown producer ${agentId}`));
17237
- if (allowedAgents.length > 0 && !allowedAgents.includes(agentId)) issues.push(issue2("semantic", "producer_agent_ineligible", file, `${stepId}.${String(key)} producer ${agentId} is outside allowedAgents`));
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(issue2("semantic", "producer_degradation_invalid", file, `${stepId}.${String(key)} degradation must lower minimumProducers`));
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(issue2("semantic", "mutation_missing_plan_hash", file, `${stepId} mutates after the approved-plan stage but is absent from requirePlanHash`));
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(issue2("semantic", "step_transitions_missing", file, `${stepId} has no declared transitions`));
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(issue2("reference", "transition_target_unknown", file, `${stepId}.${outcome} references unknown target ${String(parsed.target)}`));
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(issue2("semantic", "back_edge_unbounded", file, `${stepId}.${outcome} back-edge requires maxTransitions`));
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(issue2("semantic", "workflow_cycle_unbounded", file, "workflow with back-edges requires limits.maxTransitions"));
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(issue2("semantic", "verify_must_precede_ready", file, "verify must transition to ready"));
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(issue2("semantic", "verify_must_precede_ready", file, `step ${String(steps[srcIndex]?.id)} transitions to ready bypassing verify`));
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(issue2("semantic", "step_unreachable", file, `step ${String(step.id)} is unreachable from the first step`));
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(issue2("semantic", "required_step_bypass", file, `a completed path can bypass required ${String(requiredStep.id)}`));
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(issue2("semantic", "step_cannot_terminate", file, `step ${String(steps[index]?.id)} has no path to a terminal transition`));
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(issue2("semantic", "model_fallback_cycle", models.get(id)?.logicalPath ?? ".kxm/models", `fallback profile cycle: ${[...path, id].join(" -> ")}`));
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(issue2("path", "repository_id_collision", project.logicalPath, `${id} case-folds to existing ${previous}`));
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(issue2("semantic", "control_repository_count", project.logicalPath, `expected exactly one control repository, received ${controls}`));
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(issue2("reference", "repository_project_mismatch", repository.logicalPath, "projectId does not match the authoritative project"));
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(issue2("reference", "repository_definition_unknown", repository.logicalPath, `repositoryId ${String(id)} is not bound by project.yaml`));
17377
- if (repository.id !== id) issues.push(issue2("reference", "repository_binding_identity_mismatch", repository.logicalPath, `binding ${String(repository.id)} declares repositoryId ${String(id)}`));
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(issue2("semantic", "gate_executable_invalid", ".kxm/gates.yaml", `${id} argv[0] must be a bare executable or absolute POSIX path`));
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(issue2("reference", "executor_unknown", project.logicalPath, `defaultExecutor ${defaultExecutor} is not registered`));
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(issue2("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
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(issue2("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
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(issue2("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
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(issue2("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
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(issue2("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
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(issue2("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
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(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
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.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
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(issue2("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}`));
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
- return sortIssues2(issues);
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 = createHash2("sha256");
17463
- for (const resource of [...resources].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath))) {
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([issue2("semantic", "registered_gates_removed", ".kxm/gates.yaml", "registeredGates was removed; declare gates in .kxm/gates.yaml")]);
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) => issue2(
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(issue2("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
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(compareCodeUnits3)) {
18235
+ for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(compareCodeUnits4)) {
17522
18236
  if (!resourceIdentifier(repositoryId)) {
17523
- earlyIssues.push(issue2("path", "repository_binding_id_invalid", ".kxm/project.yaml", `host-local binding identity ${repositoryId} is invalid`));
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(issue2("reference", "repository_binding_unknown", ".kxm/project.yaml", `host-local binding references unknown repository ${repositoryId}`));
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 required = entry?.required !== false;
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(issue2("path", "repository_binding_not_absolute", ".kxm/project.yaml", `host-local binding for ${repositoryId} must be absolute`));
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(issue2("path", "control_repository_binding_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must bind to the authoritative project root`));
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(issue2("path", "control_repository_path_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must use pathHint .`));
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 (required) loadIssues.push(issue2("discovery", "repository_binding_missing", ".kxm/project.yaml", `required repository ${repositoryId} has no portable pathHint or host-local binding`));
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 (required || hasLocalBinding) loadIssues.push(issue2("discovery", "repository_binding_unavailable", ".kxm/project.yaml", `${hasLocalBinding ? "explicit" : "required"} repository binding ${repositoryId} is unavailable`));
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(issue2("path", "repository_binding_invalid", ".kxm/project.yaml", `repository binding ${repositoryId} must be a regular directory`));
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(issue2("discovery", "repository_git_root_invalid", ".kxm/project.yaml", `member repository ${repositoryId} must bind to its authoritative Git worktree root`));
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) => issue2(
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(issue2("path", "repository_binding_collision", ".kxm/project.yaml", `${repositoryId} and ${priorBinding} use the same repository binding`));
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(issue2("reference", "repository_binding_identity_mismatch", resource.logicalPath, `binding ${repositoryId} declares repositoryId ${String(resource.value.repositoryId)}`));
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 (required || hasLocalBinding) {
17636
- loadIssues.push(issue2("discovery", "repository_definition_missing", `.kxm/repositories/${repositoryId}/repo.yaml`, `${hasLocalBinding ? "explicit" : "required"} repository ${repositoryId} has no repo.yaml`));
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(issue2("reference", "repository_environment_without_definition", `.kxm/repositories/${repositoryId}/env.yaml`, `repository ${repositoryId} environment requires a validated matching repo.yaml`));
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) => compareCodeUnits3(left.logicalPath, right.logicalPath));
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) => compareCodeUnits3(left.name, right.name))) {
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 createHash6, randomUUID } from "node:crypto";
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 = pathToFileURL(resolve2(path));
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 createHash3 } from "node:crypto";
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 createHash3("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
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 createHash4 } from "node:crypto";
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:${createHash4("sha256").update(kxmCanonicalJson(KXM_DEFAULT_SYNC_POLICY), "utf8").digest("hex")}`;
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 createHash5, randomBytes } from "node:crypto";
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 issue3 = { phase: "semantic", code, file, message };
18157
- return new KxmConfigError([issue3]);
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:${createHash5("sha256").update(bytes).digest("hex")}`;
19100
+ return `sha256:${createHash6("sha256").update(bytes).digest("hex")}`;
18381
19101
  }
18382
- function backupDatabaseFile(sourcePath, targetPath, storeId) {
18383
- const resolvedSource = resolve5(sourcePath);
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, sourcePath) {
18509
- if (stores.some((store) => store.storeId === storeId || store.sourcePath === sourcePath)) return;
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(sourcePath, targetPath, id) {
18579
- const resolvedSource = resolve5(sourcePath);
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(sourcePath, id, used) {
18600
- let filename = basename2(sourcePath);
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:${createHash5("sha256").update(manifestJson, "utf8").digest("hex")}`;
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, sourcePath, id, options, currentStateRoot) {
18722
- const stateRel = pathUnder(manifest.stateRoot, sourcePath);
18723
- const projectRel = pathUnder(manifest.projectRoot, sourcePath);
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 own = ownEvents !== void 0 && (stateRel === ownEvents || stateRel === `${ownEvents}.run-prompts.json`);
18727
- if (own && !options.allProjects && options.projectRoot !== void 0) {
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: !own };
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) : sourcePath, machineWide: false };
19454
+ return { targetPath: options.projectRoot ? join5(resolve5(options.projectRoot), projectRel) : sourcePath2, machineWide: false };
18735
19455
  }
18736
- return { targetPath: sourcePath, machineWide: id !== "hub-store" };
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:${createHash6("sha256").update(body, "utf8").digest("hex")}`;
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 SHA256 = /^sha256:[a-f0-9]{64}$/;
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:${createHash6("sha256").update(kxmCanonicalJson(copy), "utf8").digest("hex")}`;
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" || !SHA256.test(value)) throw gateRowInvalid(id, `${label} is not a sha256 hash`);
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 createHash7 } from "node:crypto";
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 = sortIssues3(issues);
21761
- super(sorted.map((issue3) => `${issue3.workflowId}: ${issue3.code}: ${issue3.message}`).join("\n"));
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(compareCodeUnits4);
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 compareCodeUnits4(left, right) {
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 compareCodeUnits4(left, right);
22954
+ return compareCodeUnits5(left, right);
22235
22955
  }
22236
- function sortIssues3(issues) {
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 compareCodeUnits4(left.code, right.code);
22962
+ return compareCodeUnits5(left.code, right.code);
22243
22963
  });
22244
22964
  }
22245
- function pushIssue(sink, issue3) {
22246
- sink.issues.push({ workflowId: sink.workflowId, ...issue3 });
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:${createHash7("sha256").update(input, "utf8").digest("hex")}`;
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, required) {
23182
+ function parseRegistryPin(raw, runId, required2) {
22463
23183
  if (raw === null) {
22464
- if (required) throw runtimeError("run_plan_corrupt", runId, "gate steps require a non-null registry pin");
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: cloneJson(value.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, required, optional, runId, label) {
22786
- const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
22787
- for (const key of required) {
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 cloneJson(value);
23576
+ return cloneJson2(value);
22857
23577
  }
22858
- function cloneJson(value) {
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 required = step?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
22968
- const expectedKey = required.length === 1 ? required[0].key : null;
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 compareCodeUnits5(left, right) {
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:${createHash8("sha256").update(input, "utf8").digest("hex")}`;
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 = createHash8("sha256");
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) => compareCodeUnits5(left.relPath, right.relPath));
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) => compareCodeUnits5(left.relPath, right.relPath));
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) => compareCodeUnits5(left.id, right.id));
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:${createHash8("sha256").update(request.prompt, "utf8").digest("hex")}`;
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 createHash15, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
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 createHash9, randomUUID as randomUUID3 } from "node:crypto";
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:${createHash9("sha256").update(value).digest("hex")}`;
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 createHash10 } from "node:crypto";
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 createHash10("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
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, required = false) => {
24850
+ const rate = (field, short, required2 = false) => {
24131
24851
  const value = t[field] !== void 0 ? t[field] : t[short];
24132
- if (value == null && !required) return 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 import_yaml7 = __toESM(require_dist(), 1);
24195
- import { createHash as createHash14, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
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 import_yaml4 = __toESM(require_dist(), 1);
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, import_yaml4.parse)(readFileSync7(path, "utf8"));
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, import_yaml4.parse)(readFileSync7(join10(dir, file), "utf8"));
24220
- result[role] = (value.roster ?? []).map((entry) => entry.model).filter((model) => typeof model === "string");
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, required = false) {
24643
- if (value === void 0 || value === null) return required ? [] : void 0;
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 import_yaml5 = __toESM(require_dist(), 1);
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, import_yaml5.parse)(frontmatter);
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 import_yaml6 = __toESM(require_dist(), 1);
25307
- import { createHash as createHash11 } from "node:crypto";
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 createHash11("sha256").update(content, "utf8").digest("hex");
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, import_yaml6.parse)(rawFm);
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 createHash12 } from "node:crypto";
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 = createHash12("sha256");
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 required = envelope2.plan.steps[stepId]?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
26764
- return required.length === 1 ? required[0].key : null;
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 createHash13 } from "node:crypto";
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 createHash13("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
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:${createHash14("sha256").update(`${CAPABILITY_PREFIX}${secret}`, "utf8").digest("hex")}`;
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_${createHash14("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
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, import_yaml7.parse)(readFileSync10(agentFile, "utf8"));
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
- if (roster && !roster.includes(selector)) {
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 = createHash14("sha256").update(promptText, "utf8").digest("hex");
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, import_yaml7.parse)(readFileSync10(path, "utf8"));
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:${createHash15("sha256").update(`kxm-runtime-supervisor\0${token}`, "utf8").digest("hex")}`;
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((issue3) => issue3.code === "runtime_supervisor_conflict"))) {
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_${createHash15("sha256").update(`${paths.stateRoot}\0${process.pid}\0${now()}\0${randomBytes3(16).toString("hex")}`, "utf8").digest("hex").slice(0, 24)}`;
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 createHash16 } from "node:crypto";
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 = createHash16("sha256").update(`${group.workflowHash}:${group.stepId}:${group.agentRole}:${group.promptHash}`).digest("hex").slice(0, 10);
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 import_yaml8 = __toESM(require_dist(), 1);
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, import_yaml8.parse)(raw);
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",