@kontextmind/kxm 0.7.126 → 0.7.128

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 (56) 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/learnings.md +21 -0
  11. package/docs/contributing/test-matrix.md +1 -1
  12. package/docs/glossary.md +1 -1
  13. package/docs/guides/agent-skills.md +1 -1
  14. package/docs/operations/backup-and-restore.md +2 -2
  15. package/docs/reference/cli-reference.md +31 -74
  16. package/docs/reference/config-reference.md +83 -118
  17. package/docs/reference/harness-routing.md +15 -8
  18. package/examples/project/.kxm/models/critic-claude.yaml +6 -2
  19. package/examples/project/.kxm/models/critic-gemini.yaml +6 -2
  20. package/examples/project/.kxm/models/critic-grok.yaml +6 -2
  21. package/examples/project/.kxm/models/implementation.yaml +6 -2
  22. package/examples/project/.kxm/models/primary.yaml +6 -2
  23. package/examples/project/.kxm/roles/planner.yaml +8 -0
  24. package/examples/project/.kxm/roles/reviewer-arch.yaml +8 -0
  25. package/examples/project/.kxm/roles/reviewer-cli.yaml +8 -0
  26. package/examples/project/.kxm/roles/writer.yaml +8 -0
  27. package/package.json +1 -1
  28. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  29. package/plugins/kxm/dist/cli.js +1759 -1306
  30. package/plugins/kxm/dist/mcp-server.js +1 -1
  31. package/plugins/kxm/dist/runtime-supervisor.js +994 -245
  32. package/plugins/kxm/dist/runtime.js +1018 -269
  33. package/plugins/kxm/dist/server.js +55 -1
  34. package/plugins/kxm/package.json +1 -1
  35. package/plugins/kxm/skills/kxm/SKILL.md +1 -1
  36. package/plugins/kxm/skills/kxm-definitions/SKILL.md +3 -7
  37. package/plugins/kxm/src/autocomplete.ts +1 -1
  38. package/plugins/kxm/src/cli/project.ts +7 -1
  39. package/plugins/kxm/src/cli/roles.ts +46 -150
  40. package/plugins/kxm/src/cli.ts +8 -22
  41. package/plugins/kxm/src/engine.ts +24 -1
  42. package/plugins/kxm/src/init-guide-setup.ts +38 -4
  43. package/plugins/kxm/src/mcp-server.ts +1 -1
  44. package/plugins/kxm/src/permission.ts +11 -0
  45. package/plugins/kxm/src/policy-draft.mjs +56 -16
  46. package/plugins/kxm/src/project-config.ts +121 -10
  47. package/plugins/kxm/src/repair.ts +1 -0
  48. package/plugins/kxm/src/role.ts +40 -440
  49. package/plugins/kxm/src/routes.ts +28 -5
  50. package/plugins/kxm/src/template.ts +34 -1
  51. package/schemas/README.md +2 -1
  52. package/schemas/model.schema.json +111 -13
  53. package/schemas/role.schema.json +61 -31
  54. package/schemas/policy-draft/README.md +0 -17
  55. package/schemas/policy-draft/model.v2.schema.json +0 -140
  56. package/schemas/policy-draft/role.v2.schema.json +0 -91
@@ -164,10 +164,10 @@ var require_code = __commonJS({
164
164
  function interpolate(x) {
165
165
  return typeof x == "number" || typeof x == "boolean" || x === null ? x : safeStringify(Array.isArray(x) ? x.join(",") : x);
166
166
  }
167
- function stringify5(x) {
167
+ function stringify7(x) {
168
168
  return new _Code(safeStringify(x));
169
169
  }
170
- exports.stringify = stringify5;
170
+ exports.stringify = stringify7;
171
171
  function safeStringify(x) {
172
172
  return JSON.stringify(x).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
173
173
  }
@@ -3835,7 +3835,7 @@ var require_fast_uri = __commonJS({
3835
3835
  normalizeString(uri, options);
3836
3836
  } else if (typeof uri === "object") {
3837
3837
  uri = /** @type {T} */
3838
- parse7(serialize(uri, options), options);
3838
+ parse9(serialize(uri, options), options);
3839
3839
  }
3840
3840
  return uri;
3841
3841
  }
@@ -3875,8 +3875,8 @@ var require_fast_uri = __commonJS({
3875
3875
  function resolveComponent(base, relative5, options, skipNormalization) {
3876
3876
  const target = {};
3877
3877
  if (!skipNormalization) {
3878
- base = parse7(serialize(base, options), options);
3879
- relative5 = parse7(serialize(relative5, options), options);
3878
+ base = parse9(serialize(base, options), options);
3879
+ relative5 = parse9(serialize(relative5, options), options);
3880
3880
  }
3881
3881
  options = options || {};
3882
3882
  if (!options.tolerant && relative5.scheme) {
@@ -4168,7 +4168,7 @@ var require_fast_uri = __commonJS({
4168
4168
  }
4169
4169
  return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
4170
4170
  }
4171
- function parse7(uri, opts) {
4171
+ function parse9(uri, opts) {
4172
4172
  return parseWithStatus(uri, opts).parsed;
4173
4173
  }
4174
4174
  function normalizeString(uri, opts) {
@@ -4205,7 +4205,7 @@ var require_fast_uri = __commonJS({
4205
4205
  resolveComponent,
4206
4206
  equal,
4207
4207
  serialize,
4208
- parse: parse7
4208
+ parse: parse9
4209
4209
  };
4210
4210
  module.exports = fastUri;
4211
4211
  module.exports.default = fastUri;
@@ -6944,8 +6944,8 @@ var require_discriminator = __commonJS({
6944
6944
  if (!tagRequired)
6945
6945
  throw new Error(`discriminator: "${tagName}" must be required`);
6946
6946
  return oneOfMapping;
6947
- function hasRequired({ required }) {
6948
- return Array.isArray(required) && required.includes(tagName);
6947
+ function hasRequired({ required: required2 }) {
6948
+ return Array.isArray(required2) && required2.includes(tagName);
6949
6949
  }
6950
6950
  function addMappings(sch, i) {
6951
6951
  if (sch.const) {
@@ -8918,7 +8918,7 @@ var require_stringify = __commonJS({
8918
8918
  props.push(doc.directives.tagString(tag));
8919
8919
  return props.join(" ");
8920
8920
  }
8921
- function stringify5(item, ctx, onComment, onChompKeep) {
8921
+ function stringify7(item, ctx, onComment, onChompKeep) {
8922
8922
  if (identity.isPair(item))
8923
8923
  return item.toString(ctx, onComment, onChompKeep);
8924
8924
  if (identity.isAlias(item)) {
@@ -8947,7 +8947,7 @@ var require_stringify = __commonJS({
8947
8947
  ${ctx.indent}${str}`;
8948
8948
  }
8949
8949
  exports.createStringifyContext = createStringifyContext;
8950
- exports.stringify = stringify5;
8950
+ exports.stringify = stringify7;
8951
8951
  }
8952
8952
  });
8953
8953
 
@@ -8957,7 +8957,7 @@ var require_stringifyPair = __commonJS({
8957
8957
  "use strict";
8958
8958
  var identity = require_identity();
8959
8959
  var Scalar = require_Scalar();
8960
- var stringify5 = require_stringify();
8960
+ var stringify7 = require_stringify();
8961
8961
  var stringifyComment = require_stringifyComment();
8962
8962
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
8963
8963
  const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
@@ -8979,7 +8979,7 @@ var require_stringifyPair = __commonJS({
8979
8979
  });
8980
8980
  let keyCommentDone = false;
8981
8981
  let chompKeep = false;
8982
- let str = stringify5.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
8982
+ let str = stringify7.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
8983
8983
  if (!explicitKey && !ctx.inFlow && str.length > 1024) {
8984
8984
  if (simpleKeys)
8985
8985
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
@@ -9031,7 +9031,7 @@ ${indent}:`;
9031
9031
  ctx.indent = ctx.indent.substring(2);
9032
9032
  }
9033
9033
  let valueCommentDone = false;
9034
- const valueStr = stringify5.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
9034
+ const valueStr = stringify7.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
9035
9035
  let ws = " ";
9036
9036
  if (keyComment || vsb || vcb) {
9037
9037
  ws = vsb ? "\n" : "";
@@ -9172,7 +9172,7 @@ var require_addPairToJSMap = __commonJS({
9172
9172
  "use strict";
9173
9173
  var log = require_log();
9174
9174
  var merge = require_merge();
9175
- var stringify5 = require_stringify();
9175
+ var stringify7 = require_stringify();
9176
9176
  var identity = require_identity();
9177
9177
  var toJS = require_toJS();
9178
9178
  function addPairToJSMap(ctx, map, { key, value }) {
@@ -9208,7 +9208,7 @@ var require_addPairToJSMap = __commonJS({
9208
9208
  if (typeof jsKey !== "object")
9209
9209
  return String(jsKey);
9210
9210
  if (identity.isNode(key) && ctx?.doc) {
9211
- const strCtx = stringify5.createStringifyContext(ctx.doc, {});
9211
+ const strCtx = stringify7.createStringifyContext(ctx.doc, {});
9212
9212
  strCtx.anchors = /* @__PURE__ */ new Set();
9213
9213
  for (const node of ctx.anchors.keys())
9214
9214
  strCtx.anchors.add(node.anchor);
@@ -9275,12 +9275,12 @@ var require_stringifyCollection = __commonJS({
9275
9275
  "node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
9276
9276
  "use strict";
9277
9277
  var identity = require_identity();
9278
- var stringify5 = require_stringify();
9278
+ var stringify7 = require_stringify();
9279
9279
  var stringifyComment = require_stringifyComment();
9280
9280
  function stringifyCollection(collection, ctx, options) {
9281
9281
  const flow = ctx.inFlow ?? collection.flow;
9282
- const stringify6 = flow ? stringifyFlowCollection : stringifyBlockCollection;
9283
- return stringify6(collection, ctx, options);
9282
+ const stringify8 = flow ? stringifyFlowCollection : stringifyBlockCollection;
9283
+ return stringify8(collection, ctx, options);
9284
9284
  }
9285
9285
  function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
9286
9286
  const { indent, options: { commentString } } = ctx;
@@ -9305,7 +9305,7 @@ var require_stringifyCollection = __commonJS({
9305
9305
  }
9306
9306
  }
9307
9307
  chompKeep = false;
9308
- let str2 = stringify5.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
9308
+ let str2 = stringify7.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
9309
9309
  if (comment2)
9310
9310
  str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
9311
9311
  if (chompKeep && comment2)
@@ -9372,7 +9372,7 @@ ${indent}${line}` : "\n";
9372
9372
  }
9373
9373
  if (comment)
9374
9374
  reqNewline = true;
9375
- let str = stringify5.stringify(item, itemCtx, () => comment = null);
9375
+ let str = stringify7.stringify(item, itemCtx, () => comment = null);
9376
9376
  reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
9377
9377
  if (i < items.length - 1) {
9378
9378
  str += ",";
@@ -10733,7 +10733,7 @@ var require_stringifyDocument = __commonJS({
10733
10733
  "node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
10734
10734
  "use strict";
10735
10735
  var identity = require_identity();
10736
- var stringify5 = require_stringify();
10736
+ var stringify7 = require_stringify();
10737
10737
  var stringifyComment = require_stringifyComment();
10738
10738
  function stringifyDocument(doc, options) {
10739
10739
  const lines = [];
@@ -10748,7 +10748,7 @@ var require_stringifyDocument = __commonJS({
10748
10748
  }
10749
10749
  if (hasDirectives)
10750
10750
  lines.push("---");
10751
- const ctx = stringify5.createStringifyContext(doc, options);
10751
+ const ctx = stringify7.createStringifyContext(doc, options);
10752
10752
  const { commentString } = ctx.options;
10753
10753
  if (doc.commentBefore) {
10754
10754
  if (lines.length !== 1)
@@ -10770,7 +10770,7 @@ var require_stringifyDocument = __commonJS({
10770
10770
  contentComment = doc.contents.comment;
10771
10771
  }
10772
10772
  const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
10773
- let body = stringify5.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
10773
+ let body = stringify7.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
10774
10774
  if (contentComment)
10775
10775
  body += stringifyComment.lineComment(body, "", commentString(contentComment));
10776
10776
  if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
@@ -10778,7 +10778,7 @@ var require_stringifyDocument = __commonJS({
10778
10778
  } else
10779
10779
  lines.push(body);
10780
10780
  } else {
10781
- lines.push(stringify5.stringify(doc.contents, ctx));
10781
+ lines.push(stringify7.stringify(doc.contents, ctx));
10782
10782
  }
10783
10783
  if (doc.directives?.docEnd) {
10784
10784
  if (doc.comment) {
@@ -12914,7 +12914,7 @@ var require_cst_scalar = __commonJS({
12914
12914
  var require_cst_stringify = __commonJS({
12915
12915
  "node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
12916
12916
  "use strict";
12917
- var stringify5 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
12917
+ var stringify7 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
12918
12918
  function stringifyToken(token) {
12919
12919
  switch (token.type) {
12920
12920
  case "block-scalar": {
@@ -12967,7 +12967,7 @@ var require_cst_stringify = __commonJS({
12967
12967
  res += stringifyToken(value);
12968
12968
  return res;
12969
12969
  }
12970
- exports.stringify = stringify5;
12970
+ exports.stringify = stringify7;
12971
12971
  }
12972
12972
  });
12973
12973
 
@@ -14659,7 +14659,7 @@ var require_public_api = __commonJS({
14659
14659
  return docs;
14660
14660
  return Object.assign([], { empty: true }, composer$1.streamInfo());
14661
14661
  }
14662
- function parseDocument2(source, options = {}) {
14662
+ function parseDocument3(source, options = {}) {
14663
14663
  const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options);
14664
14664
  const parser$1 = new parser.Parser(lineCounter2?.addNewLine);
14665
14665
  const composer$1 = new composer.Composer(options);
@@ -14678,14 +14678,14 @@ var require_public_api = __commonJS({
14678
14678
  }
14679
14679
  return doc;
14680
14680
  }
14681
- function parse7(src, reviver, options) {
14681
+ function parse9(src, reviver, options) {
14682
14682
  let _reviver = void 0;
14683
14683
  if (typeof reviver === "function") {
14684
14684
  _reviver = reviver;
14685
14685
  } else if (options === void 0 && reviver && typeof reviver === "object") {
14686
14686
  options = reviver;
14687
14687
  }
14688
- const doc = parseDocument2(src, options);
14688
+ const doc = parseDocument3(src, options);
14689
14689
  if (!doc)
14690
14690
  return null;
14691
14691
  doc.warnings.forEach((warning) => log.warn(doc.options.logLevel, warning));
@@ -14697,7 +14697,7 @@ var require_public_api = __commonJS({
14697
14697
  }
14698
14698
  return doc.toJS(Object.assign({ reviver: _reviver }, options));
14699
14699
  }
14700
- function stringify5(value, replacer, options) {
14700
+ function stringify7(value, replacer, options) {
14701
14701
  let _replacer = null;
14702
14702
  if (typeof replacer === "function" || Array.isArray(replacer)) {
14703
14703
  _replacer = replacer;
@@ -14719,10 +14719,10 @@ var require_public_api = __commonJS({
14719
14719
  return value.toString(options);
14720
14720
  return new Document.Document(value, _replacer, options).toString(options);
14721
14721
  }
14722
- exports.parse = parse7;
14722
+ exports.parse = parse9;
14723
14723
  exports.parseAllDocuments = parseAllDocuments;
14724
- exports.parseDocument = parseDocument2;
14725
- exports.stringify = stringify5;
14724
+ exports.parseDocument = parseDocument3;
14725
+ exports.stringify = stringify7;
14726
14726
  }
14727
14727
  });
14728
14728
 
@@ -14780,7 +14780,7 @@ var require_dist = __commonJS({
14780
14780
 
14781
14781
  // plugins/kxm/src/runtime-supervisor.ts
14782
14782
  import { spawn as spawn3 } from "node:child_process";
14783
- import { createHash as createHash14, createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
14783
+ import { createHash as createHash15, createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
14784
14784
  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";
14785
14785
  import { createServer } from "node:http";
14786
14786
  import { dirname as dirname11, isAbsolute as isAbsolute7, join as join18 } from "node:path";
@@ -14812,7 +14812,7 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
14812
14812
  // plugins/kxm/src/project-config.ts
14813
14813
  var import__ = __toESM(require__(), 1);
14814
14814
  import { spawnSync as spawnSync2 } from "node:child_process";
14815
- import { createHash as createHash2 } from "node:crypto";
14815
+ import { createHash as createHash3 } from "node:crypto";
14816
14816
  import { existsSync as existsSync3, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
14817
14817
  import { basename, dirname as dirname2, extname, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
14818
14818
 
@@ -14941,14 +14941,598 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
14941
14941
  return value;
14942
14942
  }
14943
14943
 
14944
+ // plugins/kxm/src/project-config.ts
14945
+ import { pathToFileURL } from "node:url";
14946
+
14947
+ // plugins/kxm/src/policy-draft.mjs
14948
+ import { createHash } from "node:crypto";
14949
+ var POLICY_DRAFT_MODEL_SCHEMA = "kxm.model.v2";
14950
+ var POLICY_DRAFT_ROLE_SCHEMA = "kxm.role.v2";
14951
+ var POLICY_DRAFT_PURPOSES = Object.freeze([
14952
+ "writer",
14953
+ "planner",
14954
+ "reviewer-arch",
14955
+ "reviewer-cli",
14956
+ "experiment"
14957
+ ]);
14958
+ var POLICY_DRAFT_PERMISSIONS = Object.freeze(["edit", "read-only"]);
14959
+ var POLICY_DRAFT_STATUSES = Object.freeze(["admitted", "candidate", "retired"]);
14960
+ var IDENTIFIER = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
14961
+ var ROUTE_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
14962
+ var SHA256 = /^[a-f0-9]{64}$/;
14963
+ var TOKEN = /[\s\x00-\x1f]/u;
14964
+ var EFFORTS = Object.freeze(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
14965
+ var MODES = Object.freeze(["headless", "interactive", "either"]);
14966
+ var MODEL_KEYS = Object.freeze([
14967
+ "schema",
14968
+ "id",
14969
+ "harness",
14970
+ "model",
14971
+ "vendor",
14972
+ "status",
14973
+ "permissions",
14974
+ "origin",
14975
+ "thinking",
14976
+ "tags",
14977
+ "capabilities",
14978
+ "priority",
14979
+ "fallbacks",
14980
+ "limits"
14981
+ ]);
14982
+ var ROLE_KEYS = Object.freeze([
14983
+ "schema",
14984
+ "id",
14985
+ "purpose",
14986
+ "permission",
14987
+ "description",
14988
+ "extends",
14989
+ "roster",
14990
+ "skills",
14991
+ "tools",
14992
+ "produces",
14993
+ "consumes",
14994
+ "policy"
14995
+ ]);
14996
+ var ORIGIN_KEYS = Object.freeze(["source", "sha256"]);
14997
+ var LIMIT_KEYS = Object.freeze(["contextTokens", "outputTokens", "timeoutMs"]);
14998
+ var TOOL_KEYS = Object.freeze(["preset", "allow", "deny"]);
14999
+ var TEMPLATE_KEYS = Object.freeze(["template", "schema"]);
15000
+ var POLICY_KEYS = Object.freeze(["vendorIndependenceRequired", "maxTransitions", "requiresGateVerification", "fallback"]);
15001
+ var FALLBACK_KEYS = Object.freeze(["onError", "maxSwitches", "revert"]);
15002
+ var FALLBACK_ERRORS = Object.freeze(["rate_limit", "transport", "provider_unavailable"]);
15003
+ var FALLBACK_REVERT = Object.freeze(["next_run", "never"]);
15004
+ var ROSTER_ENTRY_KEYS = Object.freeze(["route", "effort", "mode"]);
15005
+ var CRITIC_PURPOSES = Object.freeze(["reviewer-arch", "reviewer-cli"]);
15006
+ function compareCodeUnits2(left, right) {
15007
+ return left < right ? -1 : left > right ? 1 : 0;
15008
+ }
15009
+ function sortIssues2(issues) {
15010
+ 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));
15011
+ }
15012
+ function issue2(phase, code, file, message) {
15013
+ return { phase, code, file, message };
15014
+ }
15015
+ function isObject(value) {
15016
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15017
+ }
15018
+ function own(object, key) {
15019
+ return Object.hasOwn(object, key);
15020
+ }
15021
+ function extraKeys(value, allowed) {
15022
+ return Object.keys(value).filter((key) => !allowed.includes(key));
15023
+ }
15024
+ function freezeDeep(value) {
15025
+ if (value && typeof value === "object") {
15026
+ for (const child of Object.values(value)) freezeDeep(child);
15027
+ Object.freeze(value);
15028
+ }
15029
+ return value;
15030
+ }
15031
+ function cloneJson(value) {
15032
+ return JSON.parse(JSON.stringify(value));
15033
+ }
15034
+ function sha256Bytes(input) {
15035
+ const bytes = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(input);
15036
+ return createHash("sha256").update(bytes).digest("hex");
15037
+ }
15038
+ function canonicalVendor(value, aliases) {
15039
+ if (typeof value !== "string") return "";
15040
+ const lower = value.toLowerCase();
15041
+ return own(aliases, lower) ? aliases[lower] : lower;
15042
+ }
15043
+ function identifier(value, max = 64) {
15044
+ return typeof value === "string" && value.length >= 1 && value.length <= max && IDENTIFIER.test(value);
15045
+ }
15046
+ function routeId(value) {
15047
+ return typeof value === "string" && value.length >= 1 && value.length <= 64 && ROUTE_ID.test(value);
15048
+ }
15049
+ function nonemptyToken(value, max = 512) {
15050
+ return typeof value === "string" && value.length >= 1 && value.length <= max && !TOKEN.test(value);
15051
+ }
15052
+ function codePointCount(value) {
15053
+ return [...value].length;
15054
+ }
15055
+ function sourcePath(value) {
15056
+ if (!nonemptyToken(value)) return false;
15057
+ if (value.startsWith("/") || value.includes("\\")) return false;
15058
+ const parts = value.split("/");
15059
+ return parts.length > 0 && parts.every((part) => part && part !== "." && part !== "..");
15060
+ }
15061
+ function uniqueStringList(value, allowed, { min = 1, max = 64 } = {}) {
15062
+ if (!Array.isArray(value) || value.length < min || value.length > max) return false;
15063
+ if (new Set(value).size !== value.length) return false;
15064
+ return value.every((item) => allowed.includes(item));
15065
+ }
15066
+ function identifierList(value, { maxItems = 32, itemMax = 64 } = {}) {
15067
+ if (!Array.isArray(value) || value.length > maxItems) return false;
15068
+ if (new Set(value).size !== value.length) return false;
15069
+ return value.every((item) => identifier(item, itemMax));
15070
+ }
15071
+ function parseDocument2(value, label, issues) {
15072
+ if (typeof value === "string" || value instanceof Uint8Array) {
15073
+ try {
15074
+ return parseRestrictedYaml(value, label);
15075
+ } catch (error) {
15076
+ if (error instanceof RestrictedYamlError) issues.push(...error.issues);
15077
+ else issues.push(issue2("parse", "invalid_yaml", label, error instanceof Error ? error.message : "invalid YAML"));
15078
+ return void 0;
15079
+ }
15080
+ }
15081
+ if (isObject(value)) return value;
15082
+ issues.push(issue2("schema", "root_not_object", label, "resource root must be a mapping"));
15083
+ return void 0;
15084
+ }
15085
+ function collectDocuments(raw, kind, issues) {
15086
+ const collected = [];
15087
+ const seen = /* @__PURE__ */ new Set();
15088
+ if (raw === void 0) return collected;
15089
+ if (Array.isArray(raw)) {
15090
+ for (const [index, value] of raw.entries()) {
15091
+ const label = `${kind}[${index}]`;
15092
+ const document = parseDocument2(value, label, issues);
15093
+ if (!document) continue;
15094
+ const declared = document.id;
15095
+ if (!routeId(declared) && !(kind === "roles" && identifier(declared))) {
15096
+ issues.push(issue2("schema", "missing_id", label, `${kind.slice(0, -1)} identity is missing or invalid`));
15097
+ continue;
15098
+ }
15099
+ if (seen.has(declared)) {
15100
+ issues.push(issue2("schema", kind === "models" ? "duplicate_route_id" : "duplicate_role_id", label, `duplicate ${kind.slice(0, -1)} identity ${declared}`));
15101
+ continue;
15102
+ }
15103
+ seen.add(declared);
15104
+ collected.push({ id: declared, label: `${kind}/${declared}`, document });
15105
+ }
15106
+ return collected;
15107
+ }
15108
+ if (!isObject(raw)) {
15109
+ issues.push(issue2("schema", "root_not_object", kind, `${kind} must be a mapping or array`));
15110
+ return collected;
15111
+ }
15112
+ for (const id of Object.keys(raw).sort(compareCodeUnits2)) {
15113
+ const label = `${kind}/${id}`;
15114
+ if (kind === "models" ? !routeId(id) : !identifier(id)) {
15115
+ issues.push(issue2("schema", "invalid_id", label, `invalid ${kind.slice(0, -1)} identity`));
15116
+ continue;
15117
+ }
15118
+ if (seen.has(id)) {
15119
+ issues.push(issue2("schema", kind === "models" ? "duplicate_route_id" : "duplicate_role_id", label, `duplicate ${kind.slice(0, -1)} identity ${id}`));
15120
+ continue;
15121
+ }
15122
+ seen.add(id);
15123
+ const document = parseDocument2(raw[id], label, issues);
15124
+ if (!document) continue;
15125
+ if (document.id !== void 0 && document.id !== id) {
15126
+ issues.push(issue2("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
15127
+ continue;
15128
+ }
15129
+ collected.push({ id, label, document });
15130
+ }
15131
+ return collected;
15132
+ }
15133
+ function closedObject(value, allowed, label, issues, code = "schema_additionalProperties") {
15134
+ if (!isObject(value)) {
15135
+ issues.push(issue2("schema", "root_not_object", label, "value must be a mapping"));
15136
+ return false;
15137
+ }
15138
+ const unexpected = extraKeys(value, allowed);
15139
+ if (unexpected.length > 0) {
15140
+ issues.push(issue2("schema", code, label, `unexpected properties: ${unexpected.join(", ")}`));
15141
+ return false;
15142
+ }
15143
+ return true;
15144
+ }
15145
+ function required(value, keys, label, issues) {
15146
+ for (const key of keys) {
15147
+ if (!own(value, key)) issues.push(issue2("schema", "schema_required", label, `missing ${key}`));
15148
+ }
15149
+ }
15150
+ function validateOrigin(origin, label, issues) {
15151
+ if (!closedObject(origin, ORIGIN_KEYS, `${label}.origin`, issues)) return;
15152
+ required(origin, ORIGIN_KEYS, `${label}.origin`, issues);
15153
+ if (origin.source !== void 0 && !sourcePath(origin.source)) {
15154
+ issues.push(issue2("schema", "origin_source_invalid", `${label}.origin`, "origin source must be a contained relative path"));
15155
+ }
15156
+ if (origin.sha256 !== void 0 && (typeof origin.sha256 !== "string" || !SHA256.test(origin.sha256))) {
15157
+ issues.push(issue2("schema", "origin_hash_invalid", `${label}.origin`, "origin sha256 must be a 64-character hex digest"));
15158
+ }
15159
+ }
15160
+ function validateModelShape(document, id, label, issues) {
15161
+ if (!closedObject(document, MODEL_KEYS, label, issues)) return;
15162
+ required(document, ["schema", "harness", "model", "vendor", "status", "permissions"], label, issues);
15163
+ if (document.schema !== void 0 && document.schema !== POLICY_DRAFT_MODEL_SCHEMA) {
15164
+ issues.push(issue2("schema", "schema_identity_mismatch", label, `expected ${POLICY_DRAFT_MODEL_SCHEMA}, received ${String(document.schema)}`));
15165
+ }
15166
+ if (document.id !== void 0 && document.id !== id) {
15167
+ issues.push(issue2("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
15168
+ }
15169
+ if (document.harness !== void 0 && !identifier(document.harness)) {
15170
+ issues.push(issue2("schema", "schema_pattern", label, "harness must be an identifier"));
15171
+ }
15172
+ if (document.model !== void 0 && (typeof document.model !== "string" || codePointCount(document.model) < 1 || codePointCount(document.model) > 200)) {
15173
+ issues.push(issue2("schema", "schema_pattern", label, "model must be a nonempty string"));
15174
+ }
15175
+ if (document.vendor !== void 0 && !nonemptyToken(document.vendor, 64)) {
15176
+ issues.push(issue2("schema", "schema_pattern", label, "vendor must be a nonempty token"));
15177
+ }
15178
+ if (document.status !== void 0 && !POLICY_DRAFT_STATUSES.includes(document.status)) {
15179
+ issues.push(issue2("schema", "unsupported_status", label, "status must be admitted, candidate, or retired"));
15180
+ }
15181
+ if (document.permissions !== void 0 && !uniqueStringList(document.permissions, POLICY_DRAFT_PERMISSIONS, { min: 1, max: 2 })) {
15182
+ issues.push(issue2("schema", "schema_enum", label, "permissions must be unique edit and/or read-only"));
15183
+ }
15184
+ if (document.harness === "pi" && !own(document, "origin")) {
15185
+ issues.push(issue2("schema", "schema_required", label, "Pi routes require origin source and sha256"));
15186
+ }
15187
+ if (own(document, "origin")) validateOrigin(document.origin, label, issues);
15188
+ if (document.thinking !== void 0 && (typeof document.thinking !== "string" || document.thinking.length < 1 || document.thinking.length > 64)) {
15189
+ issues.push(issue2("schema", "schema_pattern", label, "thinking must be a short string"));
15190
+ }
15191
+ if (document.tags !== void 0 && !identifierList(document.tags)) {
15192
+ issues.push(issue2("schema", "schema_pattern", label, "tags must be unique identifiers"));
15193
+ }
15194
+ if (document.capabilities !== void 0 && !identifierList(document.capabilities)) {
15195
+ issues.push(issue2("schema", "schema_pattern", label, "capabilities must be unique identifiers"));
15196
+ }
15197
+ if (document.priority !== void 0 && (!Number.isInteger(document.priority) || document.priority < -1e4 || document.priority > 1e4)) {
15198
+ issues.push(issue2("schema", "schema_type", label, "priority must be an integer in range"));
15199
+ }
15200
+ if (document.fallbacks !== void 0) {
15201
+ if (!Array.isArray(document.fallbacks) || document.fallbacks.length > 8) {
15202
+ issues.push(issue2("schema", "schema_type", label, "fallbacks must be a bounded array"));
15203
+ } else {
15204
+ for (const [index, fallback] of document.fallbacks.entries()) {
15205
+ if (!isObject(fallback)) {
15206
+ issues.push(issue2("schema", "root_not_object", `${label}.fallbacks[${index}]`, "fallback must be a mapping"));
15207
+ continue;
15208
+ }
15209
+ const keys = Object.keys(fallback);
15210
+ const profile = keys.length === 1 && own(fallback, "profile") && identifier(fallback.profile);
15211
+ const tag = own(fallback, "tag") && identifier(fallback.tag) && extraKeys(fallback, ["tag", "capabilities"]).length === 0 && (fallback.capabilities === void 0 || identifierList(fallback.capabilities));
15212
+ 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;
15213
+ if (!profile && !tag && !provider) {
15214
+ issues.push(issue2("schema", "schema_oneOf", `${label}.fallbacks[${index}]`, "fallback does not match a retained v1 selector"));
15215
+ }
15216
+ }
15217
+ }
15218
+ }
15219
+ if (document.limits !== void 0) {
15220
+ if (!closedObject(document.limits, LIMIT_KEYS, `${label}.limits`, issues)) return;
15221
+ for (const key of ["contextTokens", "outputTokens"]) {
15222
+ if (document.limits[key] !== void 0 && (!Number.isInteger(document.limits[key]) || document.limits[key] < 1)) {
15223
+ issues.push(issue2("schema", "schema_type", `${label}.limits`, `${key} must be a positive integer`));
15224
+ }
15225
+ }
15226
+ if (document.limits.timeoutMs !== void 0 && (!Number.isInteger(document.limits.timeoutMs) || document.limits.timeoutMs < 0 || document.limits.timeoutMs > 31536e6)) {
15227
+ issues.push(issue2("schema", "schema_type", `${label}.limits`, "timeoutMs must be a non-negative integer between 0 and 31536000000"));
15228
+ }
15229
+ }
15230
+ }
15231
+ function validateTemplateList(value, label, issues) {
15232
+ if (!Array.isArray(value)) {
15233
+ issues.push(issue2("schema", "schema_type", label, "must be an array"));
15234
+ return;
15235
+ }
15236
+ for (const [index, entry] of value.entries()) {
15237
+ const path = `${label}[${index}]`;
15238
+ if (!closedObject(entry, TEMPLATE_KEYS, path, issues)) continue;
15239
+ if (typeof entry.template !== "string" || !entry.template) {
15240
+ issues.push(issue2("schema", "schema_required", path, "template is required"));
15241
+ }
15242
+ if (entry.schema !== void 0 && typeof entry.schema !== "string") {
15243
+ issues.push(issue2("schema", "schema_type", path, "schema must be a string"));
15244
+ }
15245
+ }
15246
+ }
15247
+ function validateRoleShape(document, id, label, issues) {
15248
+ if (!closedObject(document, ROLE_KEYS, label, issues)) return;
15249
+ required(document, ["schema", "purpose", "permission", "description", "roster"], label, issues);
15250
+ if (document.schema !== void 0 && document.schema !== POLICY_DRAFT_ROLE_SCHEMA) {
15251
+ issues.push(issue2("schema", "schema_identity_mismatch", label, `expected ${POLICY_DRAFT_ROLE_SCHEMA}, received ${String(document.schema)}`));
15252
+ }
15253
+ if (document.id !== void 0 && document.id !== id) {
15254
+ issues.push(issue2("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
15255
+ }
15256
+ if (document.extends !== void 0 && !identifier(document.extends)) {
15257
+ issues.push(issue2("schema", "schema_pattern", label, "extends must be a role identifier"));
15258
+ }
15259
+ if (document.purpose !== void 0 && !POLICY_DRAFT_PURPOSES.includes(document.purpose)) {
15260
+ issues.push(issue2("schema", "unsupported_purpose", label, "purpose must be a runner role label"));
15261
+ }
15262
+ if (document.permission !== void 0 && !POLICY_DRAFT_PERMISSIONS.includes(document.permission)) {
15263
+ issues.push(issue2("schema", "schema_enum", label, "permission must be edit or read-only"));
15264
+ }
15265
+ if (document.description !== void 0 && (typeof document.description !== "string" || document.description.length < 1 || document.description.length > 2e3)) {
15266
+ issues.push(issue2("schema", "schema_type", label, "description must be a bounded string"));
15267
+ }
15268
+ if (document.roster !== void 0) {
15269
+ if (!Array.isArray(document.roster) || document.roster.length > 32) {
15270
+ issues.push(issue2("schema", "schema_type", label, "roster must be a bounded array"));
15271
+ } else {
15272
+ const seenRoutes = /* @__PURE__ */ new Set();
15273
+ for (const [index, entry] of document.roster.entries()) {
15274
+ const path = `${label}.roster[${index}]`;
15275
+ if (!closedObject(entry, ROSTER_ENTRY_KEYS, path, issues)) continue;
15276
+ if (!routeId(entry.route)) {
15277
+ issues.push(issue2("schema", "schema_pattern", path, "roster route must be a route identity"));
15278
+ continue;
15279
+ }
15280
+ if (seenRoutes.has(entry.route)) {
15281
+ issues.push(issue2("schema", "duplicate_route_id", path, `duplicate roster route ${entry.route}`));
15282
+ }
15283
+ seenRoutes.add(entry.route);
15284
+ if (entry.effort !== void 0 && !EFFORTS.includes(entry.effort)) {
15285
+ issues.push(issue2("schema", "schema_enum", path, "effort is not a known helper effort"));
15286
+ }
15287
+ if (entry.mode !== void 0 && !MODES.includes(entry.mode)) {
15288
+ issues.push(issue2("schema", "schema_enum", path, "mode must be headless, interactive, or either"));
15289
+ }
15290
+ }
15291
+ }
15292
+ }
15293
+ if (document.skills !== void 0) {
15294
+ if (!Array.isArray(document.skills) || document.skills.length > 64 || document.skills.some((item) => typeof item !== "string" || item.length < 1 || item.length > 128)) {
15295
+ issues.push(issue2("schema", "schema_type", label, "skills must be bounded strings"));
15296
+ }
15297
+ }
15298
+ if (document.tools !== void 0) {
15299
+ if (!closedObject(document.tools, TOOL_KEYS, `${label}.tools`, issues)) return;
15300
+ if (document.tools.preset !== void 0 && typeof document.tools.preset !== "string") {
15301
+ issues.push(issue2("schema", "schema_type", `${label}.tools`, "preset must be a string"));
15302
+ }
15303
+ for (const key of ["allow", "deny"]) {
15304
+ if (document.tools[key] !== void 0 && (!Array.isArray(document.tools[key]) || document.tools[key].some((item) => typeof item !== "string"))) {
15305
+ issues.push(issue2("schema", "schema_type", `${label}.tools`, `${key} must be a string array`));
15306
+ }
15307
+ }
15308
+ }
15309
+ if (document.produces !== void 0) validateTemplateList(document.produces, `${label}.produces`, issues);
15310
+ if (document.consumes !== void 0) validateTemplateList(document.consumes, `${label}.consumes`, issues);
15311
+ if (document.policy !== void 0) {
15312
+ if (!closedObject(document.policy, POLICY_KEYS, `${label}.policy`, issues)) return;
15313
+ if (document.policy.vendorIndependenceRequired !== void 0 && typeof document.policy.vendorIndependenceRequired !== "boolean") {
15314
+ issues.push(issue2("schema", "schema_type", `${label}.policy`, "vendorIndependenceRequired must be boolean"));
15315
+ }
15316
+ if (document.policy.requiresGateVerification !== void 0 && typeof document.policy.requiresGateVerification !== "boolean") {
15317
+ issues.push(issue2("schema", "schema_type", `${label}.policy`, "requiresGateVerification must be boolean"));
15318
+ }
15319
+ if (document.policy.maxTransitions !== void 0 && (!Number.isInteger(document.policy.maxTransitions) || document.policy.maxTransitions < 1)) {
15320
+ issues.push(issue2("schema", "schema_type", `${label}.policy`, "maxTransitions must be a positive integer"));
15321
+ }
15322
+ if (document.policy.fallback !== void 0) {
15323
+ const fallback = document.policy.fallback;
15324
+ const path = `${label}.policy.fallback`;
15325
+ if (!closedObject(fallback, FALLBACK_KEYS, path, issues)) return;
15326
+ if (fallback.onError !== void 0) {
15327
+ if (!Array.isArray(fallback.onError) || new Set(fallback.onError).size !== fallback.onError.length || fallback.onError.some((item) => !FALLBACK_ERRORS.includes(item))) {
15328
+ issues.push(issue2("schema", "schema_enum", path, "onError must be unique rate_limit, transport, or provider_unavailable values"));
15329
+ }
15330
+ }
15331
+ if (fallback.maxSwitches !== void 0 && (!Number.isInteger(fallback.maxSwitches) || fallback.maxSwitches < 0)) {
15332
+ issues.push(issue2("schema", "schema_type", path, "maxSwitches must be an integer >= 0"));
15333
+ }
15334
+ if (fallback.revert !== void 0 && !FALLBACK_REVERT.includes(fallback.revert)) {
15335
+ issues.push(issue2("schema", "schema_enum", path, "revert must be next_run or never"));
15336
+ }
15337
+ }
15338
+ }
15339
+ }
15340
+ function subset(values, allowed) {
15341
+ return values.every((value) => allowed.includes(value));
15342
+ }
15343
+ function validateModelSemantics(model, label, options, evidence, issues) {
15344
+ const aliases = options.vendorAliases ?? {};
15345
+ const ceiling = options.ceilings[model.harness];
15346
+ if (!ceiling) {
15347
+ issues.push(issue2("semantic", "unsupported_harness", label, `no code-owned ceiling for harness ${String(model.harness)}`));
15348
+ return;
15349
+ }
15350
+ const vendor = canonicalVendor(model.vendor, aliases);
15351
+ if (options.piAllowedProviders.includes(vendor)) {
15352
+ issues.push(issue2("semantic", "vendor_aggregator", label, "vendor must name the model vendor, not a billing aggregator"));
15353
+ }
15354
+ if (!subset(model.permissions, ceiling.permissions)) {
15355
+ issues.push(issue2("semantic", "permission_escalation", label, "route permissions exceed the harness ceiling"));
15356
+ }
15357
+ if (Array.isArray(ceiling.models) && ceiling.models.length > 0 && !ceiling.models.includes(model.model)) {
15358
+ issues.push(issue2("semantic", "model_not_in_ceiling", label, "exact model is not listed on the harness ceiling"));
15359
+ }
15360
+ if (model.harness === "pi") {
15361
+ const parts = typeof model.model === "string" ? model.model.split("/") : [];
15362
+ const nativeVendors = isObject(options.piNativeVendorProviders) ? options.piNativeVendorProviders : {};
15363
+ const provider = parts[0];
15364
+ const owned = own(nativeVendors, provider) ? canonicalVendor(nativeVendors[provider], aliases) : "";
15365
+ const minParts = owned ? 2 : 3;
15366
+ const maxParts = owned ? 2 : Number.POSITIVE_INFINITY;
15367
+ if (parts.length < minParts || parts.length > maxParts || parts.some((part) => !part) || !options.piAllowedProviders.includes(provider) || owned && !/^gemini-[a-z0-9.-]+$/.test(parts[1])) {
15368
+ issues.push(issue2("semantic", "unsupported_pi_model", label, "Pi model must use an allowlisted aggregator prefix"));
15369
+ } else if (owned) {
15370
+ if (owned !== vendor) {
15371
+ issues.push(issue2("semantic", "pi_native_vendor_forbidden", label, "native vendor cannot use Pi"));
15372
+ }
15373
+ } else {
15374
+ const prefix = canonicalVendor(parts[1], aliases);
15375
+ if (options.nativePiBrakeProviders.includes(prefix) || options.nativePiBrakeProviders.includes(vendor)) {
15376
+ issues.push(issue2("semantic", "pi_native_vendor_forbidden", label, "native vendor cannot use Pi"));
15377
+ }
15378
+ }
15379
+ } else if (vendor !== ceiling.provider || typeof model.model === "string" && model.model.includes("/")) {
15380
+ issues.push(issue2("semantic", "native_vendor_mismatch", label, "native route vendor or model does not match the harness ceiling"));
15381
+ }
15382
+ const origin = model.origin;
15383
+ if (isObject(origin) && typeof origin.source === "string" && typeof origin.sha256 === "string") {
15384
+ if (!own(evidence, origin.source)) {
15385
+ issues.push(issue2("semantic", "origin_evidence_missing", label, `missing evidence bytes for ${origin.source}`));
15386
+ } else if (sha256Bytes(evidence[origin.source]) !== origin.sha256) {
15387
+ issues.push(issue2("semantic", "origin_hash_mismatch", label, "origin evidence hash does not match supplied bytes"));
15388
+ }
15389
+ }
15390
+ }
15391
+ function admittedRoute(model) {
15392
+ return model.status === "admitted";
15393
+ }
15394
+ function validateRoleSemantics(role, id, label, models, options, issues) {
15395
+ if (!Array.isArray(role.roster)) return;
15396
+ for (const [index, entry] of role.roster.entries()) {
15397
+ const path = `${label}.roster[${index}]`;
15398
+ const model = models.get(entry.route);
15399
+ if (!model) {
15400
+ issues.push(issue2("reference", "route_unknown", path, `roster references unknown route ${entry.route}`));
15401
+ continue;
15402
+ }
15403
+ const ceiling = options.ceilings[model.harness];
15404
+ if (!ceiling) continue;
15405
+ if (Array.isArray(ceiling.roles) && ceiling.roles.length > 0 && !ceiling.roles.includes(role.purpose)) {
15406
+ issues.push(issue2("semantic", "purpose_ceiling_mismatch", path, `purpose ${role.purpose} is not allowed on harness ${model.harness}`));
15407
+ }
15408
+ if (!model.permissions.includes(role.permission)) {
15409
+ issues.push(issue2("semantic", "permission_escalation", path, "role permission is not granted by the route"));
15410
+ }
15411
+ if (!ceiling.permissions.includes(role.permission)) {
15412
+ issues.push(issue2("semantic", "permission_escalation", path, "role permission exceeds the harness ceiling"));
15413
+ }
15414
+ if (Array.isArray(ceiling.efforts) && ceiling.efforts.length > 0 && entry.effort !== void 0 && !ceiling.efforts.includes(entry.effort)) {
15415
+ issues.push(issue2("semantic", "effort_ceiling_mismatch", path, "effort is not allowed on the harness ceiling"));
15416
+ }
15417
+ if (!admittedRoute(model)) {
15418
+ issues.push(issue2("semantic", "route_not_admitted", path, `route ${entry.route} is ${model.status} and is not treated as admitted`));
15419
+ }
15420
+ if (model.harness === "pi") {
15421
+ if (role.permission === "edit" && !["writer", "experiment"].includes(role.purpose)) {
15422
+ issues.push(issue2("semantic", "permission_escalation", path, "Pi critic/planner cannot edit"));
15423
+ }
15424
+ if (role.purpose === "writer" && (model.permissions.length !== 1 || model.permissions[0] !== "edit")) {
15425
+ issues.push(issue2("semantic", "permission_escalation", path, "Pi writer requires edit permission only"));
15426
+ }
15427
+ }
15428
+ }
15429
+ if (id === role.purpose && CRITIC_PURPOSES.includes(role.purpose)) {
15430
+ const admitted = role.roster.map((entry) => models.get(entry.route)).filter((model) => model && admittedRoute(model));
15431
+ if (admitted.length !== 1) {
15432
+ issues.push(issue2("semantic", "critic_route_count", label, "required critic purpose files must list exactly one admitted route"));
15433
+ } else if (admitted[0].permissions.length !== 1 || admitted[0].permissions[0] !== "read-only" || role.permission !== "read-only") {
15434
+ issues.push(issue2("semantic", "permission_escalation", label, "critic purpose files must be read-only"));
15435
+ }
15436
+ }
15437
+ }
15438
+ function validateExtends(roles, issues) {
15439
+ for (const [id, role] of roles) {
15440
+ if (role.extends === void 0) continue;
15441
+ const label = `roles/${id}`;
15442
+ if (!roles.has(role.extends)) {
15443
+ issues.push(issue2("reference", "role_extends_unknown", label, `extends names unknown role ${role.extends}`));
15444
+ continue;
15445
+ }
15446
+ const seen = /* @__PURE__ */ new Set([id]);
15447
+ let current = role.extends;
15448
+ while (typeof current === "string" && current.length > 0) {
15449
+ if (seen.has(current)) {
15450
+ issues.push(issue2("semantic", "role_extends_cycle", label, `extends cycle: ${[...seen, current].join(" -> ")}`));
15451
+ break;
15452
+ }
15453
+ seen.add(current);
15454
+ current = roles.get(current)?.extends;
15455
+ }
15456
+ }
15457
+ }
15458
+ function validateCriticVendors(roles, models, options, issues) {
15459
+ const aliases = options.vendorAliases ?? {};
15460
+ const critics = [];
15461
+ for (const purpose of CRITIC_PURPOSES) {
15462
+ const role = roles.get(purpose);
15463
+ if (!role || role.purpose !== purpose) continue;
15464
+ const admitted = (role.roster ?? []).map((entry) => models.get(entry.route)).filter((model) => model && admittedRoute(model));
15465
+ if (admitted.length === 1) critics.push({ purpose, vendor: canonicalVendor(admitted[0].vendor, aliases) });
15466
+ }
15467
+ if (critics.length === 2 && critics[0].vendor === critics[1].vendor) {
15468
+ issues.push(issue2("semantic", "critic_vendor_collision", "roles/reviewer-arch", "required critics must have independent vendors"));
15469
+ }
15470
+ const writer = roles.get("writer");
15471
+ if (writer && critics.length > 0) {
15472
+ for (const entry of writer.roster ?? []) {
15473
+ const model = models.get(entry.route);
15474
+ if (!model || !admittedRoute(model)) continue;
15475
+ const vendor = canonicalVendor(model.vendor, aliases);
15476
+ if (critics.some((critic) => critic.vendor === vendor)) {
15477
+ issues.push(issue2("semantic", "writer_critic_vendor_collision", "roles/writer", "writer and critics must have independent vendors"));
15478
+ }
15479
+ }
15480
+ }
15481
+ }
15482
+ function validatePolicyDraft(input, options) {
15483
+ const issues = [];
15484
+ if (!isObject(input)) {
15485
+ return { ok: false, issues: [issue2("schema", "root_not_object", "<draft>", "draft input must be a mapping")] };
15486
+ }
15487
+ const unexpected = extraKeys(input, ["models", "roles", "evidence"]);
15488
+ if (unexpected.length > 0) {
15489
+ issues.push(issue2("schema", "schema_additionalProperties", "<draft>", `unexpected properties: ${unexpected.join(", ")}`));
15490
+ }
15491
+ if (!isObject(options) || !isObject(options.ceilings) || !Array.isArray(options.nativePiBrakeProviders) || !Array.isArray(options.piAllowedProviders)) {
15492
+ issues.push(issue2("semantic", "ceilings_required", "<options>", "code-owned ceilings and vendor brakes must be supplied explicitly"));
15493
+ return { ok: false, issues: sortIssues2(issues) };
15494
+ }
15495
+ const evidence = isObject(input.evidence) ? input.evidence : {};
15496
+ if (input.evidence !== void 0 && !isObject(input.evidence)) {
15497
+ issues.push(issue2("schema", "root_not_object", "evidence", "evidence must be a mapping of source path to bytes"));
15498
+ }
15499
+ const modelDocs = collectDocuments(input.models, "models", issues);
15500
+ const roleDocs = collectDocuments(input.roles, "roles", issues);
15501
+ for (const entry of modelDocs) validateModelShape(entry.document, entry.id, entry.label, issues);
15502
+ for (const entry of roleDocs) validateRoleShape(entry.document, entry.id, entry.label, issues);
15503
+ if (issues.length > 0) return { ok: false, issues: sortIssues2(issues) };
15504
+ const models = /* @__PURE__ */ new Map();
15505
+ for (const entry of modelDocs) {
15506
+ validateModelSemantics(entry.document, entry.label, options, evidence, issues);
15507
+ const normalized = cloneJson(entry.document);
15508
+ normalized.id = entry.id;
15509
+ models.set(entry.id, normalized);
15510
+ }
15511
+ const roles = /* @__PURE__ */ new Map();
15512
+ for (const entry of roleDocs) {
15513
+ validateRoleSemantics(entry.document, entry.id, entry.label, models, options, issues);
15514
+ const normalized = cloneJson(entry.document);
15515
+ normalized.id = entry.id;
15516
+ roles.set(entry.id, normalized);
15517
+ }
15518
+ validateExtends(roles, issues);
15519
+ validateCriticVendors(roles, models, options, issues);
15520
+ if (issues.length > 0) return { ok: false, issues: sortIssues2(issues) };
15521
+ const data = freezeDeep({
15522
+ models: Object.fromEntries([...models.entries()].sort(([left], [right]) => compareCodeUnits2(left, right))),
15523
+ roles: Object.fromEntries([...roles.entries()].sort(([left], [right]) => compareCodeUnits2(left, right)))
15524
+ });
15525
+ return { ok: true, data };
15526
+ }
15527
+
14944
15528
  // plugins/kxm/src/template.ts
14945
15529
  var import_yaml2 = __toESM(require_dist(), 1);
14946
- import { createHash } from "node:crypto";
15530
+ import { createHash as createHash2 } from "node:crypto";
14947
15531
  var KXM_TEMPLATE_ID = "builtin-minimal";
14948
15532
  var KXM_TEMPLATE_PROVENANCE_PATH = ".kxm/template-provenance.yaml";
14949
15533
  var CURRENT_KXM_TEMPLATE_VARIANT = "v4-registry";
14950
15534
  var SUPPORTED_KXM_TEMPLATE_VARIANTS = ["v1", "v2", "v3-policy", "v4-registry"];
14951
- function compareCodeUnits2(left, right) {
15535
+ function compareCodeUnits3(left, right) {
14952
15536
  return left < right ? -1 : left > right ? 1 : 0;
14953
15537
  }
14954
15538
  function canonicalJson(value) {
@@ -14956,10 +15540,10 @@ function canonicalJson(value) {
14956
15540
  return JSON.stringify(value);
14957
15541
  }
14958
15542
  if (Array.isArray(value)) return `[${value.map((candidate) => canonicalJson(candidate)).join(",")}]`;
14959
- return `{${Object.keys(value).sort(compareCodeUnits2).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
15543
+ return `{${Object.keys(value).sort(compareCodeUnits3).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
14960
15544
  }
14961
15545
  function kxmContentSha256(input) {
14962
- return `sha256:${createHash("sha256").update(input).digest("hex")}`;
15546
+ return `sha256:${createHash2("sha256").update(input).digest("hex")}`;
14963
15547
  }
14964
15548
  function authorityProjection(value) {
14965
15549
  const projected = structuredClone(value);
@@ -15104,8 +15688,41 @@ function coreTemplate(projectId, projectName, variant) {
15104
15688
  schema: "kxm.routes.v2",
15105
15689
  updatedAt: "2026-09-23T00:00:00.000Z",
15106
15690
  admitted: ["anthropic/fable", "xai/grok-4.6"],
15107
- disabled: [],
15108
- roles: { implementer: ["xai/grok-4.6"] }
15691
+ disabled: []
15692
+ });
15693
+ files.set(".kxm/models/grok-default.yaml", {
15694
+ schema: "kxm.model.v2",
15695
+ id: "grok-default",
15696
+ harness: "grok",
15697
+ model: "grok-4.6",
15698
+ vendor: "xai",
15699
+ status: "admitted",
15700
+ permissions: ["edit"]
15701
+ });
15702
+ files.set(".kxm/models/fable-default.yaml", {
15703
+ schema: "kxm.model.v2",
15704
+ id: "fable-default",
15705
+ harness: "claude",
15706
+ model: "fable",
15707
+ vendor: "anthropic",
15708
+ status: "admitted",
15709
+ permissions: ["read-only"]
15710
+ });
15711
+ files.set(".kxm/roles/writer.yaml", {
15712
+ schema: "kxm.role.v2",
15713
+ id: "writer",
15714
+ purpose: "writer",
15715
+ permission: "edit",
15716
+ description: "Primary implementation agent.",
15717
+ roster: [{ route: "grok-default" }]
15718
+ });
15719
+ files.set(".kxm/roles/planner.yaml", {
15720
+ schema: "kxm.role.v2",
15721
+ id: "planner",
15722
+ purpose: "planner",
15723
+ permission: "read-only",
15724
+ description: "Plans the change before implementation.",
15725
+ roster: [{ route: "fable-default" }]
15109
15726
  });
15110
15727
  files.set(".kxm/gates.yaml", {
15111
15728
  schema: "kxm.gate-registry.v1",
@@ -15134,7 +15751,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
15134
15751
  authoritySha256: kxmAuthoritySha256(value),
15135
15752
  bytes: bytes.byteLength
15136
15753
  };
15137
- }).sort((left, right) => compareCodeUnits2(left.path, right.path));
15754
+ }).sort((left, right) => compareCodeUnits3(left.path, right.path));
15138
15755
  const templateRevision = provenanceRevision(records);
15139
15756
  const provenance = {
15140
15757
  schema: "kxm.template-provenance.v1",
@@ -15148,7 +15765,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
15148
15765
  KXM_TEMPLATE_PROVENANCE_PATH,
15149
15766
  Buffer.from((0, import_yaml2.stringify)(provenance, { lineWidth: 0 }), "utf8")
15150
15767
  ]);
15151
- fileEntries.sort(([left], [right]) => compareCodeUnits2(left, right));
15768
+ fileEntries.sort(([left], [right]) => compareCodeUnits3(left, right));
15152
15769
  const files = new Map(fileEntries);
15153
15770
  return { projectId, projectName, templateRevision, provenance, files, values };
15154
15771
  }
@@ -15984,7 +16601,7 @@ function resolveDispatchStatus(entry, detected, authenticated, issues) {
15984
16601
  return { status: "no", supported: false, reason: "not_authenticated" };
15985
16602
  }
15986
16603
  if (authenticated === null) {
15987
- const authIssue = issues.find((issue3) => issue3 === "auth_context_required" || issue3 === "auth_unknown" || issue3 === "auth_unparsed" || issue3.startsWith("auth_"));
16604
+ const authIssue = issues.find((issue4) => issue4 === "auth_context_required" || issue4 === "auth_unknown" || issue4 === "auth_unparsed" || issue4.startsWith("auth_"));
15988
16605
  return { status: "no", supported: false, reason: authIssue ?? issues[0] ?? "auth_unknown" };
15989
16606
  }
15990
16607
  return { status: "yes", supported: true };
@@ -16331,8 +16948,8 @@ async function probeHarnessAssignmentAsync(options) {
16331
16948
  var KxmConfigError = class extends Error {
16332
16949
  issues;
16333
16950
  constructor(issues) {
16334
- const sorted = sortIssues2(issues);
16335
- super(sorted.map((issue3) => `${issue3.file}: ${issue3.code}: ${issue3.message}`).join("\n"));
16951
+ const sorted = sortIssues3(issues);
16952
+ super(sorted.map((issue4) => `${issue4.file}: ${issue4.code}: ${issue4.message}`).join("\n"));
16336
16953
  this.name = "KxmConfigError";
16337
16954
  this.issues = sorted;
16338
16955
  }
@@ -16345,12 +16962,13 @@ var RESOURCE_SCHEMA = Object.freeze({
16345
16962
  project: { identity: "kxm.project.v1", file: "project.schema.json" },
16346
16963
  repository: { identity: "kxm.repository.v1", file: "repository.schema.json" },
16347
16964
  agent: { identity: "kxm.agent.v1", file: "agent.schema.json" },
16348
- model: { identity: "kxm.model.v1", file: "model.schema.json" },
16965
+ model: { identity: "kxm.model.v2", file: "model.schema.json" },
16966
+ role: { identity: "kxm.role.v2", file: "role.schema.json" },
16349
16967
  environment: { identity: "kxm.environment.v1", file: "environment.schema.json" },
16350
16968
  workflow: { identity: "kxm.workflow.v1", file: "workflow.schema.json" },
16351
16969
  "gate-registry": { identity: "kxm.gate-registry.v1", file: "gate-registry.schema.json" }
16352
16970
  });
16353
- var IDENTIFIER = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
16971
+ var IDENTIFIER2 = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
16354
16972
  var WINDOWS_RESERVED = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³]|conin\$|conout\$|clock\$)$/i;
16355
16973
  var BUILTIN_EXECUTORS = ["local", "ssh", "exe-dev"];
16356
16974
  var BUILTIN_TOOL_PRESETS = ["coordinator", "read-only", "workspace-writer", "tests-writer"];
@@ -16362,17 +16980,17 @@ var SECRET_VALUE_PATTERNS = [
16362
16980
  /\bAKIA[A-Z0-9]{16}\b/,
16363
16981
  /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/
16364
16982
  ];
16365
- function compareCodeUnits3(left, right) {
16983
+ function compareCodeUnits4(left, right) {
16366
16984
  return left < right ? -1 : left > right ? 1 : 0;
16367
16985
  }
16368
- function sortIssues2(issues) {
16369
- 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));
16986
+ function sortIssues3(issues) {
16987
+ 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));
16370
16988
  }
16371
- function issue2(phase, code, file, message) {
16989
+ function issue3(phase, code, file, message) {
16372
16990
  return { phase, code, file, message };
16373
16991
  }
16374
16992
  function fail2(phase, code, file, message) {
16375
- throw new KxmConfigError([issue2(phase, code, file, message)]);
16993
+ throw new KxmConfigError([issue3(phase, code, file, message)]);
16376
16994
  }
16377
16995
  function isJsonObject2(value) {
16378
16996
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -16465,7 +17083,7 @@ var KxmSchemaRegistry = class {
16465
17083
  validate(kind, value, file) {
16466
17084
  const definition = RESOURCE_SCHEMA[kind];
16467
17085
  if (value.schema !== definition.identity) {
16468
- return [issue2("schema", "schema_identity_mismatch", file, `expected ${definition.identity}, received ${String(value.schema)}`)];
17086
+ return [issue3("schema", "schema_identity_mismatch", file, `expected ${definition.identity}, received ${String(value.schema)}`)];
16469
17087
  }
16470
17088
  const validator = this.validators.get(kind);
16471
17089
  if (!validator) throw new Error(`missing KXM validator for ${kind}`);
@@ -16489,7 +17107,7 @@ var KxmSchemaRegistry = class {
16489
17107
  }
16490
17108
  validateAuxiliary(value, file, identity, validator) {
16491
17109
  if (value.schema !== identity) {
16492
- return [issue2("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
17110
+ return [issue3("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
16493
17111
  }
16494
17112
  if (validator(value)) return [];
16495
17113
  return (validator.errors ?? []).map((error) => schemaIssue(file, error));
@@ -16499,7 +17117,7 @@ var cachedRunEventRegistry;
16499
17117
  function validateRunEvent(value, file) {
16500
17118
  const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
16501
17119
  if (!registry.runEventValidator(value)) {
16502
- throw new KxmConfigError([issue2(
17120
+ throw new KxmConfigError([issue3(
16503
17121
  "schema",
16504
17122
  "run_event_invalid",
16505
17123
  file,
@@ -16515,7 +17133,7 @@ function syncEventSchemaErrors(value) {
16515
17133
  function validateDriveReceipt(value, file) {
16516
17134
  const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
16517
17135
  if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.drive-receipt.v1") {
16518
- throw new KxmConfigError([issue2(
17136
+ throw new KxmConfigError([issue3(
16519
17137
  "schema",
16520
17138
  "drive_receipt_invalid",
16521
17139
  file,
@@ -16523,7 +17141,7 @@ function validateDriveReceipt(value, file) {
16523
17141
  )]);
16524
17142
  }
16525
17143
  if (!registry.driveReceiptValidator(value)) {
16526
- throw new KxmConfigError([issue2(
17144
+ throw new KxmConfigError([issue3(
16527
17145
  "schema",
16528
17146
  "drive_receipt_invalid",
16529
17147
  file,
@@ -16534,10 +17152,10 @@ function validateDriveReceipt(value, file) {
16534
17152
  function validateCoordinator(value, file) {
16535
17153
  const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
16536
17154
  if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.coordinator.v1") {
16537
- throw new KxmConfigError([issue2("schema", "coordinator_invalid", file, "expected kxm.coordinator.v1")]);
17155
+ throw new KxmConfigError([issue3("schema", "coordinator_invalid", file, "expected kxm.coordinator.v1")]);
16538
17156
  }
16539
17157
  if (!registry.coordinatorValidator(value)) {
16540
- throw new KxmConfigError([issue2(
17158
+ throw new KxmConfigError([issue3(
16541
17159
  "schema",
16542
17160
  "coordinator_invalid",
16543
17161
  file,
@@ -16548,10 +17166,10 @@ function validateCoordinator(value, file) {
16548
17166
  function validateIntakeMessage(value, file) {
16549
17167
  const registry = cachedRunEventRegistry ??= new KxmSchemaRegistry();
16550
17168
  if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.intake-message.v1") {
16551
- throw new KxmConfigError([issue2("schema", "intake_message_invalid", file, "expected kxm.intake-message.v1")]);
17169
+ throw new KxmConfigError([issue3("schema", "intake_message_invalid", file, "expected kxm.intake-message.v1")]);
16552
17170
  }
16553
17171
  if (!registry.intakeMessageValidator(value)) {
16554
- throw new KxmConfigError([issue2(
17172
+ throw new KxmConfigError([issue3(
16555
17173
  "schema",
16556
17174
  "intake_message_invalid",
16557
17175
  file,
@@ -16562,7 +17180,7 @@ function validateIntakeMessage(value, file) {
16562
17180
  function schemaIssue(file, error) {
16563
17181
  const location = error.instancePath || "/";
16564
17182
  const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
16565
- return issue2("schema", `schema_${error.keyword}`, file, `${location} ${error.message ?? "is invalid"}${suffix}`);
17183
+ return issue3("schema", `schema_${error.keyword}`, file, `${location} ${error.message ?? "is invalid"}${suffix}`);
16566
17184
  }
16567
17185
  function canonicalHostPath(path) {
16568
17186
  const resolved = resolve(path);
@@ -16588,7 +17206,7 @@ function portableBindingIssue(root, pathHint, repositoryId) {
16588
17206
  if (!existsSync3(current)) break;
16589
17207
  const stat = lstatSync(current);
16590
17208
  if (stat.isSymbolicLink()) {
16591
- return issue2("path", "repository_binding_path_link", ".kxm/project.yaml", `portable pathHint for ${repositoryId} traverses a symbolic link or junction`);
17209
+ return issue3("path", "repository_binding_path_link", ".kxm/project.yaml", `portable pathHint for ${repositoryId} traverses a symbolic link or junction`);
16592
17210
  }
16593
17211
  }
16594
17212
  const binding = resolve(root, ...pathHint.split("/"));
@@ -16596,7 +17214,7 @@ function portableBindingIssue(root, pathHint, repositoryId) {
16596
17214
  const realRoot = canonicalHostPath(root);
16597
17215
  const realBinding = canonicalHostPath(binding);
16598
17216
  if (!containedHostPath(realRoot, realBinding)) {
16599
- return issue2("path", "repository_binding_outside_project", ".kxm/project.yaml", `portable pathHint for ${repositoryId} resolves outside the authoritative project root`);
17217
+ return issue3("path", "repository_binding_outside_project", ".kxm/project.yaml", `portable pathHint for ${repositoryId} resolves outside the authoritative project root`);
16600
17218
  }
16601
17219
  }
16602
17220
  return void 0;
@@ -16611,7 +17229,7 @@ function portablePath(path) {
16611
17229
  return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== ".." && !WINDOWS_RESERVED.test((segment.split(".")[0] ?? segment).replace(/[ .]+$/g, "")) && !segment.endsWith(".") && !segment.endsWith(" ") && !segment.includes(":"));
16612
17230
  }
16613
17231
  function kxmResourceIdentifier(id) {
16614
- return id.length <= 64 && IDENTIFIER.test(id) && !WINDOWS_RESERVED.test(id);
17232
+ return id.length <= 64 && IDENTIFIER2.test(id) && !WINDOWS_RESERVED.test(id);
16615
17233
  }
16616
17234
  function resourceIdentifier(id) {
16617
17235
  return kxmResourceIdentifier(id);
@@ -16689,7 +17307,7 @@ function readTemplateProvenance(registry, root) {
16689
17307
  fail2("path", "template_provenance_path_invalid", label, `managed path ${path} is not a portable project-configuration path`);
16690
17308
  }
16691
17309
  if (seen.has(folded)) fail2("path", "template_provenance_path_collision", label, `managed path ${path} collides after case folding`);
16692
- if (prior && compareCodeUnits3(prior, path) >= 0) fail2("semantic", "template_provenance_order_invalid", label, "managed file entries must use strict code-unit order");
17310
+ if (prior && compareCodeUnits4(prior, path) >= 0) fail2("semantic", "template_provenance_order_invalid", label, "managed file entries must use strict code-unit order");
16693
17311
  seen.add(folded);
16694
17312
  prior = path;
16695
17313
  }
@@ -16706,27 +17324,27 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
16706
17324
  fail2("path", "resource_directory_invalid", displayPath(root, directory), "resource directory must be a regular directory");
16707
17325
  }
16708
17326
  const issues = [];
16709
- const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) => compareCodeUnits3(left.name, right.name));
17327
+ const entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) => compareCodeUnits4(left.name, right.name));
16710
17328
  for (const entry of entries) {
16711
17329
  if (entry.isDirectory()) {
16712
- issues.push(issue2("path", "nested_resource_directory", displayPath(root, join2(directory, entry.name)), "nested resource directories are not allowed"));
17330
+ issues.push(issue3("path", "nested_resource_directory", displayPath(root, join2(directory, entry.name)), "nested resource directories are not allowed"));
16713
17331
  continue;
16714
17332
  }
16715
17333
  if (!entry.name.toLowerCase().endsWith(".yaml") && !entry.name.toLowerCase().endsWith(".yml")) continue;
16716
17334
  if (!entry.isFile() || extname(entry.name) !== ".yaml") {
16717
- issues.push(issue2("path", "resource_filename_invalid", displayPath(root, join2(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
17335
+ issues.push(issue3("path", "resource_filename_invalid", displayPath(root, join2(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
16718
17336
  continue;
16719
17337
  }
16720
17338
  const id = basename(entry.name, ".yaml");
16721
17339
  if (kind === "model" && id === "inventory") continue;
16722
17340
  if (id === replacedId) continue;
16723
17341
  if (!resourceIdentifier(id)) {
16724
- issues.push(issue2("path", "resource_id_invalid", displayPath(root, join2(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
17342
+ issues.push(issue3("path", "resource_id_invalid", displayPath(root, join2(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
16725
17343
  continue;
16726
17344
  }
16727
17345
  const collision = [...resources.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
16728
17346
  if (collision) {
16729
- issues.push(issue2("path", "resource_id_collision", displayPath(root, join2(directory, entry.name)), `${id} case-folds to existing ${collision}`));
17347
+ issues.push(issue3("path", "resource_id_collision", displayPath(root, join2(directory, entry.name)), `${id} case-folds to existing ${collision}`));
16730
17348
  continue;
16731
17349
  }
16732
17350
  try {
@@ -16761,7 +17379,7 @@ function mapResource(resource, map, issues) {
16761
17379
  if (!id) return;
16762
17380
  const collision = [...map.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
16763
17381
  if (collision) {
16764
- issues.push(issue2("path", "resource_id_collision", resource.logicalPath, `${id} case-folds to existing ${collision}`));
17382
+ issues.push(issue3("path", "resource_id_collision", resource.logicalPath, `${id} case-folds to existing ${collision}`));
16765
17383
  return;
16766
17384
  }
16767
17385
  map.set(id, resource);
@@ -16785,7 +17403,7 @@ function validatePortablePaths(resource, issues) {
16785
17403
  }
16786
17404
  for (const candidate of candidates) {
16787
17405
  if (!portablePath(candidate.path)) {
16788
- issues.push(issue2("path", "portable_path_invalid", resource.logicalPath, `${candidate.label} is not a normalized forward-slash relative path: ${candidate.path}`));
17406
+ issues.push(issue3("path", "portable_path_invalid", resource.logicalPath, `${candidate.label} is not a normalized forward-slash relative path: ${candidate.path}`));
16789
17407
  }
16790
17408
  }
16791
17409
  }
@@ -16795,14 +17413,14 @@ function selectorCandidates(selectorValue, models, file, label, issues) {
16795
17413
  const profile = stringValue(selector.profile);
16796
17414
  if (profile) {
16797
17415
  const model = models.get(profile);
16798
- if (!model) issues.push(issue2("reference", "model_profile_unknown", file, `${label} references unknown model profile ${profile}`));
17416
+ if (!model) issues.push(issue3("reference", "model_profile_unknown", file, `${label} references unknown model profile ${profile}`));
16799
17417
  return model ? [model] : [];
16800
17418
  }
16801
17419
  const tag = stringValue(selector.tag);
16802
17420
  if (!tag) return [];
16803
17421
  const capabilities = new Set(names(selector.capabilities));
16804
17422
  const candidates = [...models.values()].filter((model) => names(model.value.tags).includes(tag) && [...capabilities].every((capability) => names(model.value.capabilities).includes(capability)));
16805
- if (candidates.length === 0) issues.push(issue2("reference", "model_tag_unresolved", file, `${label} selector tag ${tag} has no matching profile`));
17423
+ if (candidates.length === 0) issues.push(issue3("reference", "model_tag_unresolved", file, `${label} selector tag ${tag} has no matching profile`));
16806
17424
  return candidates;
16807
17425
  }
16808
17426
  function modelCandidates(selectorValue, models) {
@@ -16815,7 +17433,7 @@ function modelCandidates(selectorValue, models) {
16815
17433
  return [{
16816
17434
  key: `profile:${profile}`,
16817
17435
  profile,
16818
- ...stringValue(resource.value.provider) ? { provider: stringValue(resource.value.provider) } : {},
17436
+ ...stringValue(resource.value.provider) || stringValue(resource.value.vendor) ? { provider: stringValue(resource.value.provider) ?? stringValue(resource.value.vendor) } : {},
16819
17437
  ...stringValue(resource.value.model) ? { model: stringValue(resource.value.model) } : {}
16820
17438
  }];
16821
17439
  }
@@ -16825,7 +17443,7 @@ function modelCandidates(selectorValue, models) {
16825
17443
  return [...models.values()].filter((resource) => names(resource.value.tags).includes(tag) && [...capabilities].every((capability) => names(resource.value.capabilities).includes(capability))).map((resource) => ({
16826
17444
  key: `profile:${resource.id ?? "unknown"}`,
16827
17445
  ...resource.id ? { profile: resource.id } : {},
16828
- ...stringValue(resource.value.provider) ? { provider: stringValue(resource.value.provider) } : {},
17446
+ ...stringValue(resource.value.provider) || stringValue(resource.value.vendor) ? { provider: stringValue(resource.value.provider) ?? stringValue(resource.value.vendor) } : {},
16829
17447
  ...stringValue(resource.value.model) ? { model: stringValue(resource.value.model) } : {}
16830
17448
  }));
16831
17449
  }
@@ -16857,13 +17475,13 @@ function validateEnvironment(resource, issues) {
16857
17475
  const secret = objectValue(candidate);
16858
17476
  const name = secret && stringValue(secret.name);
16859
17477
  if (!name) continue;
16860
- if (secretNames.has(name)) issues.push(issue2("semantic", "secret_name_duplicate", resource.logicalPath, `secret environment name ${name} is duplicated`));
17478
+ if (secretNames.has(name)) issues.push(issue3("semantic", "secret_name_duplicate", resource.logicalPath, `secret environment name ${name} is duplicated`));
16861
17479
  secretNames.add(name);
16862
- if (Object.hasOwn(values, name)) issues.push(issue2("semantic", "environment_name_conflict", resource.logicalPath, `${name} appears in both values and secrets`));
17480
+ if (Object.hasOwn(values, name)) issues.push(issue3("semantic", "environment_name_conflict", resource.logicalPath, `${name} appears in both values and secrets`));
16863
17481
  }
16864
17482
  for (const [name, value] of Object.entries(values)) {
16865
17483
  if (typeof value === "string" && SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
16866
- issues.push(issue2("semantic", "probable_secret_value", resource.logicalPath, `${name} resembles a credential and must be represented by secrets[].ref`));
17484
+ issues.push(issue3("semantic", "probable_secret_value", resource.logicalPath, `${name} resembles a credential and must be represented by secrets[].ref`));
16867
17485
  }
16868
17486
  }
16869
17487
  }
@@ -16875,29 +17493,29 @@ function validateToolScope(agent, step, file, stepId, issues) {
16875
17493
  if (!requested) return;
16876
17494
  const ceiling = objectValue(agent.value.tools);
16877
17495
  if (!ceiling) {
16878
- issues.push(issue2("semantic", "tool_scope_expansion", file, `${stepId} requests tools but agent ${agent.id ?? "unknown"} declares no tool ceiling`));
17496
+ issues.push(issue3("semantic", "tool_scope_expansion", file, `${stepId} requests tools but agent ${agent.id ?? "unknown"} declares no tool ceiling`));
16879
17497
  return;
16880
17498
  }
16881
17499
  const requestedPreset = stringValue(requested.preset);
16882
17500
  const ceilingPreset = stringValue(ceiling.preset);
16883
17501
  if (requestedPreset !== ceilingPreset) {
16884
- issues.push(issue2("semantic", "tool_scope_expansion", file, `${stepId} tool preset ${String(requestedPreset)} does not preserve agent ${agent.id ?? "unknown"} preset ${String(ceilingPreset)}`));
17502
+ issues.push(issue3("semantic", "tool_scope_expansion", file, `${stepId} tool preset ${String(requestedPreset)} does not preserve agent ${agent.id ?? "unknown"} preset ${String(ceilingPreset)}`));
16885
17503
  }
16886
17504
  const allowed = new Set(names(ceiling.allow));
16887
17505
  const denied = new Set(names(ceiling.deny));
16888
17506
  for (const tool of names(requested.allow)) {
16889
- 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`));
17507
+ 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`));
16890
17508
  }
16891
17509
  const requestedDenied = new Set(names(requested.deny));
16892
17510
  for (const tool of denied) {
16893
- if (!requestedDenied.has(tool)) issues.push(issue2("semantic", "tool_scope_expansion", file, `${stepId} drops agent ${agent.id ?? "unknown"} denial for ${tool}`));
17511
+ if (!requestedDenied.has(tool)) issues.push(issue3("semantic", "tool_scope_expansion", file, `${stepId} drops agent ${agent.id ?? "unknown"} denial for ${tool}`));
16894
17512
  }
16895
17513
  }
16896
17514
  function validateToolPolicy(resource, policy, label, issues) {
16897
17515
  if (!policy) return;
16898
17516
  const allowed = new Set(names(policy.allow));
16899
17517
  for (const tool of names(policy.deny)) {
16900
- if (allowed.has(tool)) issues.push(issue2("semantic", "tool_policy_contradiction", resource.logicalPath, `${label} both allows and denies ${tool}`));
17518
+ if (allowed.has(tool)) issues.push(issue3("semantic", "tool_policy_contradiction", resource.logicalPath, `${label} both allows and denies ${tool}`));
16901
17519
  }
16902
17520
  }
16903
17521
  function validateAgentScope(agent, step, repositories, file, stepId, issues) {
@@ -16908,14 +17526,14 @@ function validateAgentScope(agent, step, repositories, file, stepId, issues) {
16908
17526
  if (!repositories.has(repositoryId)) continue;
16909
17527
  const ceiling = agentRepositories[repositoryId] ?? defaultAccess ?? "none";
16910
17528
  if (accessRank(requested) > accessRank(ceiling)) {
16911
- issues.push(issue2("semantic", "repository_scope_expansion", file, `${stepId} requests ${String(requested)} access to ${repositoryId} beyond agent ${agent.id ?? "unknown"} ceiling ${String(ceiling)}`));
17529
+ issues.push(issue3("semantic", "repository_scope_expansion", file, `${stepId} requests ${String(requested)} access to ${repositoryId} beyond agent ${agent.id ?? "unknown"} ceiling ${String(ceiling)}`));
16912
17530
  }
16913
17531
  }
16914
17532
  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)));
16915
17533
  for (const candidate of valuesOf(step, "secrets")) {
16916
17534
  const reference = stringValue(objectValue(candidate)?.ref);
16917
17535
  if (reference && !grants.has(reference)) {
16918
- issues.push(issue2("semantic", "secret_scope_expansion", file, `${stepId} requests secret ${reference} beyond agent ${agent.id ?? "unknown"} ceiling`));
17536
+ issues.push(issue3("semantic", "secret_scope_expansion", file, `${stepId} requests secret ${reference} beyond agent ${agent.id ?? "unknown"} ceiling`));
16919
17537
  }
16920
17538
  }
16921
17539
  }
@@ -16930,7 +17548,7 @@ function gateOutcomeImpossible(step, stepId, file) {
16930
17548
  const impossible = declared.filter((outcome) => !produced.includes(outcome) && outcome !== "implementation_failure" && outcome !== "repro_missing");
16931
17549
  const missing = produced.filter((outcome) => !declared.includes(outcome));
16932
17550
  if (impossible.length === 0 || missing.length === 0) return void 0;
16933
- 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 ")}`);
17551
+ 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 ")}`);
16934
17552
  }
16935
17553
  function transition(value) {
16936
17554
  if (typeof value === "string") return { target: value };
@@ -16946,13 +17564,13 @@ function transition(value) {
16946
17564
  function validateWorkflow(workflow, agents, models, repositories, gates, issues) {
16947
17565
  const file = workflow.logicalPath;
16948
17566
  const coordinator = stringValue(workflow.value.coordinator) ?? "coordinator";
16949
- if (!agents.has(coordinator)) issues.push(issue2("reference", "coordinator_unknown", file, `coordinator ${coordinator} does not exist`));
17567
+ if (!agents.has(coordinator)) issues.push(issue3("reference", "coordinator_unknown", file, `coordinator ${coordinator} does not exist`));
16950
17568
  const steps = valuesOf(workflow.value, "steps").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
16951
17569
  const stepIndex = /* @__PURE__ */ new Map();
16952
17570
  for (const [index, step] of steps.entries()) {
16953
17571
  const id = stringValue(step.id);
16954
17572
  if (!id) continue;
16955
- if (stepIndex.has(id)) issues.push(issue2("semantic", "step_id_duplicate", file, `step ${id} is duplicated`));
17573
+ if (stepIndex.has(id)) issues.push(issue3("semantic", "step_id_duplicate", file, `step ${id} is duplicated`));
16956
17574
  else stepIndex.set(id, index);
16957
17575
  }
16958
17576
  for (const field of ["reproOracle", "planHash"]) {
@@ -16962,16 +17580,16 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
16962
17580
  const evidenceKey = stringValue(oracle.evidenceKey);
16963
17581
  const index = stageId === void 0 ? void 0 : stepIndex.get(stageId);
16964
17582
  if (index === void 0) {
16965
- issues.push(issue2("reference", "oracle_stage_unknown", file, `${field} references unknown stage ${String(stageId)}`));
17583
+ issues.push(issue3("reference", "oracle_stage_unknown", file, `${field} references unknown stage ${String(stageId)}`));
16966
17584
  continue;
16967
17585
  }
16968
17586
  const evidence = new Set(valuesOf(steps[index] ?? {}, "requiredEvidence").map((candidate) => stringValue(objectValue(candidate)?.key)).filter((candidate) => Boolean(candidate)));
16969
- if (!evidenceKey || !evidence.has(evidenceKey)) issues.push(issue2("reference", "oracle_evidence_unknown", file, `${field} references undeclared evidence ${String(evidenceKey)} in ${stageId}`));
17587
+ if (!evidenceKey || !evidence.has(evidenceKey)) issues.push(issue3("reference", "oracle_evidence_unknown", file, `${field} references undeclared evidence ${String(evidenceKey)} in ${stageId}`));
16970
17588
  }
16971
17589
  const planStages = new Set(names(workflow.value.requirePlanHash));
16972
- if (planStages.size > 0 && !objectValue(workflow.value.planHash)) issues.push(issue2("semantic", "plan_hash_missing", file, "requirePlanHash requires planHash"));
17590
+ if (planStages.size > 0 && !objectValue(workflow.value.planHash)) issues.push(issue3("semantic", "plan_hash_missing", file, "requirePlanHash requires planHash"));
16973
17591
  for (const stageId of planStages) {
16974
- if (!stepIndex.has(stageId)) issues.push(issue2("reference", "plan_hash_stage_unknown", file, `requirePlanHash references unknown stage ${stageId}`));
17592
+ if (!stepIndex.has(stageId)) issues.push(issue3("reference", "plan_hash_stage_unknown", file, `requirePlanHash references unknown stage ${stageId}`));
16975
17593
  }
16976
17594
  const adjacency = steps.map(() => /* @__PURE__ */ new Set());
16977
17595
  const terminalReachable = /* @__PURE__ */ new Set();
@@ -16983,23 +17601,23 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
16983
17601
  validateToolPolicy(workflow, objectValue(step.tools), `${stepId}.tools`, issues);
16984
17602
  const primaryAgentId = stringValue(step.agent);
16985
17603
  if ((kind === "agent" || kind === "moa") && primaryAgentId && !agents.has(primaryAgentId)) {
16986
- issues.push(issue2("reference", "agent_unknown", file, `${stepId} references unknown agent ${primaryAgentId}`));
17604
+ issues.push(issue3("reference", "agent_unknown", file, `${stepId} references unknown agent ${primaryAgentId}`));
16987
17605
  }
16988
17606
  const gate = stringValue(step.gate);
16989
17607
  if (kind === "gate" && gate) {
16990
- if (!gates) issues.push(issue2("reference", "gate_registry_missing", file, `${stepId} requires .kxm/gates.yaml`));
16991
- else if (!gates.has(gate)) issues.push(issue2("reference", "gate_unknown", file, `${stepId} references unregistered gate ${gate}`));
17608
+ if (!gates) issues.push(issue3("reference", "gate_registry_missing", file, `${stepId} requires .kxm/gates.yaml`));
17609
+ else if (!gates.has(gate)) issues.push(issue3("reference", "gate_unknown", file, `${stepId} references unregistered gate ${gate}`));
16992
17610
  }
16993
17611
  if (step.expect !== void 0 && (kind !== "gate" || !["pass", "fail"].includes(String(step.expect)))) {
16994
- issues.push(issue2("semantic", "gate_expect_invalid", file, `${stepId} expect is gate-only pass or fail`));
17612
+ issues.push(issue3("semantic", "gate_expect_invalid", file, `${stepId} expect is gate-only pass or fail`));
16995
17613
  }
16996
17614
  if (kind === "gate" && Object.keys(objectValue(step.on) ?? {}).some((outcome) => outcome === "implementation_failure" || outcome === "repro_missing")) {
16997
- issues.push(issue2("semantic", "gate_outcome_renamed", file, `${stepId} must use implementation-failure and repro-missing`));
17615
+ issues.push(issue3("semantic", "gate_outcome_renamed", file, `${stepId} must use implementation-failure and repro-missing`));
16998
17616
  }
16999
17617
  const impossibleOutcome = kind === "gate" ? gateOutcomeImpossible(step, stepId, file) : void 0;
17000
17618
  if (impossibleOutcome) issues.push(impossibleOutcome);
17001
17619
  for (const repositoryId of Object.keys(objectValue(step.repositories) ?? {})) {
17002
- if (!repositories.has(repositoryId)) issues.push(issue2("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
17620
+ if (!repositories.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
17003
17621
  }
17004
17622
  selectorCandidates(step.model, models, file, `${stepId}.model`, issues);
17005
17623
  const assignment = objectValue(step.assignments);
@@ -17007,7 +17625,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17007
17625
  const allowedAgents = declaredAgents.length > 0 ? declaredAgents : primaryAgentId ? [primaryAgentId] : [];
17008
17626
  for (const agentId of allowedAgents) {
17009
17627
  const agent = agents.get(agentId);
17010
- if (!agent) issues.push(issue2("reference", "assignment_agent_unknown", file, `${stepId} allows unknown agent ${agentId}`));
17628
+ if (!agent) issues.push(issue3("reference", "assignment_agent_unknown", file, `${stepId} allows unknown agent ${agentId}`));
17011
17629
  else {
17012
17630
  validateAgentScope(agent, step, repositories, file, stepId, issues);
17013
17631
  if (step.model !== void 0 && agent.value.model !== void 0) {
@@ -17015,37 +17633,37 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17015
17633
  const ceilingModels = modelCandidates(agent.value.model, models);
17016
17634
  const effectiveModels = requestedModels.filter((requested) => ceilingModels.some((ceiling) => compatibleModel(requested, ceiling)));
17017
17635
  if (requestedModels.length > 0 && effectiveModels.length === 0) {
17018
- issues.push(issue2("semantic", "model_selector_incompatible", file, `${stepId} model selector has no candidate within agent ${agentId} ceiling`));
17636
+ issues.push(issue3("semantic", "model_selector_incompatible", file, `${stepId} model selector has no candidate within agent ${agentId} ceiling`));
17019
17637
  }
17020
17638
  }
17021
17639
  }
17022
17640
  }
17023
17641
  if (primaryAgentId && allowedAgents.length > 0 && !allowedAgents.includes(primaryAgentId)) {
17024
- issues.push(issue2("semantic", "primary_agent_ineligible", file, `${stepId} primary agent ${primaryAgentId} is not in allowedAgents`));
17642
+ issues.push(issue3("semantic", "primary_agent_ineligible", file, `${stepId} primary agent ${primaryAgentId} is not in allowedAgents`));
17025
17643
  }
17026
17644
  const minimum = numberValue(assignment?.minimum, 1);
17027
17645
  const target = numberValue(assignment?.target, minimum);
17028
17646
  const maximum = numberValue(assignment?.maximum, target);
17029
17647
  const maxParallel = numberValue(assignment?.maxParallel, maximum);
17030
- if (!(minimum <= target && target <= maximum)) issues.push(issue2("semantic", "assignment_bounds_invalid", file, `${stepId} must satisfy minimum <= target <= maximum`));
17031
- if (maxParallel > maximum) issues.push(issue2("semantic", "assignment_parallelism_invalid", file, `${stepId} maxParallel exceeds maximum`));
17648
+ if (!(minimum <= target && target <= maximum)) issues.push(issue3("semantic", "assignment_bounds_invalid", file, `${stepId} must satisfy minimum <= target <= maximum`));
17649
+ if (maxParallel > maximum) issues.push(issue3("semantic", "assignment_parallelism_invalid", file, `${stepId} maxParallel exceeds maximum`));
17032
17650
  if (assignment && allowedAgents.length > 0 && maximum > allowedAgents.length && kind === "moa") {
17033
- issues.push(issue2("semantic", "assignment_pool_too_small", file, `${stepId} maximum ${maximum} exceeds ${allowedAgents.length} eligible agents`));
17651
+ issues.push(issue3("semantic", "assignment_pool_too_small", file, `${stepId} maximum ${maximum} exceeds ${allowedAgents.length} eligible agents`));
17034
17652
  }
17035
17653
  const maxWriteRepositories = assignment && typeof assignment.maxWriteRepositories === "number" ? assignment.maxWriteRepositories : void 0;
17036
17654
  if (maxWriteRepositories !== void 0) {
17037
17655
  const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
17038
- if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
17656
+ if (maxWriteRepositories > writable) issues.push(issue3("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
17039
17657
  }
17040
17658
  const join19 = objectValue(step.join);
17041
17659
  const minimumPassed = join19 && typeof join19.minimumPassed === "number" ? join19.minimumPassed : void 0;
17042
- if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
17660
+ if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue3("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
17043
17661
  const distinctBy = names(assignment?.distinctBy);
17044
17662
  if (distinctBy.length > 0) {
17045
17663
  const candidates = diversityCandidates(step.model, allowedAgents, agents, models);
17046
17664
  for (const dimension of distinctBy) {
17047
17665
  const distinct = new Set(candidates.map((candidate) => candidate[dimension]).filter((value) => Boolean(value)));
17048
- if (distinct.size < target) issues.push(issue2("semantic", "model_diversity_impossible", file, `${stepId} requires ${target} distinct ${dimension} values but resolves ${distinct.size}`));
17666
+ if (distinct.size < target) issues.push(issue3("semantic", "model_diversity_impossible", file, `${stepId} requires ${target} distinct ${dimension} values but resolves ${distinct.size}`));
17049
17667
  }
17050
17668
  }
17051
17669
  const evidenceKeys = /* @__PURE__ */ new Set();
@@ -17054,29 +17672,29 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17054
17672
  if (!requirement) continue;
17055
17673
  const key = stringValue(requirement.key);
17056
17674
  if (key) {
17057
- if (evidenceKeys.has(key)) issues.push(issue2("semantic", "evidence_key_duplicate", file, `${stepId} evidence key ${key} is duplicated`));
17675
+ if (evidenceKeys.has(key)) issues.push(issue3("semantic", "evidence_key_duplicate", file, `${stepId} evidence key ${key} is duplicated`));
17058
17676
  evidenceKeys.add(key);
17059
17677
  }
17060
17678
  const policy = objectValue(requirement.producerPolicy);
17061
17679
  if (!policy) continue;
17062
17680
  const eligible = names(policy.eligibleAgents);
17063
17681
  const producerMinimum = numberValue(policy.minimumProducers, 1);
17064
- 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`));
17682
+ 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`));
17065
17683
  for (const agentId of eligible) {
17066
- if (!agents.has(agentId)) issues.push(issue2("reference", "producer_agent_unknown", file, `${stepId}.${String(key)} references unknown producer ${agentId}`));
17067
- if (allowedAgents.length > 0 && !allowedAgents.includes(agentId)) issues.push(issue2("semantic", "producer_agent_ineligible", file, `${stepId}.${String(key)} producer ${agentId} is outside allowedAgents`));
17684
+ if (!agents.has(agentId)) issues.push(issue3("reference", "producer_agent_unknown", file, `${stepId}.${String(key)} references unknown producer ${agentId}`));
17685
+ if (allowedAgents.length > 0 && !allowedAgents.includes(agentId)) issues.push(issue3("semantic", "producer_agent_ineligible", file, `${stepId}.${String(key)} producer ${agentId} is outside allowedAgents`));
17068
17686
  }
17069
17687
  const degradation = objectValue(policy.degradation);
17070
- if (degradation && numberValue(degradation.minimumProducers, 1) >= producerMinimum) issues.push(issue2("semantic", "producer_degradation_invalid", file, `${stepId}.${String(key)} degradation must lower minimumProducers`));
17688
+ if (degradation && numberValue(degradation.minimumProducers, 1) >= producerMinimum) issues.push(issue3("semantic", "producer_degradation_invalid", file, `${stepId}.${String(key)} degradation must lower minimumProducers`));
17071
17689
  }
17072
17690
  const writes = Object.values(objectValue(step.repositories) ?? {}).some((access) => access === "write");
17073
17691
  const planHashStage = stringValue(objectValue(workflow.value.planHash)?.stageId);
17074
17692
  const planHashIndex = planHashStage === void 0 ? void 0 : stepIndex.get(planHashStage);
17075
17693
  if (writes && planHashIndex !== void 0 && index > planHashIndex && !planStages.has(stepId)) {
17076
- issues.push(issue2("semantic", "mutation_missing_plan_hash", file, `${stepId} mutates after the approved-plan stage but is absent from requirePlanHash`));
17694
+ issues.push(issue3("semantic", "mutation_missing_plan_hash", file, `${stepId} mutates after the approved-plan stage but is absent from requirePlanHash`));
17077
17695
  }
17078
17696
  const outcomes = objectValue(step.on) ?? {};
17079
- if (Object.keys(outcomes).length === 0) issues.push(issue2("semantic", "step_transitions_missing", file, `${stepId} has no declared transitions`));
17697
+ if (Object.keys(outcomes).length === 0) issues.push(issue3("semantic", "step_transitions_missing", file, `${stepId} has no declared transitions`));
17080
17698
  for (const [outcome, rawTransition] of Object.entries(outcomes)) {
17081
17699
  const parsed = transition(rawTransition);
17082
17700
  if (parsed.target === "$terminal") {
@@ -17086,29 +17704,29 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17086
17704
  }
17087
17705
  const targetIndex = parsed.target === void 0 ? void 0 : stepIndex.get(parsed.target);
17088
17706
  if (targetIndex === void 0) {
17089
- issues.push(issue2("reference", "transition_target_unknown", file, `${stepId}.${outcome} references unknown target ${String(parsed.target)}`));
17707
+ issues.push(issue3("reference", "transition_target_unknown", file, `${stepId}.${outcome} references unknown target ${String(parsed.target)}`));
17090
17708
  continue;
17091
17709
  }
17092
17710
  adjacency[index]?.add(targetIndex);
17093
17711
  if (targetIndex <= index) {
17094
17712
  hasBackEdge = true;
17095
- if (parsed.maxTransitions === void 0) issues.push(issue2("semantic", "back_edge_unbounded", file, `${stepId}.${outcome} back-edge requires maxTransitions`));
17713
+ if (parsed.maxTransitions === void 0) issues.push(issue3("semantic", "back_edge_unbounded", file, `${stepId}.${outcome} back-edge requires maxTransitions`));
17096
17714
  }
17097
17715
  }
17098
17716
  }
17099
17717
  if (hasBackEdge && typeof objectValue(workflow.value.limits)?.maxTransitions !== "number") {
17100
- issues.push(issue2("semantic", "workflow_cycle_unbounded", file, "workflow with back-edges requires limits.maxTransitions"));
17718
+ issues.push(issue3("semantic", "workflow_cycle_unbounded", file, "workflow with back-edges requires limits.maxTransitions"));
17101
17719
  }
17102
17720
  const readyIndex = stepIndex.get("ready");
17103
17721
  const verifyIndex = stepIndex.get("verify");
17104
17722
  if (readyIndex !== void 0 && verifyIndex !== void 0) {
17105
17723
  const verifyTargets = adjacency[verifyIndex] ?? /* @__PURE__ */ new Set();
17106
17724
  if (!verifyTargets.has(readyIndex)) {
17107
- issues.push(issue2("semantic", "verify_must_precede_ready", file, "verify must transition to ready"));
17725
+ issues.push(issue3("semantic", "verify_must_precede_ready", file, "verify must transition to ready"));
17108
17726
  }
17109
17727
  for (const [srcIndex, targets] of adjacency.entries()) {
17110
17728
  if (targets.has(readyIndex) && srcIndex !== verifyIndex) {
17111
- issues.push(issue2("semantic", "verify_must_precede_ready", file, `step ${String(steps[srcIndex]?.id)} transitions to ready bypassing verify`));
17729
+ issues.push(issue3("semantic", "verify_must_precede_ready", file, `step ${String(steps[srcIndex]?.id)} transitions to ready bypassing verify`));
17112
17730
  }
17113
17731
  }
17114
17732
  }
@@ -17123,7 +17741,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17123
17741
  queue.push(target);
17124
17742
  }
17125
17743
  }
17126
- 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`));
17744
+ 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`));
17127
17745
  for (const [requiredIndex, requiredStep] of steps.entries()) {
17128
17746
  if (requiredStep.kind !== "gate" && requiredStep.kind !== "approval") continue;
17129
17747
  const reachableWithoutRequired = /* @__PURE__ */ new Set();
@@ -17139,7 +17757,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17139
17757
  }
17140
17758
  }
17141
17759
  if ([...completedTerminalSources].some((source) => source !== requiredIndex && reachableWithoutRequired.has(source))) {
17142
- issues.push(issue2("semantic", "required_step_bypass", file, `a completed path can bypass required ${String(requiredStep.id)}`));
17760
+ issues.push(issue3("semantic", "required_step_bypass", file, `a completed path can bypass required ${String(requiredStep.id)}`));
17143
17761
  }
17144
17762
  }
17145
17763
  const canTerminate = new Set(terminalReachable);
@@ -17153,7 +17771,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17153
17771
  }
17154
17772
  }
17155
17773
  }
17156
- 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`));
17774
+ 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`));
17157
17775
  }
17158
17776
  }
17159
17777
  function validateModelReferences(models, issues) {
@@ -17166,7 +17784,7 @@ function validateModelReferences(models, issues) {
17166
17784
  const visited = /* @__PURE__ */ new Set();
17167
17785
  const walk = (id, path) => {
17168
17786
  if (visiting.has(id)) {
17169
- issues.push(issue2("semantic", "model_fallback_cycle", models.get(id)?.logicalPath ?? ".kxm/models", `fallback profile cycle: ${[...path, id].join(" -> ")}`));
17787
+ issues.push(issue3("semantic", "model_fallback_cycle", models.get(id)?.logicalPath ?? ".kxm/models", `fallback profile cycle: ${[...path, id].join(" -> ")}`));
17170
17788
  return;
17171
17789
  }
17172
17790
  if (visited.has(id)) return;
@@ -17194,17 +17812,17 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17194
17812
  if (!id) continue;
17195
17813
  const folded = id.toLocaleLowerCase("en-US");
17196
17814
  const previous = foldedRepositories.get(folded);
17197
- if (previous) issues.push(issue2("path", "repository_id_collision", project.logicalPath, `${id} case-folds to existing ${previous}`));
17815
+ if (previous) issues.push(issue3("path", "repository_id_collision", project.logicalPath, `${id} case-folds to existing ${previous}`));
17198
17816
  else foldedRepositories.set(folded, id);
17199
17817
  repositoryIds.add(id);
17200
17818
  if (entry.role === "control") controls += 1;
17201
17819
  }
17202
- if (controls !== 1) issues.push(issue2("semantic", "control_repository_count", project.logicalPath, `expected exactly one control repository, received ${controls}`));
17820
+ if (controls !== 1) issues.push(issue3("semantic", "control_repository_count", project.logicalPath, `expected exactly one control repository, received ${controls}`));
17203
17821
  for (const repository of repositories.values()) {
17204
- if (repository.value.projectId !== project.value.id) issues.push(issue2("reference", "repository_project_mismatch", repository.logicalPath, "projectId does not match the authoritative project"));
17822
+ if (repository.value.projectId !== project.value.id) issues.push(issue3("reference", "repository_project_mismatch", repository.logicalPath, "projectId does not match the authoritative project"));
17205
17823
  const id = stringValue(repository.value.repositoryId);
17206
- if (!id || !repositoryIds.has(id)) issues.push(issue2("reference", "repository_definition_unknown", repository.logicalPath, `repositoryId ${String(id)} is not bound by project.yaml`));
17207
- if (repository.id !== id) issues.push(issue2("reference", "repository_binding_identity_mismatch", repository.logicalPath, `binding ${String(repository.id)} declares repositoryId ${String(id)}`));
17824
+ if (!id || !repositoryIds.has(id)) issues.push(issue3("reference", "repository_definition_unknown", repository.logicalPath, `repositoryId ${String(id)} is not bound by project.yaml`));
17825
+ if (repository.id !== id) issues.push(issue3("reference", "repository_binding_identity_mismatch", repository.logicalPath, `binding ${String(repository.id)} declares repositoryId ${String(id)}`));
17208
17826
  }
17209
17827
  for (const resource of [project, ...environments]) validatePortablePaths(resource, issues);
17210
17828
  for (const environment of environments) validateEnvironment(environment, issues);
@@ -17214,25 +17832,25 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17214
17832
  const definition = objectValue(value);
17215
17833
  const executable = Array.isArray(definition?.argv) ? definition.argv[0] : void 0;
17216
17834
  if (definition?.kind === "command" && typeof executable === "string" && (executable === "." || executable === ".." || executable.includes("\\") || !executable.startsWith("/") && executable.includes("/"))) {
17217
- issues.push(issue2("semantic", "gate_executable_invalid", ".kxm/gates.yaml", `${id} argv[0] must be a bare executable or absolute POSIX path`));
17835
+ issues.push(issue3("semantic", "gate_executable_invalid", ".kxm/gates.yaml", `${id} argv[0] must be a bare executable or absolute POSIX path`));
17218
17836
  }
17219
17837
  }
17220
17838
  const presets = new Set(options.registeredToolPresets ?? BUILTIN_TOOL_PRESETS);
17221
17839
  const harnesses = new Set(options.registeredHarnesses ?? BUILTIN_HARNESS_IDS);
17222
17840
  const defaultExecutor = stringValue(project.value.defaultExecutor);
17223
- if (defaultExecutor && !executors.has(defaultExecutor)) issues.push(issue2("reference", "executor_unknown", project.logicalPath, `defaultExecutor ${defaultExecutor} is not registered`));
17841
+ if (defaultExecutor && !executors.has(defaultExecutor)) issues.push(issue3("reference", "executor_unknown", project.logicalPath, `defaultExecutor ${defaultExecutor} is not registered`));
17224
17842
  const defaultHarness = stringValue(project.value.defaultHarness) ?? DEFAULT_HARNESS;
17225
- if (!harnesses.has(defaultHarness)) issues.push(issue2("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
17843
+ if (!harnesses.has(defaultHarness)) issues.push(issue3("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
17226
17844
  for (const agent of agents.values()) {
17227
17845
  validateToolPolicy(agent, objectValue(agent.value.tools), "tools", issues);
17228
17846
  const executor = stringValue(agent.value.executor);
17229
- if (executor && !executors.has(executor)) issues.push(issue2("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
17847
+ if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
17230
17848
  const harness = stringValue(agent.value.harness) ?? defaultHarness;
17231
- if (!harnesses.has(harness)) issues.push(issue2("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
17849
+ if (!harnesses.has(harness)) issues.push(issue3("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
17232
17850
  const preset = stringValue(objectValue(agent.value.tools)?.preset);
17233
- if (preset && !presets.has(preset)) issues.push(issue2("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
17851
+ if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
17234
17852
  for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
17235
- if (!repositoryIds.has(repositoryId)) issues.push(issue2("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
17853
+ if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
17236
17854
  }
17237
17855
  const candidates = selectorCandidates(agent.value.model, models, agent.logicalPath, "model", issues);
17238
17856
  const declaredHarness = stringValue(agent.value.harness);
@@ -17242,14 +17860,14 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17242
17860
  const candidateModel = stringValue(candidate.value.model);
17243
17861
  const validation = validateHarnessModelPair(declaredHarness, { provider: candidateProvider, model: candidateModel });
17244
17862
  if (!validation.valid) {
17245
- issues.push(issue2("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
17863
+ issues.push(issue3("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
17246
17864
  }
17247
17865
  }
17248
17866
  }
17249
17867
  }
17250
17868
  validateModelReferences(models, issues);
17251
17869
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
17252
- if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
17870
+ if (!workflows.has(defaultWorkflow)) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
17253
17871
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
17254
17872
  if (projectRoot) {
17255
17873
  const writerRolePath = join2(projectRoot, ".kxm", "roles", "writer.yaml");
@@ -17259,7 +17877,27 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17259
17877
  const rawRole = parseRestrictedYaml2(writerRole ?? readFileSync(writerRolePath, "utf8"));
17260
17878
  const roleObj = objectValue(rawRole);
17261
17879
  const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
17262
- const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
17880
+ const enabledRosterModels = rosterEntries.flatMap((entry) => {
17881
+ const direct = stringValue(entry.model);
17882
+ const route = stringValue(entry.route);
17883
+ const named = direct ? [direct] : [];
17884
+ if (route) {
17885
+ const modelFile = join2(projectRoot, ".kxm", "models", `${route}.yaml`);
17886
+ if (existsSync3(modelFile)) {
17887
+ try {
17888
+ const modelDoc = objectValue(parseRestrictedYaml2(readFileSync(modelFile, "utf8"), modelFile));
17889
+ const model = modelDoc ? stringValue(modelDoc.model) : void 0;
17890
+ const vendor = modelDoc ? stringValue(modelDoc.vendor) : void 0;
17891
+ const harness = modelDoc ? stringValue(modelDoc.harness) : void 0;
17892
+ if (model) named.push(model);
17893
+ if (vendor && model) named.push(`${vendor}/${model}`);
17894
+ if (harness && model) named.push(`${harness}/${model}`);
17895
+ } catch {
17896
+ }
17897
+ }
17898
+ }
17899
+ return named;
17900
+ });
17263
17901
  const agentModelObj = objectValue(implementerAgent.value.model);
17264
17902
  const agentModelStr = stringValue(implementerAgent.value.model);
17265
17903
  const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
@@ -17268,7 +17906,7 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17268
17906
  if (enabledRosterModels.length > 0 && canonicalAgentModel) {
17269
17907
  const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
17270
17908
  if (!matches) {
17271
- 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}`));
17909
+ 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}`));
17272
17910
  }
17273
17911
  }
17274
17912
  } catch (error) {
@@ -17278,7 +17916,83 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17278
17916
  }
17279
17917
  }
17280
17918
  }
17281
- return sortIssues2(issues);
17919
+ if (projectRoot && existsSync3(join2(projectRoot, ".kxm", "roster.yaml"))) {
17920
+ issues.push(...developerRolePolicyIssues(projectRoot));
17921
+ }
17922
+ return sortIssues3(issues);
17923
+ }
17924
+ function developerCeilings() {
17925
+ const script = join2(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
17926
+ let loaded;
17927
+ try {
17928
+ 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}))`], {
17929
+ encoding: "utf8",
17930
+ timeout: 15e3
17931
+ });
17932
+ } catch (error) {
17933
+ return { ok: false, detail: error instanceof Error ? error.message : String(error) };
17934
+ }
17935
+ if (loaded.error) {
17936
+ const code = loaded.error.code;
17937
+ const detail = code === "ETIMEDOUT" || loaded.signal ? `timed out after 15s (${loaded.error.message})` : loaded.error.message;
17938
+ return { ok: false, detail };
17939
+ }
17940
+ if (loaded.status !== 0 || !loaded.stdout) {
17941
+ const stderr = (loaded.stderr ?? "").trim();
17942
+ return { ok: false, detail: stderr || `scripts/harness-run.mjs exited ${loaded.status}` };
17943
+ }
17944
+ try {
17945
+ return { ok: true, ...JSON.parse(loaded.stdout) };
17946
+ } catch (error) {
17947
+ return { ok: false, detail: `ceilings JSON parse failed: ${error instanceof Error ? error.message : String(error)}` };
17948
+ }
17949
+ }
17950
+ function developerRolePolicyIssues(projectRoot) {
17951
+ const rolesDir = join2(projectRoot, ".kxm", "roles");
17952
+ const modelsDir = join2(projectRoot, ".kxm", "models");
17953
+ const roles = {};
17954
+ const models = {};
17955
+ const evidence = {};
17956
+ const parseIssues = [];
17957
+ const readMap = (dir, into, skipInventory) => {
17958
+ if (!existsSync3(dir)) return;
17959
+ for (const name of readdirSync(dir)) {
17960
+ if (!name.endsWith(".yaml")) continue;
17961
+ const id = basename(name, ".yaml");
17962
+ if (skipInventory && id === "inventory") continue;
17963
+ try {
17964
+ const value = parseRestrictedYaml2(readFileSync(join2(dir, name), "utf8"), `${dir}/${name}`);
17965
+ into[id] = value;
17966
+ } catch (error) {
17967
+ if (error instanceof KxmConfigError) {
17968
+ parseIssues.push(...error.issues);
17969
+ continue;
17970
+ }
17971
+ }
17972
+ }
17973
+ };
17974
+ readMap(rolesDir, roles, false);
17975
+ readMap(modelsDir, models, true);
17976
+ for (const model of Object.values(models)) {
17977
+ const origin = objectValue(model.origin);
17978
+ const source = origin ? stringValue(origin.source) : void 0;
17979
+ if (!source || evidence[source] !== void 0) continue;
17980
+ const evidenceFile = isAbsolute(source) ? source : join2(projectRoot, source);
17981
+ if (existsSync3(evidenceFile)) evidence[source] = readFileSync(evidenceFile, "utf8");
17982
+ }
17983
+ const ceilings = developerCeilings();
17984
+ if (!ceilings.ok) {
17985
+ return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/roster.yaml", `developer ceilings could not be loaded: ${ceilings.detail}`)];
17986
+ }
17987
+ const result = validatePolicyDraft({ models, roles, evidence }, {
17988
+ ceilings: ceilings.ROUTES,
17989
+ nativePiBrakeProviders: ceilings.NATIVE_PI_BRAKE_PROVIDERS,
17990
+ piAllowedProviders: ceilings.PI_ALLOWED_PROVIDERS,
17991
+ piNativeVendorProviders: ceilings.PI_NATIVE_VENDOR_PROVIDERS,
17992
+ vendorAliases: { "x-ai": "xai", moonshotai: "moonshot", "google-ai": "google", qwen: "alibaba" }
17993
+ });
17994
+ if (result.ok) return parseIssues;
17995
+ return [...parseIssues, ...result.issues.map((entry) => issue3(entry.phase, entry.code, entry.file, entry.message))];
17282
17996
  }
17283
17997
  function kxmCanonicalJson(value) {
17284
17998
  if (value === null || typeof value !== "object") return JSON.stringify(value);
@@ -17289,8 +18003,8 @@ function canonicalize(value) {
17289
18003
  return kxmCanonicalJson(value);
17290
18004
  }
17291
18005
  function bundleRevision(resources) {
17292
- const hash = createHash2("sha256");
17293
- for (const resource of [...resources].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath))) {
18006
+ const hash = createHash3("sha256");
18007
+ for (const resource of [...resources].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath))) {
17294
18008
  hash.update(resource.logicalPath, "utf8");
17295
18009
  hash.update("\0", "utf8");
17296
18010
  hash.update(canonicalize(resource.value), "utf8");
@@ -17316,7 +18030,7 @@ function discoverGitRoot(start = process.cwd()) {
17316
18030
  }
17317
18031
  function assertNoRegisteredGates(options) {
17318
18032
  if ("registeredGates" in options) {
17319
- throw new KxmConfigError([issue2("semantic", "registered_gates_removed", ".kxm/gates.yaml", "registeredGates was removed; declare gates in .kxm/gates.yaml")]);
18033
+ throw new KxmConfigError([issue3("semantic", "registered_gates_removed", ".kxm/gates.yaml", "registeredGates was removed; declare gates in .kxm/gates.yaml")]);
17320
18034
  }
17321
18035
  }
17322
18036
  function loadKxmProject(projectRoot, options = {}) {
@@ -17329,7 +18043,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
17329
18043
  const root = resolve(projectRoot);
17330
18044
  const legacyPresent = legacyConfigFilesAt(root);
17331
18045
  if (legacyPresent.length > 0) {
17332
- throw new KxmConfigError(legacyPresent.map((file) => issue2(
18046
+ throw new KxmConfigError(legacyPresent.map((file) => issue3(
17333
18047
  "semantic",
17334
18048
  "legacy_state_unsupported",
17335
18049
  file,
@@ -17344,20 +18058,25 @@ function loadProjectBundle(projectRoot, options, candidate) {
17344
18058
  if (templateProvenance && templateProvenance.inputs && typeof templateProvenance.inputs === "object" && !Array.isArray(templateProvenance.inputs)) {
17345
18059
  const provenanceProjectId = stringValue(templateProvenance.inputs.projectId);
17346
18060
  if (provenanceProjectId !== stringValue(project.value.id)) {
17347
- earlyIssues.push(issue2("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
18061
+ earlyIssues.push(issue3("semantic", "template_provenance_project_mismatch", ".kxm/template-provenance.yaml", "template provenance belongs to a different project identity"));
17348
18062
  }
17349
18063
  }
17350
18064
  const declaredRepositoryIds = new Set(valuesOf(project.value, "repositories").map((candidate2) => stringValue(objectValue(candidate2)?.id)).filter((candidate2) => candidate2 !== void 0));
17351
- for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(compareCodeUnits3)) {
18065
+ for (const repositoryId of Object.keys(options.repositoryBindings ?? {}).sort(compareCodeUnits4)) {
17352
18066
  if (!resourceIdentifier(repositoryId)) {
17353
- earlyIssues.push(issue2("path", "repository_binding_id_invalid", ".kxm/project.yaml", `host-local binding identity ${repositoryId} is invalid`));
18067
+ earlyIssues.push(issue3("path", "repository_binding_id_invalid", ".kxm/project.yaml", `host-local binding identity ${repositoryId} is invalid`));
17354
18068
  } else if (!declaredRepositoryIds.has(repositoryId)) {
17355
- earlyIssues.push(issue2("reference", "repository_binding_unknown", ".kxm/project.yaml", `host-local binding references unknown repository ${repositoryId}`));
18069
+ earlyIssues.push(issue3("reference", "repository_binding_unknown", ".kxm/project.yaml", `host-local binding references unknown repository ${repositoryId}`));
17356
18070
  }
17357
18071
  }
17358
18072
  if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
17359
18073
  const agents = listNamedResources(registry, root, join2(root, ".kxm", "agents"), ".kxm/agents", "agent");
17360
18074
  const models = listNamedResources(registry, root, join2(root, ".kxm", "models"), ".kxm/models", "model");
18075
+ const roles = listNamedResources(registry, root, join2(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
18076
+ if (writerRole !== void 0) {
18077
+ const logicalPath = ".kxm/roles/writer.yaml";
18078
+ roles.set("writer", { kind: "role", id: "writer", file: join2(root, logicalPath), logicalPath, value: resourceValue(registry, writerRole, logicalPath, "role") });
18079
+ }
17361
18080
  const workflows = listNamedResources(registry, root, join2(root, ".kxm", "workflows"), ".kxm/workflows", "workflow", workflowCandidate?.id);
17362
18081
  if (workflowCandidate) {
17363
18082
  const { id, document } = workflowCandidate;
@@ -17387,20 +18106,20 @@ function loadProjectBundle(projectRoot, options, candidate) {
17387
18106
  const repositoryId = entry && stringValue(entry.id);
17388
18107
  const pathHint = entry && stringValue(entry.pathHint);
17389
18108
  const role = entry && stringValue(entry.role);
17390
- const required = entry?.required !== false;
18109
+ const required2 = entry?.required !== false;
17391
18110
  if (!repositoryId) continue;
17392
18111
  const hasLocalBinding = Object.hasOwn(options.repositoryBindings ?? {}, repositoryId);
17393
18112
  const localBinding = options.repositoryBindings?.[repositoryId];
17394
18113
  if (hasLocalBinding && (!localBinding || !isAbsolute(localBinding))) {
17395
- loadIssues.push(issue2("path", "repository_binding_not_absolute", ".kxm/project.yaml", `host-local binding for ${repositoryId} must be absolute`));
18114
+ loadIssues.push(issue3("path", "repository_binding_not_absolute", ".kxm/project.yaml", `host-local binding for ${repositoryId} must be absolute`));
17396
18115
  continue;
17397
18116
  }
17398
18117
  if (role === "control" && localBinding && !sameHostPath(localBinding, root)) {
17399
- loadIssues.push(issue2("path", "control_repository_binding_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must bind to the authoritative project root`));
18118
+ loadIssues.push(issue3("path", "control_repository_binding_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must bind to the authoritative project root`));
17400
18119
  continue;
17401
18120
  }
17402
18121
  if (role === "control" && pathHint !== void 0 && pathHint !== ".") {
17403
- loadIssues.push(issue2("path", "control_repository_path_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must use pathHint .`));
18122
+ loadIssues.push(issue3("path", "control_repository_path_invalid", ".kxm/project.yaml", `control repository ${repositoryId} must use pathHint .`));
17404
18123
  continue;
17405
18124
  }
17406
18125
  if (!hasLocalBinding && role !== "control" && pathHint && portablePath(pathHint)) {
@@ -17412,28 +18131,28 @@ function loadProjectBundle(projectRoot, options, candidate) {
17412
18131
  }
17413
18132
  const binding = hasLocalBinding && localBinding ? resolve(localBinding) : role === "control" ? root : pathHint && portablePath(pathHint) ? resolve(root, ...pathHint.split("/")) : void 0;
17414
18133
  if (!binding) {
17415
- if (required) loadIssues.push(issue2("discovery", "repository_binding_missing", ".kxm/project.yaml", `required repository ${repositoryId} has no portable pathHint or host-local binding`));
18134
+ if (required2) loadIssues.push(issue3("discovery", "repository_binding_missing", ".kxm/project.yaml", `required repository ${repositoryId} has no portable pathHint or host-local binding`));
17416
18135
  continue;
17417
18136
  }
17418
18137
  if (!existsSync3(binding)) {
17419
- if (required || hasLocalBinding) loadIssues.push(issue2("discovery", "repository_binding_unavailable", ".kxm/project.yaml", `${hasLocalBinding ? "explicit" : "required"} repository binding ${repositoryId} is unavailable`));
18138
+ if (required2 || hasLocalBinding) loadIssues.push(issue3("discovery", "repository_binding_unavailable", ".kxm/project.yaml", `${hasLocalBinding ? "explicit" : "required"} repository binding ${repositoryId} is unavailable`));
17420
18139
  continue;
17421
18140
  }
17422
18141
  const bindingStat = lstatSync(binding);
17423
18142
  if (bindingStat.isSymbolicLink() || !bindingStat.isDirectory()) {
17424
- loadIssues.push(issue2("path", "repository_binding_invalid", ".kxm/project.yaml", `repository binding ${repositoryId} must be a regular directory`));
18143
+ loadIssues.push(issue3("path", "repository_binding_invalid", ".kxm/project.yaml", `repository binding ${repositoryId} must be a regular directory`));
17425
18144
  continue;
17426
18145
  }
17427
18146
  if (role === "member") {
17428
18147
  const repositoryGitRoot = discoverGitRoot(binding);
17429
18148
  if (!repositoryGitRoot || !sameHostPath(repositoryGitRoot, binding)) {
17430
- loadIssues.push(issue2("discovery", "repository_git_root_invalid", ".kxm/project.yaml", `member repository ${repositoryId} must bind to its authoritative Git worktree root`));
18149
+ loadIssues.push(issue3("discovery", "repository_git_root_invalid", ".kxm/project.yaml", `member repository ${repositoryId} must bind to its authoritative Git worktree root`));
17431
18150
  continue;
17432
18151
  }
17433
18152
  }
17434
18153
  const memberLegacy = binding === root ? [] : legacyConfigFilesAt(binding);
17435
18154
  if (memberLegacy.length > 0) {
17436
- throw new KxmConfigError(memberLegacy.map((file) => issue2(
18155
+ throw new KxmConfigError(memberLegacy.map((file) => issue3(
17437
18156
  "semantic",
17438
18157
  "legacy_state_unsupported",
17439
18158
  `${repositoryId}/${file}`,
@@ -17443,7 +18162,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
17443
18162
  const foldedBinding = canonicalHostPath(binding).toLocaleLowerCase("en-US");
17444
18163
  const priorBinding = seenBindings.get(foldedBinding);
17445
18164
  if (priorBinding && priorBinding !== repositoryId) {
17446
- loadIssues.push(issue2("path", "repository_binding_collision", ".kxm/project.yaml", `${repositoryId} and ${priorBinding} use the same repository binding`));
18165
+ loadIssues.push(issue3("path", "repository_binding_collision", ".kxm/project.yaml", `${repositoryId} and ${priorBinding} use the same repository binding`));
17447
18166
  continue;
17448
18167
  }
17449
18168
  seenBindings.set(foldedBinding, repositoryId);
@@ -17453,7 +18172,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
17453
18172
  try {
17454
18173
  const resource = readResource(registry, root, repositoryFile, `.kxm/repositories/${repositoryId}/repo.yaml`, "repository", repositoryId, binding);
17455
18174
  if (resource.value.repositoryId !== repositoryId) {
17456
- loadIssues.push(issue2("reference", "repository_binding_identity_mismatch", resource.logicalPath, `binding ${repositoryId} declares repositoryId ${String(resource.value.repositoryId)}`));
18175
+ loadIssues.push(issue3("reference", "repository_binding_identity_mismatch", resource.logicalPath, `binding ${repositoryId} declares repositoryId ${String(resource.value.repositoryId)}`));
17457
18176
  } else {
17458
18177
  mapResource(resource, repositories, loadIssues);
17459
18178
  definitionValidated = repositories.get(repositoryId) === resource;
@@ -17462,13 +18181,13 @@ function loadProjectBundle(projectRoot, options, candidate) {
17462
18181
  if (error instanceof KxmConfigError) loadIssues.push(...error.issues);
17463
18182
  else throw error;
17464
18183
  }
17465
- } else if (required || hasLocalBinding) {
17466
- loadIssues.push(issue2("discovery", "repository_definition_missing", `.kxm/repositories/${repositoryId}/repo.yaml`, `${hasLocalBinding ? "explicit" : "required"} repository ${repositoryId} has no repo.yaml`));
18184
+ } else if (required2 || hasLocalBinding) {
18185
+ loadIssues.push(issue3("discovery", "repository_definition_missing", `.kxm/repositories/${repositoryId}/repo.yaml`, `${hasLocalBinding ? "explicit" : "required"} repository ${repositoryId} has no repo.yaml`));
17467
18186
  }
17468
18187
  const environmentFile = join2(binding, ".kxm", "repo", "env.yaml");
17469
18188
  if (existsSync3(environmentFile)) {
17470
18189
  if (!definitionValidated) {
17471
- loadIssues.push(issue2("reference", "repository_environment_without_definition", `.kxm/repositories/${repositoryId}/env.yaml`, `repository ${repositoryId} environment requires a validated matching repo.yaml`));
18190
+ loadIssues.push(issue3("reference", "repository_environment_without_definition", `.kxm/repositories/${repositoryId}/env.yaml`, `repository ${repositoryId} environment requires a validated matching repo.yaml`));
17472
18191
  } else {
17473
18192
  try {
17474
18193
  environments.push(readResource(registry, root, environmentFile, `.kxm/repositories/${repositoryId}/env.yaml`, "environment", void 0, binding));
@@ -17482,13 +18201,14 @@ function loadProjectBundle(projectRoot, options, candidate) {
17482
18201
  if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
17483
18202
  const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
17484
18203
  if (issues.length > 0) throw new KxmConfigError(issues);
17485
- const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
18204
+ const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...roles.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath));
17486
18205
  return {
17487
18206
  projectRoot: root,
17488
18207
  project,
17489
18208
  repositories,
17490
18209
  agents,
17491
18210
  models,
18211
+ roles,
17492
18212
  workflows,
17493
18213
  environments,
17494
18214
  ...gateRegistry ? { gateRegistry } : {},
@@ -17515,7 +18235,7 @@ function legacyConfigFilesAt(root) {
17515
18235
  return files;
17516
18236
  }
17517
18237
  if (stat.isDirectory()) {
17518
- 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))) {
18238
+ 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))) {
17519
18239
  files.push(`.kxm/config/workflows/${entry.name}`);
17520
18240
  }
17521
18241
  }
@@ -17524,7 +18244,7 @@ function legacyConfigFilesAt(root) {
17524
18244
  }
17525
18245
 
17526
18246
  // plugins/kxm/src/runtime-store.ts
17527
- import { createHash as createHash5, randomUUID } from "node:crypto";
18247
+ import { createHash as createHash6, randomUUID } from "node:crypto";
17528
18248
  import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
17529
18249
  import { dirname as dirname5, resolve as resolve6 } from "node:path";
17530
18250
 
@@ -17532,7 +18252,7 @@ import { dirname as dirname5, resolve as resolve6 } from "node:path";
17532
18252
  import { existsSync as existsSync4 } from "node:fs";
17533
18253
  import { createRequire } from "node:module";
17534
18254
  import { resolve as resolve2 } from "node:path";
17535
- import { pathToFileURL } from "node:url";
18255
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
17536
18256
  var requireFromHere = createRequire(import.meta.url);
17537
18257
  function loadNative() {
17538
18258
  try {
@@ -17571,13 +18291,13 @@ var DatabaseSync = class {
17571
18291
  };
17572
18292
  function openReadOnlyDatabase(path) {
17573
18293
  if (existsSync4(`${path}-wal`)) return new DatabaseSync(path, { readOnly: true });
17574
- const uri = pathToFileURL(resolve2(path));
18294
+ const uri = pathToFileURL2(resolve2(path));
17575
18295
  uri.searchParams.set("immutable", "1");
17576
18296
  return new DatabaseSync(uri.href, { readOnly: true });
17577
18297
  }
17578
18298
 
17579
18299
  // plugins/kxm/src/runtime-paths.ts
17580
- import { createHash as createHash3 } from "node:crypto";
18300
+ import { createHash as createHash4 } from "node:crypto";
17581
18301
  import { realpathSync as realpathSync2 } from "node:fs";
17582
18302
  import { join as join4, resolve as resolve4 } from "node:path";
17583
18303
 
@@ -17631,11 +18351,11 @@ function projectRuntimeKey(projectRoot) {
17631
18351
  canonical = resolve4(projectRoot);
17632
18352
  }
17633
18353
  const folded = process.platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
17634
- return createHash3("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
18354
+ return createHash4("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
17635
18355
  }
17636
18356
 
17637
18357
  // plugins/kxm/src/sync-transform.ts
17638
- import { createHash as createHash4 } from "node:crypto";
18358
+ import { createHash as createHash5 } from "node:crypto";
17639
18359
 
17640
18360
  // plugins/kxm/src/redact.ts
17641
18361
  var SECRET_PATTERNS = [
@@ -17674,7 +18394,7 @@ var KXM_DEFAULT_SYNC_POLICY = Object.freeze({
17674
18394
  diffs: false,
17675
18395
  environmentValues: false
17676
18396
  });
17677
- var KXM_DEFAULT_SYNC_POLICY_REVISION = `sha256:${createHash4("sha256").update(kxmCanonicalJson(KXM_DEFAULT_SYNC_POLICY), "utf8").digest("hex")}`;
18397
+ var KXM_DEFAULT_SYNC_POLICY_REVISION = `sha256:${createHash5("sha256").update(kxmCanonicalJson(KXM_DEFAULT_SYNC_POLICY), "utf8").digest("hex")}`;
17678
18398
  var MIN_REGISTERED_SECRET_CHARS = 4;
17679
18399
  var REDACTED = "[redacted]";
17680
18400
  var PATH_REDACTED = "[path]";
@@ -17979,8 +18699,8 @@ import {
17979
18699
  } from "node:fs";
17980
18700
  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";
17981
18701
  function databaseError(code, file, message) {
17982
- const issue3 = { phase: "semantic", code, file, message };
17983
- return new KxmConfigError([issue3]);
18702
+ const issue4 = { phase: "semantic", code, file, message };
18703
+ return new KxmConfigError([issue4]);
17984
18704
  }
17985
18705
  function checkedParent(path, description) {
17986
18706
  const parent = dirname4(path);
@@ -18403,7 +19123,7 @@ var KxmRuntimeRegistry = class {
18403
19123
  var KXM_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
18404
19124
  function hashKxmDriveLog(events) {
18405
19125
  const body = events.map((event) => `${event.eventId}:${event.sequence}`).join("\n");
18406
- return `sha256:${createHash5("sha256").update(body, "utf8").digest("hex")}`;
19126
+ return `sha256:${createHash6("sha256").update(body, "utf8").digest("hex")}`;
18407
19127
  }
18408
19128
  function verifyKxmDriveReceipt(receipt, events, foldedStatus, binding) {
18409
19129
  const reasons = [];
@@ -19558,7 +20278,7 @@ function newKxmObservationId() {
19558
20278
  function newKxmEvidenceId() {
19559
20279
  return `gev_${randomUUID().replaceAll("-", "")}`;
19560
20280
  }
19561
- var SHA256 = /^sha256:[a-f0-9]{64}$/;
20281
+ var SHA2562 = /^sha256:[a-f0-9]{64}$/;
19562
20282
  var OPAQUE = /^[a-z][a-z0-9]{1,15}_[A-Za-z0-9][A-Za-z0-9_-]{5,127}$/;
19563
20283
  var FORBIDDEN_TEXT_KEYS = /* @__PURE__ */ new Set(["stdout", "stderr", "error", "errorText", "stdoutText", "stderrText", "errorMessage", "message"]);
19564
20284
  var GATE_ATTEMPT_KEYS = [
@@ -19637,7 +20357,7 @@ function gateRowContentHash(table, row) {
19637
20357
  const copy = { ...row };
19638
20358
  delete copy.contentHash;
19639
20359
  void table;
19640
- return `sha256:${createHash5("sha256").update(kxmCanonicalJson(copy), "utf8").digest("hex")}`;
20360
+ return `sha256:${createHash6("sha256").update(kxmCanonicalJson(copy), "utf8").digest("hex")}`;
19641
20361
  }
19642
20362
  function assertClosedGateObservation(kind, row, id = "observation") {
19643
20363
  if (row.completeness === "incomplete") return;
@@ -19689,7 +20409,7 @@ function assertId(value, prefix, id, label) {
19689
20409
  }
19690
20410
  }
19691
20411
  function assertSha(value, id, label) {
19692
- if (typeof value !== "string" || !SHA256.test(value)) throw gateRowInvalid(id, `${label} is not a sha256 hash`);
20412
+ if (typeof value !== "string" || !SHA2562.test(value)) throw gateRowInvalid(id, `${label} is not a sha256 hash`);
19693
20413
  }
19694
20414
  function assertFlag(value, id, label) {
19695
20415
  if (value !== 0 && value !== 1) throw gateRowInvalid(id, `${label} must be 0 or 1`);
@@ -19867,7 +20587,7 @@ function capabilityFromRow(row) {
19867
20587
  }
19868
20588
 
19869
20589
  // plugins/kxm/src/runtime-service.ts
19870
- import { createHash as createHash7 } from "node:crypto";
20590
+ import { createHash as createHash8 } from "node:crypto";
19871
20591
  import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
19872
20592
  import { join as join7 } from "node:path";
19873
20593
 
@@ -21080,7 +21800,7 @@ function isTerminalRunStatus(status) {
21080
21800
  }
21081
21801
 
21082
21802
  // plugins/kxm/src/engine-plan.ts
21083
- import { createHash as createHash6 } from "node:crypto";
21803
+ import { createHash as createHash7 } from "node:crypto";
21084
21804
 
21085
21805
  // plugins/kxm/src/engine-compile.ts
21086
21806
  var KXM_COMPILED_WORKFLOW_SCHEMA = "kxm.compiled-workflow.v1";
@@ -21096,8 +21816,8 @@ var BANNED_RUNTIME_HINT = "not yet supported";
21096
21816
  var KxmEngineCompileError = class extends Error {
21097
21817
  issues;
21098
21818
  constructor(issues) {
21099
- const sorted = sortIssues3(issues);
21100
- super(sorted.map((issue3) => `${issue3.workflowId}: ${issue3.code}: ${issue3.message}`).join("\n"));
21819
+ const sorted = sortIssues4(issues);
21820
+ super(sorted.map((issue4) => `${issue4.workflowId}: ${issue4.code}: ${issue4.message}`).join("\n"));
21101
21821
  this.name = "KxmEngineCompileError";
21102
21822
  this.issues = sorted;
21103
21823
  }
@@ -21233,7 +21953,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
21233
21953
  const join19 = compileJoin(step, id, sink);
21234
21954
  const requiredEvidence = compileEvidence(step, id, sink);
21235
21955
  const transitions = compileTransitions(step, id, index, stepIndex, sink);
21236
- const outcomes = Object.keys(transitions).sort(compareCodeUnits4);
21956
+ const outcomes = Object.keys(transitions).sort(compareCodeUnits5);
21237
21957
  const orderedTransitions = /* @__PURE__ */ Object.create(null);
21238
21958
  for (const outcome of outcomes) {
21239
21959
  const transition2 = transitions[outcome];
@@ -21563,26 +22283,26 @@ function deepFreeze(value) {
21563
22283
  }
21564
22284
  return Object.freeze(value);
21565
22285
  }
21566
- function compareCodeUnits4(left, right) {
22286
+ function compareCodeUnits5(left, right) {
21567
22287
  return left < right ? -1 : left > right ? 1 : 0;
21568
22288
  }
21569
22289
  function compareOptional(left, right) {
21570
22290
  if (left === right) return 0;
21571
22291
  if (left === void 0) return -1;
21572
22292
  if (right === void 0) return 1;
21573
- return compareCodeUnits4(left, right);
22293
+ return compareCodeUnits5(left, right);
21574
22294
  }
21575
- function sortIssues3(issues) {
22295
+ function sortIssues4(issues) {
21576
22296
  return [...issues].sort((left, right) => {
21577
22297
  const byStep = compareOptional(left.stepId, right.stepId);
21578
22298
  if (byStep !== 0) return byStep;
21579
22299
  const byOutcome = compareOptional(left.outcome, right.outcome);
21580
22300
  if (byOutcome !== 0) return byOutcome;
21581
- return compareCodeUnits4(left.code, right.code);
22301
+ return compareCodeUnits5(left.code, right.code);
21582
22302
  });
21583
22303
  }
21584
- function pushIssue(sink, issue3) {
21585
- sink.issues.push({ workflowId: sink.workflowId, ...issue3 });
22304
+ function pushIssue(sink, issue4) {
22305
+ sink.issues.push({ workflowId: sink.workflowId, ...issue4 });
21586
22306
  }
21587
22307
 
21588
22308
  // plugins/kxm/src/engine-plan.ts
@@ -21627,7 +22347,7 @@ var JOIN_OPTIONAL = ["minimumPassed", "cancelRemaining"];
21627
22347
  var EVIDENCE_REQUIRED = ["key", "kind", "minimum", "reusableAcrossAttempts"];
21628
22348
  var EVIDENCE_OPTIONAL = ["producerPolicy"];
21629
22349
  function kxmSha256(input) {
21630
- return `sha256:${createHash6("sha256").update(input, "utf8").digest("hex")}`;
22350
+ return `sha256:${createHash7("sha256").update(input, "utf8").digest("hex")}`;
21631
22351
  }
21632
22352
  function kxmStepAskSha256(plan, stepId, agentId) {
21633
22353
  const step = Object.hasOwn(plan.steps, stepId) ? plan.steps[stepId] : void 0;
@@ -21798,9 +22518,9 @@ function parseGates(raw, plan, runId) {
21798
22518
  }
21799
22519
  return { registry, definitions, controlRoot };
21800
22520
  }
21801
- function parseRegistryPin(raw, runId, required) {
22521
+ function parseRegistryPin(raw, runId, required2) {
21802
22522
  if (raw === null) {
21803
- if (required) throw runtimeError("run_plan_corrupt", runId, "gate steps require a non-null registry pin");
22523
+ if (required2) throw runtimeError("run_plan_corrupt", runId, "gate steps require a non-null registry pin");
21804
22524
  return null;
21805
22525
  }
21806
22526
  const value = asObject(raw, runId, "gates.registry");
@@ -21983,7 +22703,7 @@ function parseStep(raw, stepId, runId) {
21983
22703
  repositories,
21984
22704
  ...value.tools !== void 0 ? { tools: asJsonObject(value.tools, runId, `${stepId}.tools`) } : {},
21985
22705
  secrets: asObjectArray(value.secrets, runId, `${stepId}.secrets`).map((entry) => ({ ...entry })),
21986
- ...value.model !== void 0 ? { model: cloneJson(value.model) } : {},
22706
+ ...value.model !== void 0 ? { model: cloneJson2(value.model) } : {},
21987
22707
  requiredEvidence: asUnknownArray(value.requiredEvidence, runId, `${stepId}.requiredEvidence`).map((entry, index) => parseEvidence(entry, stepId, index, runId)),
21988
22708
  outcomes,
21989
22709
  transitions,
@@ -22121,9 +22841,9 @@ function asObject(value, runId, label) {
22121
22841
  }
22122
22842
  return value;
22123
22843
  }
22124
- function assertExactKeys(value, required, optional, runId, label) {
22125
- const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
22126
- for (const key of required) {
22844
+ function assertExactKeys(value, required2, optional, runId, label) {
22845
+ const allowed = /* @__PURE__ */ new Set([...required2, ...optional]);
22846
+ for (const key of required2) {
22127
22847
  if (!(key in value)) throw runtimeError("run_plan_corrupt", runId, `${label} is missing ${key}`);
22128
22848
  }
22129
22849
  for (const key of Object.keys(value)) {
@@ -22192,9 +22912,9 @@ function asJsonObject(value, runId, label) {
22192
22912
  if (!value || typeof value !== "object" || Array.isArray(value)) {
22193
22913
  throw runtimeError("run_plan_corrupt", runId, `${label} is not an object`);
22194
22914
  }
22195
- return cloneJson(value);
22915
+ return cloneJson2(value);
22196
22916
  }
22197
- function cloneJson(value) {
22917
+ function cloneJson2(value) {
22198
22918
  return JSON.parse(JSON.stringify(value));
22199
22919
  }
22200
22920
  function sameSet(left, right) {
@@ -22303,8 +23023,8 @@ function verifyKxmGateEvidence(store, run, envelope2, events, state) {
22303
23023
  throw runtimeError("gate_evidence_hash_mismatch", run.runId, "evidence outcome does not match the observation and expect");
22304
23024
  }
22305
23025
  const step = envelope2.plan.steps[row.stepId];
22306
- const required = step?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
22307
- const expectedKey = required.length === 1 ? required[0].key : null;
23026
+ const required2 = step?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
23027
+ const expectedKey = required2.length === 1 ? required2[0].key : null;
22308
23028
  if (row.evidenceKey !== expectedKey) {
22309
23029
  throw runtimeError("gate_evidence_unexpected", run.runId, "evidence_key does not match the declared requiredEvidence key");
22310
23030
  }
@@ -22699,11 +23419,11 @@ function pump(storePath, owner) {
22699
23419
  }
22700
23420
 
22701
23421
  // plugins/kxm/src/runtime-service.ts
22702
- function compareCodeUnits5(left, right) {
23422
+ function compareCodeUnits6(left, right) {
22703
23423
  return left < right ? -1 : left > right ? 1 : 0;
22704
23424
  }
22705
23425
  function sha256Of(input) {
22706
- return `sha256:${createHash7("sha256").update(input, "utf8").digest("hex")}`;
23426
+ return `sha256:${createHash8("sha256").update(input, "utf8").digest("hex")}`;
22707
23427
  }
22708
23428
  function objectValue3(value) {
22709
23429
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
@@ -22751,9 +23471,9 @@ function collectMemoryFiles(dir, baseDir, ignoreSubdirs = /* @__PURE__ */ new Se
22751
23471
  return results;
22752
23472
  }
22753
23473
  function computeKxmMemoryRevision(bundle, options = {}) {
22754
- const hash = createHash7("sha256");
23474
+ const hash = createHash8("sha256");
22755
23475
  const memoryDir = options.memoryDir ?? join7(bundle.projectRoot, ".kxm", "memory");
22756
- const memoryFiles = collectMemoryFiles(memoryDir, memoryDir, /* @__PURE__ */ new Set(["candidates"])).sort((left, right) => compareCodeUnits5(left.relPath, right.relPath));
23476
+ const memoryFiles = collectMemoryFiles(memoryDir, memoryDir, /* @__PURE__ */ new Set(["candidates"])).sort((left, right) => compareCodeUnits6(left.relPath, right.relPath));
22757
23477
  for (const file of memoryFiles) {
22758
23478
  hash.update(`memory:${file.relPath}\0`, "utf8");
22759
23479
  hash.update(readFileSync4(file.fullPath));
@@ -22761,14 +23481,14 @@ function computeKxmMemoryRevision(bundle, options = {}) {
22761
23481
  }
22762
23482
  const skillsDir = options.skillsDir ?? join7(bundle.projectRoot, ".kxm", "skills");
22763
23483
  const promotedSkillsDir = join7(skillsDir, "promoted");
22764
- const skillFiles = collectMemoryFiles(promotedSkillsDir, promotedSkillsDir).sort((left, right) => compareCodeUnits5(left.relPath, right.relPath));
23484
+ const skillFiles = collectMemoryFiles(promotedSkillsDir, promotedSkillsDir).sort((left, right) => compareCodeUnits6(left.relPath, right.relPath));
22765
23485
  for (const file of skillFiles) {
22766
23486
  hash.update(`skill:${file.relPath}\0`, "utf8");
22767
23487
  hash.update(readFileSync4(file.fullPath));
22768
23488
  hash.update("\0", "utf8");
22769
23489
  }
22770
23490
  if (options.promotedState && options.promotedState.length > 0) {
22771
- const currentItems = options.promotedState.filter((item) => !item.status || item.status === "current").slice().sort((left, right) => compareCodeUnits5(left.id, right.id));
23491
+ const currentItems = options.promotedState.filter((item) => !item.status || item.status === "current").slice().sort((left, right) => compareCodeUnits6(left.id, right.id));
22772
23492
  for (const item of currentItems) {
22773
23493
  hash.update(`state:${item.id}\0`, "utf8");
22774
23494
  hash.update(kxmCanonicalJson(item), "utf8");
@@ -22908,7 +23628,7 @@ function acceptKxmRun(context, bundle, request, options = {}) {
22908
23628
  }
22909
23629
  const revisions = kxmPolicyRevisions(bundle, options);
22910
23630
  const memoryRevision = options.memoryRevision ?? revisions.memoryRevision;
22911
- const promptSha256 = `sha256:${createHash7("sha256").update(request.prompt, "utf8").digest("hex")}`;
23631
+ const promptSha256 = `sha256:${createHash8("sha256").update(request.prompt, "utf8").digest("hex")}`;
22912
23632
  return context.eventStore.transaction(() => {
22913
23633
  const prior = context.eventStore.command(commandId);
22914
23634
  if (prior) {
@@ -23150,7 +23870,7 @@ function cancelKxmRun(context, runId, options = {}) {
23150
23870
  }
23151
23871
 
23152
23872
  // plugins/kxm/src/oneshot-evidence.ts
23153
- import { createHash as createHash8, randomUUID as randomUUID3 } from "node:crypto";
23873
+ import { createHash as createHash9, randomUUID as randomUUID3 } from "node:crypto";
23154
23874
  import { lstatSync as lstatSync4, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "node:fs";
23155
23875
  import { lstat, mkdir, open, rename } from "node:fs/promises";
23156
23876
  import { join as join8 } from "node:path";
@@ -23159,7 +23879,7 @@ var RETIRED_ONESHOT_EVIDENCE_SCHEMAS = /* @__PURE__ */ new Set(["kxm.oneshot-evi
23159
23879
  var TEXT_LIMIT = 4 * 1024 * 1024;
23160
23880
  var ARGV_LIMIT = 64 * 1024;
23161
23881
  var RECORD_LIMIT = 16 * 1024 * 1024;
23162
- var digest = (value) => `sha256:${createHash8("sha256").update(value).digest("hex")}`;
23882
+ var digest = (value) => `sha256:${createHash9("sha256").update(value).digest("hex")}`;
23163
23883
  function parseOneShotEvidenceRecord(bytes) {
23164
23884
  let parsed;
23165
23885
  try {
@@ -23301,7 +24021,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
23301
24021
 
23302
24022
  // plugins/kxm/src/prices.ts
23303
24023
  var import_yaml3 = __toESM(require_dist(), 1);
23304
- import { createHash as createHash9 } from "node:crypto";
24024
+ import { createHash as createHash10 } from "node:crypto";
23305
24025
  import { existsSync as existsSync8, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync3 } from "node:fs";
23306
24026
  import { join as join9 } from "node:path";
23307
24027
 
@@ -23364,7 +24084,7 @@ function hashPriceCatalog(catalog) {
23364
24084
  }))
23365
24085
  })).sort((a, b) => a.id.localeCompare(b.id))
23366
24086
  };
23367
- return createHash9("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
24087
+ return createHash10("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
23368
24088
  }
23369
24089
  function parsePriceCatalog(text) {
23370
24090
  const parsed = (0, import_yaml3.parse)(text);
@@ -23412,9 +24132,9 @@ function parsePriceCatalog(text) {
23412
24132
  throw new Error(`tier at index ${tIndex} for model ${m.id} must be an object`);
23413
24133
  }
23414
24134
  const t = tItem;
23415
- const rate = (field, short, required = false) => {
24135
+ const rate = (field, short, required2 = false) => {
23416
24136
  const value = t[field] !== void 0 ? t[field] : t[short];
23417
- if (value == null && !required) return null;
24137
+ if (value == null && !required2) return null;
23418
24138
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
23419
24139
  throw new Error(`tier ${tIndex} for model ${m.id} requires finite non-negative ${field}`);
23420
24140
  }
@@ -23464,22 +24184,30 @@ function loadPriceCatalog(rootOrPath) {
23464
24184
  }
23465
24185
 
23466
24186
  // plugins/kxm/src/engine.ts
23467
- var import_yaml7 = __toESM(require_dist(), 1);
23468
- import { createHash as createHash13, randomBytes, timingSafeEqual } from "node:crypto";
24187
+ var import_yaml9 = __toESM(require_dist(), 1);
24188
+ import { createHash as createHash14, randomBytes, timingSafeEqual } from "node:crypto";
23469
24189
  import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
23470
24190
  import { join as join15 } from "node:path";
23471
24191
 
23472
24192
  // plugins/kxm/src/routes.ts
23473
- var import_yaml4 = __toESM(require_dist(), 1);
24193
+ var import_yaml6 = __toESM(require_dist(), 1);
23474
24194
  import { existsSync as existsSync9, readFileSync as readFileSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readdirSync as readdirSync5 } from "node:fs";
23475
24195
  import { join as join10 } from "node:path";
24196
+
24197
+ // plugins/kxm/src/role.ts
24198
+ var import_yaml5 = __toESM(require_dist(), 1);
24199
+
24200
+ // plugins/kxm/src/config.ts
24201
+ var import_yaml4 = __toESM(require_dist(), 1);
24202
+
24203
+ // plugins/kxm/src/routes.ts
23476
24204
  var RETIRED_POLICY = ".kxm/producers.yaml";
23477
24205
  var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
23478
24206
  function loadRoutePolicy(root) {
23479
24207
  if (existsSync9(join10(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
23480
24208
  const path = join10(root, ".kxm", "routes.yaml");
23481
24209
  if (!existsSync9(path)) return empty();
23482
- const value = (0, import_yaml4.parse)(readFileSync7(path, "utf8"));
24210
+ const value = (0, import_yaml6.parse)(readFileSync7(path, "utf8"));
23483
24211
  if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
23484
24212
  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")])) : {} };
23485
24213
  }
@@ -23489,8 +24217,8 @@ function listRoleBindings(root) {
23489
24217
  if (!existsSync9(dir)) return result;
23490
24218
  for (const file of readdirSync5(dir).filter((name) => name.endsWith(".yaml"))) {
23491
24219
  const role = file.slice(0, -5);
23492
- const value = (0, import_yaml4.parse)(readFileSync7(join10(dir, file), "utf8"));
23493
- result[role] = (value.roster ?? []).map((entry) => entry.model).filter((model) => typeof model === "string");
24220
+ const value = (0, import_yaml6.parse)(readFileSync7(join10(dir, file), "utf8"));
24221
+ result[role] = (value.roster ?? []).map((entry) => entry.route).filter((route) => typeof route === "string");
23494
24222
  }
23495
24223
  return result;
23496
24224
  }
@@ -23909,8 +24637,8 @@ function oneOf(value, field, allowed) {
23909
24637
  }
23910
24638
  return value;
23911
24639
  }
23912
- function idRefs(value, field, required = false) {
23913
- if (value === void 0 || value === null) return required ? [] : void 0;
24640
+ function idRefs(value, field, required2 = false) {
24641
+ if (value === void 0 || value === null) return required2 ? [] : void 0;
23914
24642
  if (!Array.isArray(value)) {
23915
24643
  throw new ProtocolError(400, `${field} must be an array of identifiers`, "invalid_context_field");
23916
24644
  }
@@ -24445,7 +25173,7 @@ function memoryRecordToContextItem(record2, project) {
24445
25173
  }
24446
25174
 
24447
25175
  // plugins/kxm/src/memory.ts
24448
- var import_yaml5 = __toESM(require_dist(), 1);
25176
+ var import_yaml7 = __toESM(require_dist(), 1);
24449
25177
  import { existsSync as existsSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
24450
25178
  import { extname as extname2, join as join11, resolve as resolve7 } from "node:path";
24451
25179
  var MEMORY_SCHEMA = "kxm.memory.v1";
@@ -24477,7 +25205,7 @@ function parseFrontmatter(content) {
24477
25205
  }
24478
25206
  function parseMemoryRecord(raw, filename = "memory.md") {
24479
25207
  const { frontmatter, body } = parseFrontmatter(raw);
24480
- const data = (0, import_yaml5.parse)(frontmatter);
25208
+ const data = (0, import_yaml7.parse)(frontmatter);
24481
25209
  if (!data || typeof data !== "object" || Array.isArray(data)) {
24482
25210
  throw new Error(`invalid YAML frontmatter in ${filename}`);
24483
25211
  }
@@ -24573,8 +25301,8 @@ function loadAuthoredMemory(repoRoot) {
24573
25301
  }
24574
25302
 
24575
25303
  // plugins/kxm/src/skills.ts
24576
- var import_yaml6 = __toESM(require_dist(), 1);
24577
- import { createHash as createHash10 } from "node:crypto";
25304
+ var import_yaml8 = __toESM(require_dist(), 1);
25305
+ import { createHash as createHash11 } from "node:crypto";
24578
25306
  import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
24579
25307
  import { dirname as dirname7, join as join12 } from "node:path";
24580
25308
  var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
@@ -24599,7 +25327,7 @@ var SkillLifecycleError = class extends Error {
24599
25327
  }
24600
25328
  };
24601
25329
  function skillContentSha256(content) {
24602
- return createHash10("sha256").update(content, "utf8").digest("hex");
25330
+ return createHash11("sha256").update(content, "utf8").digest("hex");
24603
25331
  }
24604
25332
  function skillIdFor(name, contentSha256) {
24605
25333
  const slug = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -24617,7 +25345,7 @@ function parseSkillFrontmatter(content) {
24617
25345
  return { frontmatter: null, body: content };
24618
25346
  }
24619
25347
  try {
24620
- const parsed = (0, import_yaml6.parse)(rawFm);
25348
+ const parsed = (0, import_yaml8.parse)(rawFm);
24621
25349
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
24622
25350
  return { frontmatter: parsed, body: rawBody };
24623
25351
  }
@@ -25242,7 +25970,7 @@ function evaluateArtifactsGate(context, definition) {
25242
25970
 
25243
25971
  // plugins/kxm/src/engine-command.ts
25244
25972
  import { spawn as spawn2 } from "node:child_process";
25245
- import { createHash as createHash11 } from "node:crypto";
25973
+ import { createHash as createHash12 } from "node:crypto";
25246
25974
 
25247
25975
  // plugins/kxm/src/safety-integrity.ts
25248
25976
  var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
@@ -25279,7 +26007,7 @@ function createCommandObserver(input) {
25279
26007
  return new CommandObserver(input);
25280
26008
  }
25281
26009
  var StreamDigest = class {
25282
- hash = createHash11("sha256");
26010
+ hash = createHash12("sha256");
25283
26011
  bytes = 0;
25284
26012
  complete = 0;
25285
26013
  digest() {
@@ -26019,8 +26747,8 @@ function appendEvaluatedTail(seq, envelope2, ids, stepId, outcome) {
26019
26747
  }
26020
26748
  }
26021
26749
  function evidenceKeyFor(envelope2, stepId) {
26022
- const required = envelope2.plan.steps[stepId]?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
26023
- return required.length === 1 ? required[0].key : null;
26750
+ const required2 = envelope2.plan.steps[stepId]?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
26751
+ return required2.length === 1 ? required2[0].key : null;
26024
26752
  }
26025
26753
  function placeholderReceipt(observationId, kind, now) {
26026
26754
  return {
@@ -26126,7 +26854,7 @@ function hashFromStore(context, runId) {
26126
26854
  }
26127
26855
 
26128
26856
  // plugins/kxm/src/routing.ts
26129
- import { createHash as createHash12 } from "node:crypto";
26857
+ import { createHash as createHash13 } from "node:crypto";
26130
26858
  var ROUTING_RECORD_V2_SCHEMA = "kxm.routing-record.v2";
26131
26859
  var BEHAVIORAL_HASH_VERSION = 1;
26132
26860
  var MAX_PROVIDER_METADATA_FIELDS = 32;
@@ -26149,7 +26877,7 @@ function behavioralConfigHash(input) {
26149
26877
  workflowDefinitionSha256: input.workflowDefinitionSha256?.trim() ?? null,
26150
26878
  verifierConfigSha256: input.verifierConfigSha256?.trim() ?? null
26151
26879
  };
26152
- return createHash12("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
26880
+ return createHash13("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
26153
26881
  }
26154
26882
  function boundedInt(value, field) {
26155
26883
  if (value === void 0 || value === null) return void 0;
@@ -26318,7 +27046,7 @@ function requireTrustedProducer(producer) {
26318
27046
  }
26319
27047
  }
26320
27048
  function hashCapabilitySecret(secret) {
26321
- return `sha256:${createHash13("sha256").update(`${CAPABILITY_PREFIX}${secret}`, "utf8").digest("hex")}`;
27049
+ return `sha256:${createHash14("sha256").update(`${CAPABILITY_PREFIX}${secret}`, "utf8").digest("hex")}`;
26322
27050
  }
26323
27051
  function mintKxmCapabilitySecret() {
26324
27052
  const secret = `kxmcap_${randomBytes(32).toString("base64url")}`;
@@ -26451,7 +27179,7 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
26451
27179
  return latest;
26452
27180
  }
26453
27181
  function newDriveId(runId, token, runtimeId, monotonicNs) {
26454
- return `drv_${createHash13("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
27182
+ return `drv_${createHash14("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
26455
27183
  }
26456
27184
  function driveErrorSummary(error) {
26457
27185
  const raw = error instanceof KxmConfigError ? error.issues[0]?.code : void 0;
@@ -27065,6 +27793,26 @@ function commitCommandUncertainty(context, prepared, token, reason, observation,
27065
27793
  }
27066
27794
  }
27067
27795
  var kxmPanelDispatchSeams = {};
27796
+ function selectorsNamedByRoutes(projectRoot, routeIds) {
27797
+ const selectors = /* @__PURE__ */ new Set();
27798
+ for (const routeId2 of routeIds) {
27799
+ const file = join15(projectRoot, ".kxm", "models", `${routeId2}.yaml`);
27800
+ if (!existsSync12(file)) continue;
27801
+ let parsed;
27802
+ try {
27803
+ parsed = (0, import_yaml9.parse)(readFileSync10(file, "utf8"));
27804
+ } catch {
27805
+ continue;
27806
+ }
27807
+ const model = typeof parsed.model === "string" ? parsed.model : "";
27808
+ const vendor = typeof parsed.vendor === "string" ? parsed.vendor : "";
27809
+ const harness = typeof parsed.harness === "string" ? parsed.harness : "";
27810
+ if (model) selectors.add(model);
27811
+ if (vendor && model) selectors.add(`${vendor}/${model}`);
27812
+ if (harness && model) selectors.add(`${harness}/${model}`);
27813
+ }
27814
+ return [...selectors];
27815
+ }
27068
27816
  function unreconciledPanelAttemptId(state) {
27069
27817
  const current = state.currentStep;
27070
27818
  if (!current) return void 0;
@@ -27084,7 +27832,7 @@ function resolveProducerRoute(projectRoot, step, agentId) {
27084
27832
  const agentFile = join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
27085
27833
  if (existsSync12(agentFile)) {
27086
27834
  try {
27087
- const parsed = (0, import_yaml7.parse)(readFileSync10(agentFile, "utf8"));
27835
+ const parsed = (0, import_yaml9.parse)(readFileSync10(agentFile, "utf8"));
27088
27836
  if (typeof parsed?.model === "string") {
27089
27837
  agentModel = parsed.model;
27090
27838
  } else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
@@ -27143,7 +27891,8 @@ function resolveProducerRoute(projectRoot, step, agentId) {
27143
27891
  const role = agentId === "implementer" ? "writer" : agentId;
27144
27892
  const roleBindings = listRoleBindings(projectRoot);
27145
27893
  const roster = roleBindings[role];
27146
- if (roster && !roster.includes(selector)) {
27894
+ const rosterSelectors = roster ? selectorsNamedByRoutes(projectRoot, roster) : void 0;
27895
+ if (rosterSelectors && !rosterSelectors.includes(selector)) {
27147
27896
  return {
27148
27897
  error: {
27149
27898
  reason: "step_unsupported",
@@ -27464,7 +28213,7 @@ function birthMember(context, input) {
27464
28213
  if (promptText === void 0) {
27465
28214
  throw runtimeError("run_prompt_mismatch", run.runId, "prompt text missing from accepted run prompt store");
27466
28215
  }
27467
- const promptHash = createHash13("sha256").update(promptText, "utf8").digest("hex");
28216
+ const promptHash = createHash14("sha256").update(promptText, "utf8").digest("hex");
27468
28217
  const expectedHash = run.promptSha256.replace(/^sha256:/, "");
27469
28218
  if (promptHash !== expectedHash) {
27470
28219
  throw runtimeError("run_prompt_mismatch", run.runId, "prompt text does not match accepted promptSha256");
@@ -28367,7 +29116,7 @@ function unsupportedStep(plan, step, producerId) {
28367
29116
  function readYamlRecord(path) {
28368
29117
  if (!existsSync12(path)) return void 0;
28369
29118
  try {
28370
- const parsed = (0, import_yaml7.parse)(readFileSync10(path, "utf8"));
29119
+ const parsed = (0, import_yaml9.parse)(readFileSync10(path, "utf8"));
28371
29120
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
28372
29121
  } catch {
28373
29122
  return void 0;
@@ -29605,7 +30354,7 @@ function publishKxmSupervisorToken(paths, token) {
29605
30354
  }
29606
30355
  }
29607
30356
  function hashKxmSupervisorToken(token) {
29608
- return `sha256:${createHash14("sha256").update(`kxm-runtime-supervisor\0${token}`, "utf8").digest("hex")}`;
30357
+ return `sha256:${createHash15("sha256").update(`kxm-runtime-supervisor\0${token}`, "utf8").digest("hex")}`;
29609
30358
  }
29610
30359
  function readKxmSupervisorToken(paths) {
29611
30360
  const file = kxmSupervisorTokenFile(paths);
@@ -29968,7 +30717,7 @@ async function startKxmRuntimeSupervisor(options = {}) {
29968
30717
  try {
29969
30718
  return await startKxmRuntimeSupervisorInner(paths, options.port, now, env);
29970
30719
  } catch (error) {
29971
- if (!(error instanceof KxmConfigError && error.issues.some((issue3) => issue3.code === "runtime_supervisor_conflict"))) {
30720
+ if (!(error instanceof KxmConfigError && error.issues.some((issue4) => issue4.code === "runtime_supervisor_conflict"))) {
29972
30721
  recordSupervisorError(paths, error.message);
29973
30722
  }
29974
30723
  throw error;
@@ -29986,7 +30735,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
29986
30735
  const token = randomBytes2(32).toString("hex");
29987
30736
  const tokenHash = hashKxmSupervisorToken(token);
29988
30737
  const registry = new KxmRuntimeRegistry(paths.registryDb);
29989
- const runtimeId = `rtm_${createHash14("sha256").update(`${paths.stateRoot}\0${process.pid}\0${now()}\0${randomBytes2(16).toString("hex")}`, "utf8").digest("hex").slice(0, 24)}`;
30738
+ const runtimeId = `rtm_${createHash15("sha256").update(`${paths.stateRoot}\0${process.pid}\0${now()}\0${randomBytes2(16).toString("hex")}`, "utf8").digest("hex").slice(0, 24)}`;
29990
30739
  const requestedPort = requestedPortOption ?? 0;
29991
30740
  let activeRuntimeId = runtimeId;
29992
30741
  const contexts = /* @__PURE__ */ new Map();