@wairon/cli 5.1.1-dev.85 → 5.1.1-dev.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -65,7 +65,7 @@ var init_defaults = __esm({
65
65
  copilot: ".github/prompts",
66
66
  codex: ".codex/agents"
67
67
  };
68
- WAIRON_VERSION = "5.1.1-dev.85";
68
+ WAIRON_VERSION = "5.1.1-dev.86";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -569,6 +569,13 @@ var init_agent = __esm({
569
569
  dependencies: import_zod2.z.array(import_zod2.z.string()).default([]),
570
570
  /** Rendered implementation guidance for this agent's variant-tagged components (deep variant integration); empty when none. */
571
571
  variantGuidance: import_zod2.z.string().optional(),
572
+ /**
573
+ * A member project's delegating owner only: the ids of the member's own
574
+ * agents, each qualified `<alias>::<agentId>`, resolved from the member's
575
+ * topology at the member's own root. REFERENCES, never copies: nothing of the
576
+ * member's agents is rendered into this layer (one level deep, ids only).
577
+ */
578
+ delegatesTo: import_zod2.z.array(import_zod2.z.string()).optional(),
572
579
  /** Why this agent was created — the architectural reason for its existence */
573
580
  creationReason: import_zod2.z.string(),
574
581
  status: AgentStatusSchema.default("active"),
@@ -600,6 +607,13 @@ var init_agent = __esm({
600
607
  instructions: import_zod2.z.string(),
601
608
  /** Rendered variant guidance, also folded into instructions */
602
609
  variantGuidance: import_zod2.z.string().optional(),
610
+ /**
611
+ * Set only on a brief for a member's agent (an id qualified `<alias>::`): the
612
+ * member's directory relative to the root the brief was asked from. The
613
+ * brief was composed at that root, and its ownedPaths and readPaths are
614
+ * re-expressed under this prefix so the fence is correct from the asking root.
615
+ */
616
+ root: import_zod2.z.string().optional(),
603
617
  /**
604
618
  * The resource shape of this agent's work, and the allowance it earns.
605
619
  *
@@ -1210,7 +1224,13 @@ var init_project = __esm({
1210
1224
  profile: import_zod3.z.string().optional()
1211
1225
  });
1212
1226
  CompositionConfigSchema = import_zod3.z.object({
1213
- requirePolicies: import_zod3.z.array(PackRequirementSchema).optional()
1227
+ requirePolicies: import_zod3.z.array(PackRequirementSchema).optional(),
1228
+ /**
1229
+ * Opt-in, off by default (stage 5). When true, this project's lock refuses
1230
+ * while any DIRECT member is drifted or never approved, naming each, and
1231
+ * writes nothing. Off: the lock proceeds and records each member's state.
1232
+ */
1233
+ requireApprovedMembers: import_zod3.z.boolean().optional()
1214
1234
  });
1215
1235
  ProjectConfigSchema = import_zod3.z.object({
1216
1236
  /**
@@ -1473,21 +1493,21 @@ function childrenOf(tokens, index) {
1473
1493
  }
1474
1494
  return out;
1475
1495
  }
1476
- function locateScalar(tokens, path83) {
1496
+ function locateScalar(tokens, path82) {
1477
1497
  let region = tokens;
1478
- for (let k = 0; k < path83.length; k += 1) {
1479
- const segment = path83[k];
1480
- if (region.length === 0) return `nothing is nested at ${path83.slice(0, k).join(".")}`;
1498
+ for (let k = 0; k < path82.length; k += 1) {
1499
+ const segment = path82[k];
1500
+ if (region.length === 0) return `nothing is nested at ${path82.slice(0, k).join(".")}`;
1481
1501
  const base = Math.min(...region.map((t) => t.col));
1482
- const last = k === path83.length - 1;
1502
+ const last = k === path82.length - 1;
1483
1503
  let at;
1484
1504
  if (typeof segment === "string") {
1485
1505
  at = region.findIndex((t) => t.kind === "key" && t.col === base && t.name === segment);
1486
- if (at < 0) return `no block key "${segment}" at ${path83.slice(0, k).join(".") || "the document root"}`;
1506
+ if (at < 0) return `no block key "${segment}" at ${path82.slice(0, k).join(".") || "the document root"}`;
1487
1507
  if (last) return region[at].valueStart !== void 0 ? region[at] : `"${segment}" holds no inline scalar`;
1488
1508
  } else {
1489
1509
  const items = region.map((t, i) => ({ t, i })).filter(({ t }) => t.kind === "item" && t.col === base);
1490
- if (segment >= items.length) return `no block sequence item ${segment} at ${path83.slice(0, k).join(".")}`;
1510
+ if (segment >= items.length) return `no block sequence item ${segment} at ${path82.slice(0, k).join(".")}`;
1491
1511
  at = items[segment].i;
1492
1512
  if (last) {
1493
1513
  const value = region[at + 1];
@@ -1535,10 +1555,10 @@ function writeScalar(value, style) {
1535
1555
  if (style === "plain" && /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value)) return value;
1536
1556
  return `'${value.replace(/'/g, "''")}'`;
1537
1557
  }
1538
- function setAt(document, path83, value) {
1558
+ function setAt(document, path82, value) {
1539
1559
  let node = document;
1540
- for (const segment of path83.slice(0, -1)) node = node[segment];
1541
- node[path83[path83.length - 1]] = value;
1560
+ for (const segment of path82.slice(0, -1)) node = node[segment];
1561
+ node[path82[path82.length - 1]] = value;
1542
1562
  }
1543
1563
  function rewriteScalarsInText(text3, edits, root) {
1544
1564
  const refuse = (why) => {
@@ -1812,9 +1832,9 @@ function registryOver(store, root) {
1812
1832
  const next = rekeyAnchor(rename, { spec: entry.spec, at, covers });
1813
1833
  const base = ["rules", "conformance", "carried", g, "findings", f];
1814
1834
  const named2 = { code: String(entry.code), spec: entry.spec, at: at ?? "" };
1815
- const change = (path83, field, from, to) => {
1835
+ const change = (path82, field, from, to) => {
1816
1836
  if (from === to) return;
1817
- edits.push({ path: [...base, ...path83], from, to });
1837
+ edits.push({ path: [...base, ...path82], from, to });
1818
1838
  rekeys.push({ ...named2, field, from, to });
1819
1839
  };
1820
1840
  change(["spec"], "spec", entry.spec, next.spec);
@@ -2365,18 +2385,26 @@ var init_canonical_json = __esm({
2365
2385
  function inIdentityOrder(specs) {
2366
2386
  return specs.map((spec) => ({ spec, content: canonicalize(spec) })).sort((a, b) => compareOrdinal(a.spec.id, b.spec.id) || compareOrdinal(a.content, b.content)).map((entry) => entry.spec);
2367
2387
  }
2368
- function computeStateId() {
2388
+ function digestTree(keep) {
2389
+ const kind = (specs) => inIdentityOrder(specs.filter((s) => keep(s.id)));
2369
2390
  const tree = {
2370
2391
  system: loadSystemSpec(),
2371
- subsystems: inIdentityOrder(loadSubsystemSpecs()),
2372
- components: inIdentityOrder(loadComponentSpecs()),
2373
- interfaces: inIdentityOrder(loadInterfaceSpecs()),
2374
- implementations: inIdentityOrder(loadImplementationSpecs()),
2375
- types: inIdentityOrder(loadTypeSpecs())
2392
+ subsystems: kind(loadSubsystemSpecs()),
2393
+ components: kind(loadComponentSpecs()),
2394
+ interfaces: kind(loadInterfaceSpecs()),
2395
+ implementations: kind(loadImplementationSpecs()),
2396
+ types: kind(loadTypeSpecs())
2376
2397
  };
2377
2398
  const digest2 = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
2378
2399
  return { algorithm: CONTENT_ALGORITHM, digest: digest2 };
2379
2400
  }
2401
+ function computeStateId() {
2402
+ return digestTree(() => true);
2403
+ }
2404
+ function computeOwnStateId() {
2405
+ const owners = graph().owners;
2406
+ return digestTree((id) => (owners.get(id) ?? "") === "");
2407
+ }
2380
2408
  function stateIdEquals(a, b) {
2381
2409
  return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
2382
2410
  }
@@ -2435,12 +2463,13 @@ function withSortedMaps(record2) {
2435
2463
  for (const k of Object.keys(m).sort()) out[k] = m[k];
2436
2464
  return out;
2437
2465
  };
2438
- const specs = sorted(record2.specs);
2439
- const children = sorted(record2.children);
2466
+ const { children: _legacy, ...rest } = record2;
2467
+ const specs = sorted(rest.specs);
2468
+ const members = sorted(rest.members);
2440
2469
  return {
2441
- ...record2,
2470
+ ...rest,
2442
2471
  ...specs ? { specs } : {},
2443
- ...children ? { children } : {}
2472
+ ...members ? { members } : {}
2444
2473
  };
2445
2474
  }
2446
2475
  var fs6, path8;
@@ -4313,12 +4342,12 @@ var init_exports = __esm({
4313
4342
  });
4314
4343
 
4315
4344
  // src/models/project-family.ts
4316
- function walkMemberPath(byKey, start2, segments) {
4345
+ function walkMemberPath(byKey2, start2, segments) {
4317
4346
  let node = start2;
4318
4347
  for (const segment of segments.slice(0, -1)) {
4319
4348
  const next = node.aliases.get(segment);
4320
4349
  if (next !== void 0) {
4321
- const found = byKey.get(next);
4350
+ const found = byKey2.get(next);
4322
4351
  if (!found) return null;
4323
4352
  node = found;
4324
4353
  continue;
@@ -4330,13 +4359,13 @@ function walkMemberPath(byKey, start2, segments) {
4330
4359
  return { project: node, name: segments[segments.length - 1] };
4331
4360
  }
4332
4361
  function landReference(projects, reference, from) {
4333
- const byKey = new Map(projects.map((p) => [p.namespace, p]));
4334
- const origin = byKey.get(from);
4335
- const root = byKey.get("");
4362
+ const byKey2 = new Map(projects.map((p) => [p.namespace, p]));
4363
+ const origin = byKey2.get(from);
4364
+ const root = byKey2.get("");
4336
4365
  if (!reference.includes("::")) return { kind: "project", project: from, name: reference };
4337
4366
  const land = (form, start2, segments2) => {
4338
4367
  if (!start2) return { kind: "unresolved", form };
4339
- const walked = walkMemberPath(byKey, start2, segments2);
4368
+ const walked = walkMemberPath(byKey2, start2, segments2);
4340
4369
  if (walked === "outside") return { kind: "outside", form };
4341
4370
  if (walked) return { kind: "project", form, project: walked.project.namespace, name: walked.name };
4342
4371
  return { kind: "unresolved", form };
@@ -4348,7 +4377,7 @@ function landReference(projects, reference, from) {
4348
4377
  while (segments[0] === "super") {
4349
4378
  segments.shift();
4350
4379
  if (!node || node.parent === void 0) return { kind: "outside", form: "super" };
4351
- node = byKey.get(node.parent);
4380
+ node = byKey2.get(node.parent);
4352
4381
  }
4353
4382
  if (segments.length === 0 || !node) return { kind: "unresolved", form: "super" };
4354
4383
  return land("super", node, segments);
@@ -4360,11 +4389,11 @@ function landReference(projects, reference, from) {
4360
4389
  return land(segments.length === 2 ? "alias" : "path", origin, segments);
4361
4390
  }
4362
4391
  if (origin.subsystems.has(first)) return { kind: "project", project: from, name: segments[segments.length - 1] };
4363
- const fromRoot = root ? walkMemberPath(byKey, root, segments) : null;
4392
+ const fromRoot = root ? walkMemberPath(byKey2, root, segments) : null;
4364
4393
  if (fromRoot && fromRoot !== "outside") return { kind: "project", form: "path", project: fromRoot.project.namespace, name: fromRoot.name };
4365
4394
  const byId = projects.filter((p) => p.id === first || p.namespace !== "" && p.namespace === first);
4366
4395
  if (byId.length === 1) {
4367
- const walked = walkMemberPath(byKey, byId[0], segments.slice(1));
4396
+ const walked = walkMemberPath(byKey2, byId[0], segments.slice(1));
4368
4397
  if (walked === "outside") return { kind: "outside", form: "path" };
4369
4398
  if (walked) return { kind: "project", form: "path", project: walked.project.namespace, name: walked.name };
4370
4399
  }
@@ -4474,9 +4503,9 @@ function shortestPath(from, to, members, edges, rank) {
4474
4503
  const v = queue.shift();
4475
4504
  for (const w of [...edges.get(v) ?? []].filter((x) => members.has(x)).sort((a, b) => rank(a) - rank(b))) {
4476
4505
  if (w === to) {
4477
- const path83 = [w];
4478
- for (let at = v; at !== from; at = prev.get(at)) path83.unshift(at);
4479
- return path83;
4506
+ const path82 = [w];
4507
+ for (let at = v; at !== from; at = prev.get(at)) path82.unshift(at);
4508
+ return path82;
4480
4509
  }
4481
4510
  if (seen.has(w)) continue;
4482
4511
  seen.add(w);
@@ -10863,38 +10892,102 @@ function cycleFindings(family, config) {
10863
10892
  }
10864
10893
  return out;
10865
10894
  }
10866
- function approvalFinding(node, config) {
10867
- return runWithProjectRoot(node.directory, () => {
10868
- const current2 = computeGateStateId();
10869
- const record2 = readLockRecord();
10870
- if (!record2) {
10871
- return finding(
10872
- config,
10873
- "MEMBER_UNAPPROVED",
10874
- `The member ${named(node.namespace)} has never been locked: nobody has approved its design. Lock it at its own root (\`wairon lock\`).`,
10875
- node.namespace
10876
- );
10877
- }
10878
- if (record2.stateId.algorithm !== current2.algorithm || record2.stateId.digest !== current2.digest) {
10879
- return finding(
10880
- config,
10881
- "MEMBER_DRIFTED",
10882
- `The member ${named(node.namespace)} changed since it was approved on ${record2.lockedAt}: its lock no longer matches its own gate identity. Re-lock it at its own root (\`wairon lock\`).`,
10883
- node.namespace
10884
- );
10885
- }
10886
- return null;
10887
- });
10895
+ function approvalFinding(entry, config) {
10896
+ if (entry.state === "never") {
10897
+ return finding(
10898
+ config,
10899
+ "MEMBER_UNAPPROVED",
10900
+ `The member ${named(entry.key)} has never been locked: nobody has approved its design. Lock it at its own root (\`wairon lock\`).`,
10901
+ entry.key
10902
+ );
10903
+ }
10904
+ if (entry.state === "drifted" && entry.upgraded) {
10905
+ return finding(
10906
+ config,
10907
+ "MEMBER_DRIFTED",
10908
+ `The member ${named(entry.key)} was approved under an earlier gate identity \u2014 the gate identity gained inputs in stage 5 (members' composition subjects, \`composition\`; code conformance moved beside the claim), which says nothing about its design. Re-lock it once at its own root (\`wairon lock\`).`,
10909
+ entry.key
10910
+ );
10911
+ }
10912
+ if (entry.state === "drifted") {
10913
+ return finding(
10914
+ config,
10915
+ "MEMBER_DRIFTED",
10916
+ `The member ${named(entry.key)} changed since it was approved: its lock no longer matches its own gate identity. Re-lock it at its own root (\`wairon lock\`).`,
10917
+ entry.key
10918
+ );
10919
+ }
10920
+ return null;
10888
10921
  }
10889
10922
  function checkMembers(family) {
10890
10923
  const config = configOrNull();
10891
10924
  const out = [...problemFindings(family, config), ...cycleFindings(family, config)];
10892
- for (const node of family.nodes) {
10893
- if (node.namespace === "") continue;
10894
- out.push(approvalFinding(node, config));
10925
+ const present = new Set(family.nodes.filter((n) => n.namespace !== "").map((n) => n.namespace));
10926
+ for (const entry of projectApprovals()) {
10927
+ if (present.has(entry.key)) out.push(approvalFinding(entry, config));
10895
10928
  }
10896
10929
  return out.filter((f) => f !== null);
10897
10930
  }
10931
+ function subjectOf(record2) {
10932
+ return `${record2.stateId.algorithm}:${record2.stateId.digest}`;
10933
+ }
10934
+ function ownApproval(node, root) {
10935
+ return within(node.directory, { topRoot: root, narrowed: true }, () => {
10936
+ const current2 = computeGateStateId();
10937
+ const record2 = readLockRecord();
10938
+ const base = {
10939
+ key: node.namespace,
10940
+ ...node.mountAlias !== void 0 ? { alias: node.mountAlias } : {},
10941
+ ...node.parent !== void 0 ? { parent: node.parent } : {},
10942
+ ...node.id !== void 0 ? { projectId: node.id } : {},
10943
+ state: "never"
10944
+ };
10945
+ if (!record2) return base;
10946
+ const matches = record2.stateId.algorithm === current2.algorithm && record2.stateId.digest === current2.digest;
10947
+ return {
10948
+ ...base,
10949
+ state: matches ? "approved" : "drifted",
10950
+ subject: subjectOf(record2),
10951
+ ...!matches && record2.stateId.algorithm !== current2.algorithm ? { upgraded: true } : {}
10952
+ };
10953
+ });
10954
+ }
10955
+ function pinOf(parent, alias, subject) {
10956
+ if (!parent) return "unpinned";
10957
+ const recorded = parent.members ? alias in parent.members ? parent.members[alias].subject ?? "never" : void 0 : parent.children?.[alias];
10958
+ if (recorded === void 0) return "unpinned";
10959
+ return recorded === (subject ?? "never") ? "matches" : "moved";
10960
+ }
10961
+ function projectApprovals(depth) {
10962
+ const family = graph();
10963
+ const root = path14.resolve(getProjectRoot());
10964
+ const limit = depth ?? Infinity;
10965
+ const selected = family.nodes.filter((n) => depthOf(family, n) <= limit);
10966
+ const directoryOf = new Map(family.nodes.map((n) => [n.namespace, n.directory]));
10967
+ const parentRecord = (key) => {
10968
+ const dir = directoryOf.get(key);
10969
+ return dir === void 0 ? null : approvalRecord(dir);
10970
+ };
10971
+ const entries = selected.map((node) => {
10972
+ const own = ownApproval(node, root);
10973
+ if (node.parent === void 0 || node.mountAlias === void 0) return own;
10974
+ return { ...own, pinned: pinOf(parentRecord(node.parent), node.mountAlias, own.subject) };
10975
+ });
10976
+ for (const problem of family.problems) {
10977
+ if (problem.kind !== "member-absent" || !problem.id) continue;
10978
+ const parentKey = problem.projects[0] ?? "";
10979
+ const parentNode = family.nodes.find((n) => n.namespace === parentKey);
10980
+ if (!parentNode || depthOf(family, parentNode) + 1 > limit) continue;
10981
+ entries.push({
10982
+ key: parentKey === "" ? problem.id : `${parentKey}::${problem.id}`,
10983
+ alias: problem.id,
10984
+ parent: parentKey,
10985
+ state: "never",
10986
+ pinned: pinOf(parentRecord(parentKey), problem.id, void 0)
10987
+ });
10988
+ }
10989
+ return entries;
10990
+ }
10898
10991
  function requiredBy(key) {
10899
10992
  return key === "" ? "the family root" : `"${key}"`;
10900
10993
  }
@@ -11783,6 +11876,9 @@ var init_extensions = __esm({
11783
11876
  });
11784
11877
 
11785
11878
  // src/core/rules/types.ts
11879
+ function judgesCode(rule) {
11880
+ return rule.judges === "code";
11881
+ }
11786
11882
  var BUILTIN_PROFILES, PROJECT_KINDS;
11787
11883
  var init_types = __esm({
11788
11884
  "src/core/rules/types.ts"() {
@@ -11823,21 +11919,21 @@ function buildCodeIndex(model) {
11823
11919
  const declarations = /* @__PURE__ */ new Map();
11824
11920
  const anchors = /* @__PURE__ */ new Map();
11825
11921
  const findingAnchors = /* @__PURE__ */ new Map();
11826
- const namesAt = (cache2, path83, make) => {
11827
- const cached = cache2.get(path83);
11922
+ const namesAt = (cache2, path82, make) => {
11923
+ const cached = cache2.get(path82);
11828
11924
  if (cached) return cached;
11829
- const f = facts.get(path83);
11925
+ const f = facts.get(path82);
11830
11926
  if (!f) return NO_NAMES;
11831
11927
  const built = make(f);
11832
- cache2.set(path83, built);
11928
+ cache2.set(path82, built);
11833
11929
  return built;
11834
11930
  };
11835
11931
  const republished = /* @__PURE__ */ new Map();
11836
- const republishedFrom = (path83) => {
11837
- const cached = republished.get(path83);
11932
+ const republishedFrom = (path82) => {
11933
+ const cached = republished.get(path82);
11838
11934
  if (cached) return cached;
11839
- const out = /* @__PURE__ */ new Set([path83]);
11840
- const queue = [path83];
11935
+ const out = /* @__PURE__ */ new Set([path82]);
11936
+ const queue = [path82];
11841
11937
  while (queue.length) {
11842
11938
  const from = queue.pop();
11843
11939
  const f = facts.get(from);
@@ -11850,7 +11946,7 @@ function buildCodeIndex(model) {
11850
11946
  }
11851
11947
  }
11852
11948
  }
11853
- republished.set(path83, out);
11949
+ republished.set(path82, out);
11854
11950
  return out;
11855
11951
  };
11856
11952
  const NO_ORIGIN = /* @__PURE__ */ new Set();
@@ -11859,9 +11955,9 @@ function buildCodeIndex(model) {
11859
11955
  return to ? republishedFrom(to) : NO_ORIGIN;
11860
11956
  };
11861
11957
  const scopeOf2 = (site, from) => {
11862
- const path83 = pathKey(site.from ?? from);
11863
- const f = facts.get(path83);
11864
- return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path83, facts: f } : void 0;
11958
+ const path82 = pathKey(site.from ?? from);
11959
+ const f = facts.get(path82);
11960
+ return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path82, facts: f } : void 0;
11865
11961
  };
11866
11962
  const originOf2 = (site, from) => {
11867
11963
  const resolved = scopeOf2(site, from);
@@ -11900,7 +11996,7 @@ function buildCodeIndex(model) {
11900
11996
  if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
11901
11997
  return out;
11902
11998
  };
11903
- const declarationsAt = (path83) => namesAt(declarations, pathKey(path83), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
11999
+ const declarationsAt = (path82) => namesAt(declarations, pathKey(path82), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
11904
12000
  const forwardsOf = (start2, name) => {
11905
12001
  const out = [];
11906
12002
  const seen = /* @__PURE__ */ new Set();
@@ -11926,10 +12022,10 @@ function buildCodeIndex(model) {
11926
12022
  return {
11927
12023
  paths,
11928
12024
  exactPaths,
11929
- factsAt: (path83) => facts.get(pathKey(path83)),
12025
+ factsAt: (path82) => facts.get(pathKey(path82)),
11930
12026
  declarationsAt,
11931
- anchorsAt: (path83) => namesAt(anchors, pathKey(path83), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
11932
- findingAnchorsAt: (path83) => namesAt(findingAnchors, pathKey(path83), (f) => new Set(f.anchoredNames)),
12027
+ anchorsAt: (path82) => namesAt(anchors, pathKey(path82), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
12028
+ findingAnchorsAt: (path82) => namesAt(findingAnchors, pathKey(path82), (f) => new Set(f.anchoredNames)),
11933
12029
  originOf: originOf2,
11934
12030
  possibleOriginsOf,
11935
12031
  forwardsOf
@@ -11968,10 +12064,10 @@ function buildRealizationIndex(ctx) {
11968
12064
  return {
11969
12065
  paths,
11970
12066
  implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
11971
- implementationsAt: (path83) => implsByPath.get(pathKey(path83)) ?? NONE_IMPL,
12067
+ implementationsAt: (path82) => implsByPath.get(pathKey(path82)) ?? NONE_IMPL,
11972
12068
  filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
11973
12069
  filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
11974
- componentsAt: (path83) => componentsByPath.get(pathKey(path83)) ?? NONE_COMP
12070
+ componentsAt: (path82) => componentsByPath.get(pathKey(path82)) ?? NONE_COMP
11975
12071
  };
11976
12072
  }
11977
12073
  function buildImportGraph(index, universe) {
@@ -11983,22 +12079,22 @@ function buildImportGraph(index, universe) {
11983
12079
  if (to && to !== from) into.add(to);
11984
12080
  }
11985
12081
  };
11986
- const importsOf = (path83) => {
11987
- const cached = imports.get(path83);
12082
+ const importsOf = (path82) => {
12083
+ const cached = imports.get(path82);
11988
12084
  if (cached) return cached;
11989
- const facts = index.factsAt(path83);
12085
+ const facts = index.factsAt(path82);
11990
12086
  const out = /* @__PURE__ */ new Set();
11991
- if (facts?.status === "analyzed") resolveAll(path83, facts.imports, out);
11992
- imports.set(path83, out);
12087
+ if (facts?.status === "analyzed") resolveAll(path82, facts.imports, out);
12088
+ imports.set(path82, out);
11993
12089
  return out;
11994
12090
  };
11995
- const tracesOf = (path83) => {
11996
- const cached = traces.get(path83);
12091
+ const tracesOf = (path82) => {
12092
+ const cached = traces.get(path82);
11997
12093
  if (cached) return cached;
11998
- const facts = index.factsAt(path83);
11999
- const out = new Set(importsOf(path83));
12000
- if (facts?.status === "analyzed") resolveAll(path83, facts.reexports, out);
12001
- traces.set(path83, out);
12094
+ const facts = index.factsAt(path82);
12095
+ const out = new Set(importsOf(path82));
12096
+ if (facts?.status === "analyzed") resolveAll(path82, facts.reexports, out);
12097
+ traces.set(path82, out);
12002
12098
  return out;
12003
12099
  };
12004
12100
  return {
@@ -12134,6 +12230,7 @@ var init_hierarchy_integrity = __esm({
12134
12230
  init_models();
12135
12231
  hierarchyRule = {
12136
12232
  name: "hierarchy-integrity",
12233
+ judges: "design",
12137
12234
  description: "Every subsystem references the system, every component an existing subsystem, every interface an existing component, and every implementation an existing interface contract. Draft/design specs are reported informationally.",
12138
12235
  codes: [
12139
12236
  { code: "DRAFT_SUBSYSTEM_WARNING", defaultSeverity: "warning", summary: "Subsystem is in draft/design status" },
@@ -12213,6 +12310,7 @@ var init_reserved_id_segments = __esm({
12213
12310
  "use strict";
12214
12311
  reservedIdSegmentsRule = {
12215
12312
  name: "reserved-id-segments",
12313
+ judges: "design",
12216
12314
  description: 'No segment of a spec id may be the reserved namespace keyword "super" \u2014 the :: grammar consumes it as a namespace hop, so a stored reference to such an id resolves to a different spec.',
12217
12315
  codes: [
12218
12316
  { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
@@ -12253,6 +12351,7 @@ var init_local_ids = __esm({
12253
12351
  init_models();
12254
12352
  localIdsRule = {
12255
12353
  name: "local-ids",
12354
+ judges: "design",
12256
12355
  description: "Inside one project an id names one spec, and an id without `::` is always local. Two spec files of one project declaring one key are DUPLICATE_SPEC_ID: whichever the loader kept, every reference to that key \u2014 local, or `alias::name` from another project \u2014 could mean either, so the tree has no single reading. A local id equal to one of the project's aliases (a `members` or `externals` key) is LOCAL_ID_SHADOWS_PROJECT: the grammar keeps them apart (a bare id is local, `alias::name` crosses), but a reader seeing `billing` beside `billing::invoice_portal` cannot tell a local subsystem from a member, so the shadow is made visible. It reads the project graph's owners, alias tables and duplicate-spec problems.",
12257
12356
  codes: [
12258
12357
  { code: "DUPLICATE_SPEC_ID", defaultSeverity: "error", summary: "Two spec files of one project declare the same id" },
@@ -12329,6 +12428,7 @@ var init_project_identity = __esm({
12329
12428
  "use strict";
12330
12429
  projectIdentityRule = {
12331
12430
  name: "project-identity",
12431
+ judges: "design",
12332
12432
  description: "The bound project is keyed on its id, so the id must be declared, well-formed, stable and its own: a project that declares none answers to its display name slugified until it writes one (PROJECT_ID_DEFAULTED), a project whose name yields no id or whose declared id breaks the grammar has nothing reliable to key on (PROJECT_ID_AMBIGUOUS), and an id that differs from the one the lock approved has moved under everything that keyed on it (PROJECT_ID_CHANGED). It judges the bound project's own identity only. The family half \u2014 a member with a defaulted id (named with its alias as the id to declare), a member with no id, and two members resolving to one id \u2014 is a family check since stage 4 (family_validator.checkMembers), which reads the family root's graph.",
12333
12433
  codes: [
12334
12434
  { code: "PROJECT_ID_AMBIGUOUS", defaultSeverity: "warning", summary: "Project has no usable id: its name yields no slug, or its declared id breaks the grammar" },
@@ -12350,6 +12450,7 @@ var init_roundtrip_serialization = __esm({
12350
12450
  "use strict";
12351
12451
  roundtripRule = {
12352
12452
  name: "roundtrip-serialization",
12453
+ judges: "design",
12353
12454
  description: "Every loaded spec must re-serialize through the exact writer pipeline (same relativization, same schema, no I/O) \u2014 validate must predict every refusal that lock's status promotion or any later save would otherwise raise mid-write.",
12354
12455
  codes: [
12355
12456
  { code: "ROUNDTRIP_SERIALIZATION", defaultSeverity: "error", summary: "Spec cannot be re-serialized through the writer schema (any save or lock would refuse it)" }
@@ -12370,6 +12471,7 @@ var init_type_declarations = __esm({
12370
12471
  "use strict";
12371
12472
  typeDeclarationsRule = {
12372
12473
  name: "type-declarations",
12474
+ judges: "design",
12373
12475
  description: "A type owned by a subsystem must reference an existing one, and a type declaring neither fields nor methods is a placeholder that can inform neither implementers nor the ERD.",
12374
12476
  codes: [
12375
12477
  { code: "INVALID_SUBSYSTEM_REFERENCE", defaultSeverity: "error", summary: "Type references a non-existent owning subsystem" },
@@ -12411,6 +12513,7 @@ var init_field_type_references = __esm({
12411
12513
  init_models();
12412
12514
  fieldTypeReferencesRule = {
12413
12515
  name: "field-type-references",
12516
+ judges: "design",
12414
12517
  description: "Every type identifier a type field's declared type names must resolve to a builtin or a defined entity/value-object type \u2014 the type's own generic parameters are not field references.",
12415
12518
  codes: [
12416
12519
  { code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
@@ -12449,6 +12552,7 @@ var init_signature_type_references = __esm({
12449
12552
  init_models();
12450
12553
  signatureTypeReferencesRule = {
12451
12554
  name: "signature-type-references",
12555
+ judges: "design",
12452
12556
  description: "Every type identifier an interface method signature names must resolve to a builtin, a generic parameter in scope on the interface or the method, or a defined entity/value-object type.",
12453
12557
  codes: [
12454
12558
  { code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
@@ -12492,6 +12596,7 @@ var init_public_surface_binding = __esm({
12492
12596
  init_models();
12493
12597
  publicSurfaceBindingRule = {
12494
12598
  name: "public-surface-binding",
12599
+ judges: "design",
12495
12600
  description: "Every declared publicInterface names a backing component, that component exists, and the subsystem owns it \u2014 a subsystem may only publish its own components.",
12496
12601
  codes: [
12497
12602
  { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
@@ -12530,6 +12635,7 @@ var init_public_surface_consumers = __esm({
12530
12635
  init_models();
12531
12636
  publicSurfaceConsumersRule = {
12532
12637
  name: "public-surface-consumers",
12638
+ judges: "design",
12533
12639
  description: "Every subsystem a published surface names as its consumer must exist in the tree. A consumers list restricts who may depend on the surface, so an id that names nothing locks the surface against a caller nobody can be \u2014 and a typo there silently locks out the caller that was meant, which the boundary rule would then report against the wrong party.",
12534
12640
  codes: [
12535
12641
  { code: "PUBLIC_INTERFACE_UNKNOWN_CONSUMER", defaultSeverity: "error", summary: "A published surface names as its consumer a subsystem the tree does not have" }
@@ -12594,6 +12700,7 @@ var init_public_surface_declared_type = __esm({
12594
12700
  };
12595
12701
  publicSurfaceDeclaredTypeRule = {
12596
12702
  name: "public-surface-declared-type",
12703
+ judges: "design",
12597
12704
  description: "A publicInterface entry's declared type must be realizable by the stereotype of its backing component; Custom entries whose prose implies eventing must be backed by an event-capable component.",
12598
12705
  codes: [
12599
12706
  { code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
@@ -12631,6 +12738,7 @@ var init_public_surface_bound_contract = __esm({
12631
12738
  init_models();
12632
12739
  publicSurfaceBoundContractRule = {
12633
12740
  name: "public-surface-bound-contract",
12741
+ judges: "design",
12634
12742
  description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
12635
12743
  codes: [
12636
12744
  { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
@@ -12662,6 +12770,7 @@ var init_export_tables = __esm({
12662
12770
  init_models();
12663
12771
  exportTablesRule = {
12664
12772
  name: "export-tables",
12773
+ judges: "design",
12665
12774
  description: "Every level's export table must resolve the way a module resolver resolves `export \u2026 from`: one public name binds one target (EXPORT_ID_DUPLICATE when two wildcards \u2014 or two explicit entries \u2014 bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out), no chain leads back to itself (EXPORT_CYCLE \u2014 an error for a named chain that never reaches a component or type, a warning for a wildcard cycle, which resolves to its union), every entry names a source and an item that exist, that the source exports and, for an own type, that the level owns \u2014 a subsystem-owned type at L1, a project-level type (no subsystem) for an L0 `{ typeDef }` without `from` \u2014 under a public name of [a-z0-9-_]+ (EXPORT_INVALID), every re-exported component can serve a boundary caller \u2014 a Portal, a gateway being a Portal variant, or an Observer for events (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports; an error since stage 4), and an L0 entry that re-exports a member's or an external's export never declares a wider audience than that export has (EXPORT_WIDENS_AUDIENCE): a re-export can narrow reach, never widen it. The rule judges the facts the export resolver recorded in the scan \u2014 for the bound root's table and every contained member's \u2014 and resolves nothing itself.",
12666
12775
  codes: [
12667
12776
  { code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
@@ -12735,6 +12844,7 @@ var init_external_declarations = __esm({
12735
12844
  init_models();
12736
12845
  externalDeclarationsRule = {
12737
12846
  name: "external-declarations",
12847
+ judges: "design",
12738
12848
  description: "Every external a project declares in project.yaml must be usable: its alias fits [a-z0-9-_]+ (a dotted producer id needs an explicit alias) and is not also a `members` key (one alias names one project), its producer id fits the project-id grammar, and it has something to be judged against \u2014 a project of the scan answers to the id, source.path names a directory (whose project, when it is in the scan, answers to the declared id), or the project holds a pin for the alias. A declaration that fails is reported (EXTERNAL_UNRESOLVED) with its reason, so a misspelled declaration never passes as declared. A member validated from its own root that declares its parent or a sibling is not reported when it has pinned it: the pin is what its gate judges against, and nothing is climbed to find the producer. Every name an external's or a member's `use` lists by name must be a public name the producer exports to this project \u2014 a contained member's live L0 table, an external's pin \u2014 compared by nameKey (IMPORT_UNRESOLVED, naming the closest public names; a `use` on an unpinned external is left to the pin's own EXTERNAL_CHECK_UNAVAILABLE), and must not share its nameKey with a spec of the project itself (IMPORT_SHADOWED_BY_LOCAL: the local spec wins every bare reference, so the named import can never take effect and says something false \u2014 rename one or drop the import). A `*` import shadowed by locals is silent, as a glob import is in Rust. One code, one meaning: IMPORT_UNRESOLVED is only a name the producer does not export, IMPORT_SHADOWED_BY_LOCAL only a named import a local spec hides. A malformed `use` entry is the declaration's problem (EXTERNAL_UNRESOLVED for an external, the member's for a member). The findings name no spec: externals and members live in configuration. It reads the bound root's node of the project graph, its alias-conflict problems, the members' export tables, the project's own spec ids and ctx.pinnedExternals.",
12739
12849
  codes: [
12740
12850
  { code: "EXTERNAL_UNRESOLVED", defaultSeverity: "notice", summary: "A declared external whose alias is malformed or taken by a member, whose producer id is malformed, or whose producer the family and its source.path do not provide" },
@@ -12836,6 +12946,7 @@ var init_reference_forms = __esm({
12836
12946
  init_models();
12837
12947
  referenceFormsRule = {
12838
12948
  name: "reference-forms",
12949
+ judges: "design",
12839
12950
  description: "A cross-project reference is `alias::name` (or a bare name a `use` imports): the alias is one of the referring project's members or externals, the name a public name of that project's L0 table. The older forms still bind for one release and are reported (DEPRECATED_REFERENCE_FORM), each naming the spec, where in it, the form as written and the text to write instead (the scan's rewrite: a bare local id, or `alias::publicName`): a leading `::`, which escapes to the root of whatever checkout loads it; `super::`, which climbs the containing projects \u2014 a place in one family, not a name; a member path (`desktop::shell` written inside desktop, `waffler_core::transpiler::x` written inside transpiler), a first segment the referring project does not declare, read from the bound root; and a self-prefix \u2014 the referring project's own id as the first segment, with `::` or `.` (`registry.advisory-channel` inside registry), which stage 3's migration did not rewrite and stage 4's positional step does. Wairon's own writer emits none of them. A form with no rewrite (its target is out of reach) is reported with the reason. It reads the project graph's authored references, which the scan records before binding erases the form.",
12840
12951
  codes: [
12841
12952
  { code: "DEPRECATED_REFERENCE_FORM", defaultSeverity: "notice", summary: "A reference written with a leading ::, super:: or a member path where a local id or alias::name belongs" }
@@ -12872,6 +12983,7 @@ var init_member_declarations = __esm({
12872
12983
  DECLARATION_FIELDS = /* @__PURE__ */ new Set(["id", "name", "description", "parentSystem", "projectPath", "status", "createdAt", "updatedAt"]);
12873
12984
  memberDeclarationsRule = {
12874
12985
  name: "member-declarations",
12986
+ judges: "design",
12875
12987
  description: "A project declares its members in project.yaml `members`; a member is not a subsystem and carries no content in its parent. The L1 form \u2014 a subsystem carrying projectPath \u2014 still loads for one release and is reported (DEPRECATED_MOUNT_FORM), naming the mount, its path, the `members` entry that replaces it, and every field it carries that `doctor --fix` will move into the member (or stop on); a mount whose alias `members` also declares is reported as ignored. It reads the project graph's nodes and their legacy mounts.",
12876
12988
  codes: [
12877
12989
  { code: "DEPRECATED_MOUNT_FORM", defaultSeverity: "notice", summary: "A member declared as an L1 subsystem carrying projectPath instead of a project.yaml `members` entry" }
@@ -12935,6 +13047,7 @@ var init_project_boundaries = __esm({
12935
13047
  init_models();
12936
13048
  projectBoundariesRule = {
12937
13049
  name: "project-boundaries",
13050
+ judges: "design",
12938
13051
  description: "The one judge of every cross-project reference the bound project writes (decision 9 \u2014 every project is a crate of its own, reached only through its L0 exports and only as a dependency it declared). It reads each of the project's own references from the project graph \u2014 `::` forms and bare names bound through `use` imports alike \u2014 takes its resolution from resolveCrossProject, decided before any severity from the project's own files alone, and reports every outcome but resolved once, carrying the resolution: forbidden is EXTERNAL_UNDECLARED (the reference reaches a project the referrer declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers); missing is EXTERNAL_NOT_EXPORTED (the producer's table \u2014 a contained member's live one, or the external's pin \u2014 has no public name covering the referrer, or not the used method, or exports the type only inside a signature's closure); an ambiguous import is IMPORT_AMBIGUOUS (two imports supply one bare name: two explicit `use` names, or two `*` with no explicit one \u2014 name it `alias::name` or narrow a `use`); unavailable is EXTERNAL_CHECK_UNAVAILABLE (no pin, an unreadable pin, a member absent on disk, or a deprecated form naming no alias \u2014 never a pass). A bare name no import supplies is not this rule's: it is the unresolved-reference finding it always was, with rule_context.importHint appended. A trustedLinks entry naming a subsystem of another project (`alias::sub`) is TRUSTED_LINK_CROSSES_PROJECT: a sanctioned fast lane stays inside one project. Stage 4 makes the three stage-2 notices errors now that the owner's gate judges them exactly; a reference is never left to a parent's verdict.",
12939
13052
  codes: [
12940
13053
  { code: "EXTERNAL_UNDECLARED", defaultSeverity: "error", summary: "A reference reaches another project the referring project declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers" },
@@ -13032,6 +13145,7 @@ var init_lint_allows = __esm({
13032
13145
  "use strict";
13033
13146
  lintAllowsRule = {
13034
13147
  name: "lint-allows",
13148
+ judges: "design",
13035
13149
  description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. An allow covers exactly the occurrence it names: a finding that reports a site is silenced only by an allow whose `at` is that site, a finding that reports none only by an allow that names none, and an aggregating finding only by an allow whose `covers` lists every unit it reports \u2014 a unit nobody listed is named back as new instead of inheriting a decision taken about its neighbours. So a coarse allow left on a rule that names sites, and an allow whose site the run no longer reports, are both UNUSED_LINT_ALLOW, and the finding names the sites that did fire. Allows silence warnings and notices; errors always surface.",
13036
13150
  codes: [
13037
13151
  { code: "UNKNOWN_LINT_ALLOW_CODE", defaultSeverity: "warning", summary: "lint.allow names an issue code no registered rule emits" },
@@ -13079,6 +13193,7 @@ var init_contract_symmetry = __esm({
13079
13193
  "use strict";
13080
13194
  contractSymmetryRule = {
13081
13195
  name: "contract-symmetry",
13196
+ judges: "design",
13082
13197
  description: "Implementations mirror their contract method-for-method: every method an implementation declares is defined on the contract it realizes, and every contract method has an implementation.",
13083
13198
  codes: [
13084
13199
  { code: "UNEXPECTED_IMPLEMENTATION_METHOD", defaultSeverity: "error", summary: "Implementation method not present on the contract" },
@@ -13145,6 +13260,7 @@ var init_narrative_target_references = __esm({
13145
13260
  };
13146
13261
  narrativeTargetReferencesRule = {
13147
13262
  name: "narrative-target-references",
13263
+ judges: "design",
13148
13264
  description: `A narrative call, dispatch or register step must name a target component, and a target method unless it dispatches; a method's declared calls name the same pair as one "<component>.<method>" reference, the spelling the conformance debt register and a lint allow name a unit with. A target this tree contains must be a collaborator the calling component declares, must carry the named method on one of its interfaces, and that method must declare every semantic guarantee the entry asserts.`,
13149
13265
  codes: [
13150
13266
  { code: "MISSING_TARGET_COMPONENT", defaultSeverity: "error", summary: "Call/register step missing targetComponent" },
@@ -13253,6 +13369,7 @@ var init_cross_tree_references = __esm({
13253
13369
  init_models();
13254
13370
  crossTreeReferencesRule = {
13255
13371
  name: "cross-tree-references",
13372
+ judges: "design",
13256
13373
  description: `A narrative call, dispatch or register target that is not a component of this tree either leaves the project \u2014 and then its resolution (resolveCrossProject) decides: resolved is surface-reference-backing's, foreign snapshots that disagree are SURFACE_REF_AMBIGUOUS here, and every other outcome is project-boundaries' single finding \u2014 or it is a local id that names nothing, the INVALID_TARGET_COMPONENT_REFERENCE typo it always was. The CROSS_TREE_REF_UNRESOLVED warning ("only the parent project can verify it") is retired: no reference is left for a parent to judge.`,
13257
13374
  codes: [
13258
13375
  { code: "INVALID_TARGET_COMPONENT_REFERENCE", defaultSeverity: "error", summary: "Call/register step targets a non-existent component" },
@@ -13315,6 +13432,7 @@ var init_surface_reference_backing = __esm({
13315
13432
  "use strict";
13316
13433
  surfaceReferenceBackingRule = {
13317
13434
  name: "surface-reference-backing",
13435
+ judges: "design",
13318
13436
  description: "The contract entry a narrative target in another project resolves to \u2014 a contained member's live export table, a declared external's pinned snapshot, or a foreign surface snapshot \u2014 must back what the step asks of it: the calling component declares the collaborator, the entry exposes the called method (or serves the dispatched capability), and it declares every semantic guarantee the step asserts. These are contract verdicts on a resolved reference, and each carries that resolution.",
13319
13437
  codes: [
13320
13438
  { code: "SURFACE_REF_NOT_EXPOSED", defaultSeverity: "error", summary: "Cross-tree reference resolves to a surface snapshot that does not expose the called method/capability" },
@@ -13404,6 +13522,7 @@ var init_guarantee_tokens = __esm({
13404
13522
  init_models();
13405
13523
  guaranteeTokensRule = {
13406
13524
  name: "guarantee-tokens",
13525
+ judges: "design",
13407
13526
  description: "Every semantic-guarantee token an L3 method declares or a narrative step asserts must be a builtin guarantee or one a loaded extension pack declares in its `guarantees` list. The vocabulary is open for packs, not for typos \u2014 an undeclared token is matched by nothing and silently escapes the narrative\u2194contract consistency checks.",
13408
13527
  codes: [
13409
13528
  { code: "UNKNOWN_GUARANTEE", defaultSeverity: "warning", summary: "Guarantee token is neither builtin nor pack-declared" }
@@ -13458,6 +13577,7 @@ var init_narrative_step_config = __esm({
13458
13577
  init_models();
13459
13578
  narrativeStepConfigRule = {
13460
13579
  name: "narrative-step-config",
13580
+ judges: "design",
13461
13581
  description: 'The step configuration of L5 narratives: every flow step (branch, switch, loop, try, parallel, jump) carries the config its type requires and no simple step carries flow config, "detach" (fire-and-forget) is legal on call/dispatch steps only, no step carries a field foreign to its type, a parallel body is covered by contiguous, ordered arms (>= 2), no two steps share a step number or a label, and every jump field targets an existing step number in the same narrative.',
13462
13582
  codes: [
13463
13583
  { code: "MALFORMED_FLOW_STEP", defaultSeverity: "error", summary: "Flow step missing required config (or flow config on a local/call step)" },
@@ -13499,6 +13619,7 @@ var init_narrative_reachability = __esm({
13499
13619
  init_models();
13500
13620
  narrativeReachabilityRule = {
13501
13621
  name: "narrative-reachability",
13622
+ judges: "design",
13502
13623
  description: "Every step of an L5 narrative must be reachable from the first step following fall-through and jumps, and the loop/try/parallel regions it declares must nest or stay disjoint \u2014 interleaved regions map onto no structured code. Judged over the step graph, and only for a narrative whose step configuration is sound (narrative-step-config reports the rest).",
13503
13624
  codes: [
13504
13625
  { code: "UNREACHABLE_STEP", defaultSeverity: "warning", summary: "Step not reachable from the first step following fall-through and jumps" },
@@ -13588,6 +13709,7 @@ var init_narrative_jump_edges = __esm({
13588
13709
  init_models();
13589
13710
  narrativeJumpEdgesRule = {
13590
13711
  name: "narrative-jump-edges",
13712
+ judges: "design",
13591
13713
  description: "Where an L5 narrative's jump edges land: a loop/try/parallel region is entered through its header, never into the middle of its body; a backward jump is only idiomatic as a continue to an enclosing loop header, and otherwise models repetition that belongs in a loop step; and a try body must not fall through into its own catch/finally region on the success path. Judged only for a narrative whose step configuration is sound (narrative-step-config reports the rest).",
13592
13714
  codes: [
13593
13715
  { code: "JUMP_INTO_REGION", defaultSeverity: "warning", summary: "Jump lands in the middle of a loop/try body from outside \u2014 regions are entered through their header" },
@@ -13776,6 +13898,7 @@ var init_meaningless_branches = __esm({
13776
13898
  init_completed_step_graph();
13777
13899
  meaninglessBranchesRule = {
13778
13900
  name: "meaningless-branches",
13901
+ judges: "design",
13779
13902
  description: "A branch or switch whose arms all target the same step decides nothing: the condition is evaluated and flow continues at one place whatever it answers. A decision's targets are its explicit ones plus, where it leaves its fall-through field unset, the steps the step graph continues it at \u2014 so the classic authoring slip of pointing both arms at the next step is caught. Structure-only \u2014 a warning, lint.allow-suppressible.",
13780
13903
  codes: [
13781
13904
  { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
@@ -13835,6 +13958,7 @@ var init_inescapable_cycles = __esm({
13835
13958
  init_completed_step_graph();
13836
13959
  inescapableCyclesRule = {
13837
13960
  name: "inescapable-cycles",
13961
+ judges: "design",
13838
13962
  description: "A cycle in a narrative's step graph with no exit edge and no return or throw member never terminates, by construction: every path that enters it stays inside it forever. Read over the step graph closed by a completion step, so falling off the end of the narrative counts as leaving. Structure-only \u2014 prose conditions are never judged \u2014 and reported as a warning, lint.allow-suppressible.",
13839
13963
  codes: [
13840
13964
  { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
@@ -13883,6 +14007,7 @@ var init_unconditional_call_cycles = __esm({
13883
14007
  byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
13884
14008
  unconditionalCallCyclesRule = {
13885
14009
  name: "unconditional-call-cycles",
14010
+ judges: "design",
13886
14011
  description: "A method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path of its narrative is unbounded recursion, by construction. Dispatch steps are resolved through their portal's table; register steps are never edges (a handoff defers the invocation to the runtime, so it cannot recurse). A cycle with even one guarded edge is NOT flagged, since prose conditions are never judged. Structure-only \u2014 a warning, lint.allow-suppressible, anchored on the member edge that sorts first so one allow covers the cycle.",
13887
14012
  codes: [
13888
14013
  { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
@@ -13942,11 +14067,11 @@ var init_unconditional_call_cycles = __esm({
13942
14067
  const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
13943
14068
  if (memberEdges.length === 0) continue;
13944
14069
  const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
13945
- const path83 = [...keys].sort(byCodeUnit).join(" \u2192 ");
14070
+ const path82 = [...keys].sort(byCodeUnit).join(" \u2192 ");
13946
14071
  ctx.addIssue(
13947
14072
  "warning",
13948
14073
  "UNCONDITIONAL_CALL_CYCLE",
13949
- `Call cycle with no guard: ${path83} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
14074
+ `Call cycle with no guard: ${path82} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
13950
14075
  anchor.impl.id,
13951
14076
  memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
13952
14077
  );
@@ -13964,6 +14089,7 @@ var init_narrative_detail = __esm({
13964
14089
  init_models();
13965
14090
  narrativeDetailRule = {
13966
14091
  name: "narrative-detail",
14092
+ judges: "design",
13967
14093
  description: "The narrative detail dial: each method resolves to full | calls-only | intent (method override \u2192 spec default \u2192 stereotype default). full requires a narrative; intent-level methods without a narrative must specify behavior as non-trivial prose (L4 intent or L3 description) \u2014 dialing detail down never means leaving behavior unspecified. Explicit declarations are held to their promise as errors; stereotype-defaulted gaps surface as warnings. An honest lint over declarations \u2014 it never claims the narrative or the prose is CORRECT.",
13968
14094
  codes: [
13969
14095
  { code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
@@ -14016,6 +14142,7 @@ var init_detail_sufficiency = __esm({
14016
14142
  DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
14017
14143
  detailSufficiencyRule = {
14018
14144
  name: "detail-sufficiency",
14145
+ judges: "code",
14019
14146
  description: "A method dialed below detail: full with no narrative may not be hiding behavior it owes a reader. A method whose realized function \u2014 in the method's own source file, else the implementation's \u2014 measures real branching (cyclomatic complexity above rules.complexity.maxUnnarratedComplexity, exact AST grade only) is reported (UNNARRATED_COMPLEXITY), and an explicit dial below a full-floor logic stereotype without a narrative is a visible, lint.allow-justifiable choice (DETAIL_BELOW_STEREOTYPE) \u2014 reported only where the measured finding did not already fire, since evidence outranks expectation. Both are honest lints over declarations: they never claim a narrative would be correct, only that the dial hides something.",
14020
14147
  codes: [
14021
14148
  { code: "UNNARRATED_COMPLEXITY", defaultSeverity: "warning", summary: "Method below detail: full with no narrative whose realized function, in the method's source file, has real branching (cyclomatic complexity over the configured threshold, exact-grade analysis only)" },
@@ -14079,6 +14206,7 @@ var init_portal_fields = __esm({
14079
14206
  "use strict";
14080
14207
  portalFieldsRule = {
14081
14208
  name: "portal-fields",
14209
+ judges: "design",
14082
14210
  scope: "spec",
14083
14211
  description: "A Portal declares its portalType. Non-Portal components carry no portalType, basePath, or auth (auth is inbound transport auth \u2014 it belongs on the Portal that exposes the surface). Intrinsic to one component: no tree required.",
14084
14212
  codes: [
@@ -14132,6 +14260,7 @@ var init_durability_declaration = __esm({
14132
14260
  "use strict";
14133
14261
  durabilityDeclarationRule = {
14134
14262
  name: "durability-declaration",
14263
+ judges: "design",
14135
14264
  scope: "spec",
14136
14265
  description: "Every Store declares its durability (MISSING_DURABILITY) and nothing but a Store may declare one (DURABILITY_ON_NON_STORE). Intrinsic to one component: no tree required. The round-trip consequences of the declaration are enforced by durability-round-trip.",
14137
14266
  codes: [
@@ -14176,6 +14305,7 @@ var init_logic_declaration = __esm({
14176
14305
  init_models();
14177
14306
  logicDeclarationRule = {
14178
14307
  name: "logic-declaration",
14308
+ judges: "design",
14179
14309
  scope: "spec",
14180
14310
  description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by logic-dependency-class. Intrinsic to one component: no tree required.",
14181
14311
  codes: [
@@ -14207,6 +14337,7 @@ var init_retired_stereotypes = __esm({
14207
14337
  init_models();
14208
14338
  retiredStereotypesRule = {
14209
14339
  name: "retired-stereotypes",
14340
+ judges: "design",
14210
14341
  scope: "spec",
14211
14342
  description: "A component typed with a retired stereotype is an error until it is migrated (STEREOTYPE_RETIRED). A Specialist becomes an Orchestrator with the dependencyClass its dependencies give it, which `wairon doctor --fix` applies. A Gateway becomes the Portal it owns, with the gateway variant: its other members are depended on rather than owned, its consumers depend on that Portal, and the Gateway spec is deleted, steps the finding lists for the author. Intrinsic to one component: no tree required.",
14212
14343
  codes: [
@@ -14256,6 +14387,7 @@ var init_portal_endpoints = __esm({
14256
14387
  };
14257
14388
  portalsRule = {
14258
14389
  name: "portal-endpoints",
14390
+ judges: "design",
14259
14391
  description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
14260
14392
  codes: [
14261
14393
  { code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
@@ -14302,6 +14434,7 @@ var init_non_portal_endpoints = __esm({
14302
14434
  "use strict";
14303
14435
  nonPortalEndpointsRule = {
14304
14436
  name: "non-portal-endpoints",
14437
+ judges: "design",
14305
14438
  description: "Only a Portal may carry endpoints: a method on a non-Portal component's interface that declares one is publishing a block that is not the system's boundary as if it were.",
14306
14439
  codes: [
14307
14440
  { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
@@ -14337,6 +14470,7 @@ var init_portal_call_auth = __esm({
14337
14470
  "use strict";
14338
14471
  portalCallAuthRule = {
14339
14472
  name: "portal-call-auth",
14473
+ judges: "design",
14340
14474
  description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
14341
14475
  codes: [
14342
14476
  { code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
@@ -14388,6 +14522,7 @@ var init_auth_source_wiring = __esm({
14388
14522
  COMPONENT_REF_PREFIX = "component:";
14389
14523
  authSourceWiringRule = {
14390
14524
  name: "auth-source-wiring",
14525
+ judges: "design",
14391
14526
  description: "Resolves the credential source a narrative call presents to an authenticated Portal. When the step's `auth.from` is a modeled reference (`component:<id>`) it must name a component that exists (UNKNOWN_AUTH_SOURCE), that can hold or load a secret \u2014 an Adapter or a Store (AUTH_SOURCE_NOT_PROVIDER) \u2014 and that the presenting component depends on or owns (AUTH_SOURCE_UNWIRED). Opaque sources (env:/config:/vault:/free note) are design notes and are never resolved; a missing source is portal-call-auth's finding.",
14392
14527
  codes: [
14393
14528
  { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
@@ -14460,6 +14595,7 @@ var init_subsystem_boundary_dependencies = __esm({
14460
14595
  init_models();
14461
14596
  subsystemBoundaryDepsRule = {
14462
14597
  name: "subsystem-boundary-dependencies",
14598
+ judges: "design",
14463
14599
  description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A published surface may also name the subsystems it serves (a publicInterfaces entry's consumers): a component every one of whose entries names consumers may be depended on only from those subsystems. An edge into another project is judged on the contract entry it resolves to (resolveSurfaceRef \u2014 a contained member's live table, a declared external's pin, or a foreign snapshot): the same Adapter requirement applies to it, and foreign snapshots of several providers that disagree make it SURFACE_REF_AMBIGUOUS. An edge into another project that does not resolve is reported once, by project-boundaries, with its resolution \u2014 never here, and never as the retired CROSS_TREE_REF_UNRESOLVED warning. A bare id that names nothing is INVALID_DEPENDENCY_REFERENCE. Boundary rules are never relaxed by a pack profile.",
14464
14600
  codes: [
14465
14601
  { code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
@@ -14588,6 +14724,7 @@ var init_logic_dependency_class = __esm({
14588
14724
  init_stereotype_terms();
14589
14725
  logicDependencyClassRule = {
14590
14726
  name: "logic-dependency-class",
14727
+ judges: "design",
14591
14728
  description: "Bounds an Orchestrator's dependencies by the dependencyClass it declares: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters; an Orchestrator that declares no class is a workflow and may depend on anything the rest of the matrix allows. Whether read logic calls a WRITE method on one of those is judged only once facade methods carry effect tags.",
14592
14729
  codes: [
14593
14730
  { code: "DEPENDENCY_CLASS_VIOLATION", defaultSeverity: "error", summary: "Orchestrator depending on a component its dependencyClass does not allow: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters" }
@@ -14652,6 +14789,7 @@ var init_data_block_dependencies = __esm({
14652
14789
  init_stereotype_terms();
14653
14790
  dataBlockDepsRule = {
14654
14791
  name: "data-block-dependencies",
14792
+ judges: "design",
14655
14793
  description: "Keeps the data layer facing down. A Store may depend only on another Store, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse. A Registry is the write path to its Store and may depend on that Store, a backend Adapter, or the pure logic that validates the write. An Index is a read projection and a Query a computed read, each over its Store through a backend Adapter where one serves it; as an exceptional case an Index may project another Index of the same Repository, never in a cycle. An Adapter is a sink toward the system: it may use pure logic, but never a Store or any other Orchestrator.",
14656
14794
  codes: [
14657
14795
  { code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
@@ -14728,6 +14866,7 @@ var init_entrypoint_dependencies = __esm({
14728
14866
  init_stereotype_terms();
14729
14867
  entrypointDepsRule = {
14730
14868
  name: "entrypoint-dependencies",
14869
+ judges: "design",
14731
14870
  description: "Judges the edges at the system's entry points and its process layer. Portals and Observers are top-level entry points and subscribers, so nothing may depend on them \u2014 and that is the edge's one finding, which is why no other matrix rule judges it. Downward, a Portal dispatches to Orchestrators and may READ through Indexes and Repository facades but never reaches Store/Registry/Query or an Adapter, while an Observer forwards to one Orchestrator or Supervisor over a message-bus Adapter. A View stays a passive presenter. A Supervisor reaches data only through workflows, and a component depending on a live Actor must also depend on a Supervisor that supervises it.",
14732
14871
  codes: [
14733
14872
  { code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
@@ -14811,6 +14950,7 @@ var init_portal_write_shortcut = __esm({
14811
14950
  "use strict";
14812
14951
  portalWriteShortcutRule = {
14813
14952
  name: "portal-write-shortcut",
14953
+ judges: "design",
14814
14954
  description: "A Portal narrative call step or dispatch-table binding that reaches a write-effect method on a Repository or Index directly is the write shortcut: the Portal\u2192data-facade edge is licensed for reads only, and a write routes through an Orchestrator that owns the workflow. A dispatch step reaches its server only through a table binding, so judging every binding judges each dispatch step that takes it, once, where the route is declared. Methods that carry no effect tag are not judged.",
14815
14955
  codes: [
14816
14956
  { code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
@@ -14868,6 +15008,7 @@ var init_pattern_membership = __esm({
14868
15008
  init_models();
14869
15009
  patternMembershipRule = {
14870
15010
  name: "pattern-membership",
15011
+ judges: "design",
14871
15012
  description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern to claim it.",
14872
15013
  codes: [
14873
15014
  { code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
@@ -14924,6 +15065,7 @@ var init_pattern_containment = __esm({
14924
15065
  REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
14925
15066
  patternContainmentRule = {
14926
15067
  name: "pattern-containment",
15068
+ judges: "design",
14927
15069
  description: "Holds each pattern to the containment its definition prescribes. A Repository may own only Store, Registry, Index, Query and (optionally) Adapter, judged member by member. A FeatureComponent owns exactly one Orchestrator (the logic side) and one or more Views (its faces \u2014 list, detail, form \u2014 sharing the one logic component), and nothing else. A RouterComponent owns exactly one Portal as its facade and at least one other child to route to. A counting pattern that still owns a retired member is not judged until that member is migrated: its counts change with the migration, and STEREOTYPE_RETIRED is the one finding.",
14928
15070
  codes: [
14929
15071
  { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
@@ -14990,6 +15132,7 @@ var init_unowned_blocks = __esm({
14990
15132
  "use strict";
14991
15133
  unownedBlocksRule = {
14992
15134
  name: "unowned-blocks",
15135
+ judges: "design",
14993
15136
  description: `Judges the data blocks no pattern owns. A Store may stand alone deliberately \u2014 the sanctioned lightweight form for genuinely simple state, acknowledged with a lint.allow \u2014 while the recommended shape stays a Repository; what must never happen is folding the state into a consuming component, where no spec, diagram or conformance check can see it again. A Registry standing alone with no Store to write to is either mistyped (the "file-backed Registry" idiom, a fused persistent store that belongs typed Store) or orphaned. A Query has no standalone form at all: it computes reads over its own Repository's Store.`,
14994
15137
  codes: [
14995
15138
  { code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
@@ -15042,6 +15185,7 @@ var init_member_visibility = __esm({
15042
15185
  init_models();
15043
15186
  memberVisibilityRule = {
15044
15187
  name: "member-visibility",
15188
+ judges: "design",
15045
15189
  description: "A component may depend on a block within its own group (it is the owning pattern, or a sibling member of the same pattern), on any pattern facade, or on a standalone block \u2014 never on a block privately owned by ANOTHER pattern, which must be reached through that pattern's facade. A retired depending component is skipped, as everywhere in this family.",
15046
15190
  codes: [
15047
15191
  { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
@@ -15070,6 +15214,7 @@ var init_facade_forwarding = __esm({
15070
15214
  "use strict";
15071
15215
  facadeForwardingRule = {
15072
15216
  name: "facade-forwarding",
15217
+ judges: "design",
15073
15218
  description: "A Repository facade method with an authored narrative must be pure 1:1 forwarding: exactly one call step, targeting one of the pattern's owned members. Anything else is logic living on the facade \u2014 move it into a member block, or acknowledge a deliberate exception via lint.allow.",
15074
15219
  codes: [
15075
15220
  { code: "FACADE_FORWARDING", defaultSeverity: "warning", summary: "Pattern facade narrative is not a single call to an owned member" }
@@ -15118,6 +15263,7 @@ var init_profile_registration = __esm({
15118
15263
  PROJECT_KINDS2 = new Set(PROJECT_KINDS);
15119
15264
  profileRegistrationRule = {
15120
15265
  name: "profile-registration",
15266
+ judges: "design",
15121
15267
  description: "Every profile name in play \u2014 the project's own projectType and each subsystem's declared profile \u2014 must be a built-in profile, a composite project kind, or one a loaded extension pack registers. An unregistered name is flagged rather than guessed at: no doctrine is governing it.",
15122
15268
  codes: [
15123
15269
  { code: "UNKNOWN_PROFILE", defaultSeverity: "warning", summary: "Profile name is neither built-in nor registered by an extension pack" }
@@ -15155,6 +15301,7 @@ var init_profile_stereotype_fencing = __esm({
15155
15301
  FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
15156
15302
  profileStereotypeFencingRule = {
15157
15303
  name: "profile-stereotype-fencing",
15304
+ judges: "design",
15158
15305
  description: "The built-in profile families fence which stereotypes may run under them: View/FeatureComponent/RouterComponent only in a frontend-like profile; Actor/Supervisor forbidden in plc-cyclic, whose logic runs single-threaded inside one execution scan cycle; Actor/Supervisor in a frontend-like profile warned. A pack profile opts in by declaring its family; an unregistered profile is neutral, with no doctrine to guess.",
15159
15306
  codes: [
15160
15307
  { code: "FRONTEND_STEREOTYPE_IN_BACKEND", defaultSeverity: "error", summary: "Frontend stereotype in a backend-oriented subsystem" },
@@ -15214,6 +15361,7 @@ var init_pack_profile_stereotypes = __esm({
15214
15361
  "use strict";
15215
15362
  packProfileStereotypesRule = {
15216
15363
  name: "pack-profile-stereotypes",
15364
+ judges: "design",
15217
15365
  description: "A pack-registered profile carries its own stereotype doctrine on top of the built-in family fencing: the stereotypes it lists as forbidden are errors and the ones it discourages are warnings, each reported with the reason the pack states for it.",
15218
15366
  codes: [
15219
15367
  { code: "PROFILE_FORBIDDEN_STEREOTYPE", defaultSeverity: "error", summary: "Stereotype forbidden by the governing pack-defined profile" },
@@ -15260,6 +15408,7 @@ var init_pattern_references = __esm({
15260
15408
  "use strict";
15261
15409
  patternReferencesRule = {
15262
15410
  name: "pattern-references",
15411
+ judges: "design",
15263
15412
  description: "Every component pattern reference resolves to a reusable pattern declared by a loaded extension pack (UNKNOWN_PATTERN_REF); a pinned version with no matching pack pattern is warned (PATTERN_VERSION_MISMATCH). Pattern constraint enforcement itself is delegated to the declaring pack's rules.",
15264
15413
  codes: [
15265
15414
  { code: "UNKNOWN_PATTERN_REF", defaultSeverity: "warning", summary: "Component references a pattern no loaded pack declares" },
@@ -15310,6 +15459,7 @@ var init_component_variants = __esm({
15310
15459
  "use strict";
15311
15460
  variantReferencesRule = {
15312
15461
  name: "component-variants",
15462
+ judges: "design",
15313
15463
  description: "A component's variant resolves to a declared registry variant (UNKNOWN_VARIANT), and the component's stereotype equals the variant's declared base (VARIANT_BASE_MISMATCH). A variant is a base-anchored specialization (a kind of the base stereotype), not a cross-cutting attribute \u2014 those stay method guarantees.",
15314
15464
  codes: [
15315
15465
  { code: "UNKNOWN_VARIANT", defaultSeverity: "warning", summary: "Component references a variant no registry declares" },
@@ -15381,6 +15531,7 @@ var init_assertion_forbidden_edges = __esm({
15381
15531
  init_declared_assertion();
15382
15532
  assertionForbiddenEdgesRule = {
15383
15533
  name: "assertion-forbidden-edges",
15534
+ judges: "design",
15384
15535
  description: "Evaluates the `forbid-edge` assertions loaded packs declare: for every component matching the assertion's from-selector, every dependsOn/owns id whose target matches the to-selector is reported. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
15385
15536
  // Static codes are unknown here — packs bring their own. The validator
15386
15537
  // gathers every loaded assertion's fullCode into knownIssueCodes.
@@ -15452,6 +15603,7 @@ var init_assertion_required_fields = __esm({
15452
15603
  init_declared_assertion();
15453
15604
  assertionRequiredFieldsRule = {
15454
15605
  name: "assertion-required-fields",
15606
+ judges: "design",
15455
15607
  description: "Evaluates the `require-field` assertions loaded packs declare: at the assertion's level (component / interface / implementation), every spec whose owning component matches the on-selector must declare the named field \u2014 a top-level spec field or an `ext.*` path \u2014 and, when the assertion states a closed value set, hold one of those values. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
15456
15608
  // Static codes are unknown here — packs bring their own. The validator
15457
15609
  // gathers every loaded assertion's fullCode into knownIssueCodes.
@@ -15516,6 +15668,7 @@ var init_assertion_endpoint_shapes = __esm({
15516
15668
  init_declared_assertion();
15517
15669
  assertionEndpointShapesRule = {
15518
15670
  name: "assertion-endpoint-shapes",
15671
+ judges: "design",
15519
15672
  description: "Evaluates the `endpoint-shape` assertions loaded packs declare: every endpoint bound on a contract whose component matches the on-selector must use an allowed transport and bind an address (path / topic / command / pipe / channel) matching the assertion's anchored pattern. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
15520
15673
  // Static codes are unknown here — packs bring their own. The validator
15521
15674
  // gathers every loaded assertion's fullCode into knownIssueCodes.
@@ -15547,6 +15700,7 @@ var init_pack_resolution = __esm({
15547
15700
  "use strict";
15548
15701
  packResolutionRule = {
15549
15702
  name: "pack-resolution",
15703
+ judges: "design",
15550
15704
  description: "Every pack a project declares must resolve \u2014 from a committed bundle under .wai/packs/, or from this wairon install's pack store \u2014 and must be the content it claims. An unresolvable selection is an error reported under the code that names the remedy: PACK_NOT_INSTALLED (absent entirely), PACK_VERSION_UNSATISFIED (installed, but no version satisfies the pin), or PACK_INTEGRITY_MISMATCH (resolved content does not match the pinned integrity digest, recomputed from the files rather than read from metadata, on whichever path won). Those never downgrade to warnings: a project whose declared doctrine is missing or altered is misconfigured, and a gate running without it must not look clean. PACK_STORE_DRIFT is the one warning \u2014 a committed bundle and the store hold different content for the same name@version; the bundle applies, so the store copy is silently ignored and that deserves saying.",
15551
15705
  codes: [
15552
15706
  { code: "PACK_NOT_INSTALLED", defaultSeverity: "error", summary: "A declared pack is neither bundled nor installed in the pack store" },
@@ -15570,6 +15724,7 @@ var init_pack_reproducibility = __esm({
15570
15724
  "use strict";
15571
15725
  reproducibilityRule = {
15572
15726
  name: "pack-reproducibility",
15727
+ judges: "design",
15573
15728
  description: "Under rules.enforceReproducibility (default true), every by-name pack selection must be reproducible elsewhere: pinned to an exact version (ideally with an integrity digest) or bundled into the repository. A floating selection resolves against whatever the local machine happens to have installed, so a clone or CI can validate the same tree against a different rule set (UNPINNED_PACK_SELECTION). A selection that records no fetchable source cannot be obtained by CI at all (PACK_SOURCE_UNFETCHABLE).",
15574
15729
  codes: [
15575
15730
  { code: "UNPINNED_PACK_SELECTION", defaultSeverity: "warning", summary: "Pack selection floats to the latest installed version instead of pinning one" },
@@ -15607,6 +15762,7 @@ var init_pack_requirements = __esm({
15607
15762
  init_project();
15608
15763
  packRequirementsRule = {
15609
15764
  name: "pack-requirements",
15765
+ judges: "design",
15610
15766
  description: "Every requirement in the bound project's own composition.requirePolicies must be readable: a pack name and a semver range that parses (pack_requirement.rangeProblem). One that does not is POLICY_REQUIREMENT_INVALID, quoting the range and the unreadable token \u2014 an error on the requiring project's own gate, because the requirement is its own configuration and the family run cannot judge a member against it. It judges only the syntax: whether members adopt the requirement is the family run's question (family_validator.adoption), and nothing here reads a parent's requirements.",
15611
15767
  codes: [
15612
15768
  { code: "POLICY_REQUIREMENT_INVALID", defaultSeverity: "error", summary: "A project's composition.requirePolicies names a pack range that does not parse" }
@@ -15634,6 +15790,7 @@ var init_dependency_cycles = __esm({
15634
15790
  "use strict";
15635
15791
  cyclesRule = {
15636
15792
  name: "dependency-cycles",
15793
+ judges: "design",
15637
15794
  description: "The component dependsOn graph must be a DAG; the first cycle through a component in the run's scope is reported with its full path.",
15638
15795
  codes: [
15639
15796
  { code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
@@ -15684,6 +15841,7 @@ var init_dispatch_table_bindings = __esm({
15684
15841
  "use strict";
15685
15842
  dispatchTableBindingsRule = {
15686
15843
  name: "dispatch-table-bindings",
15844
+ judges: "design",
15687
15845
  description: "A dispatch table belongs to a Portal, binds each capability once, and binds it to an existing component.method inside the portal's own subsystem (the portal dispatches inward) that the portal declares under dependsOn or owns \u2014 the table is a real runtime invocation path.",
15688
15846
  codes: [
15689
15847
  { code: "DISPATCH_ON_NON_PORTAL", defaultSeverity: "error", summary: "Dispatch table declared on a component that is not a Portal" },
@@ -15769,6 +15927,7 @@ var init_dispatch_step_routing = __esm({
15769
15927
  "use strict";
15770
15928
  dispatchStepRoutingRule = {
15771
15929
  name: "dispatch-step-routing",
15930
+ judges: "design",
15772
15931
  description: "A dispatch narrative step must name the capability it routes and route it through a Portal whose dispatch table actually serves that capability; a guarantee the step asserts must be declared by the contract method the capability resolves to.",
15773
15932
  codes: [
15774
15933
  { code: "MALFORMED_DISPATCH_STEP", defaultSeverity: "error", summary: "Dispatch step missing its capability (targetComponent presence is narrative-target-references' finding)" },
@@ -15846,6 +16005,7 @@ var init_lifecycle_entrypoints = __esm({
15846
16005
  init_models();
15847
16006
  lifecycleRule = {
15848
16007
  name: "lifecycle-entrypoints",
16008
+ judges: "design",
15849
16009
  description: "Declared subsystem lifecycle entrypoints (init/shutdown flows) must name an existing component and a method on one of its interfaces \u2014 they are reachability roots, so a dangling entrypoint would silently detach every flow rooted in it.",
15850
16010
  codes: [
15851
16011
  { code: "INVALID_LIFECYCLE_ENTRYPOINT", defaultSeverity: "error", summary: "Lifecycle entrypoint names a missing component or method" },
@@ -15892,8 +16052,8 @@ var init_lifecycle_entrypoints = __esm({
15892
16052
  });
15893
16053
 
15894
16054
  // src/core/rules/wiring/portal-mounts.ts
15895
- function liesUnder(path83, prefix) {
15896
- return path83 === prefix || path83.startsWith(prefix + "/");
16055
+ function liesUnder(path82, prefix) {
16056
+ return path82 === prefix || path82.startsWith(prefix + "/");
15897
16057
  }
15898
16058
  var portalMountsRule;
15899
16059
  var init_portal_mounts = __esm({
@@ -15901,6 +16061,7 @@ var init_portal_mounts = __esm({
15901
16061
  "use strict";
15902
16062
  portalMountsRule = {
15903
16063
  name: "portal-mounts",
16064
+ judges: "design",
15904
16065
  description: "Which listener serves which portal: a portal's routes are its methods' endpoint bindings, but nothing said which listener hands it its requests, so a portal no listener serves was unreachable without anyone noticing, and the call that mounts one crossed a boundary no contract described. A listener Portal declares its mounts \u2014 each a portal, the path prefixes routed to it, and optionally the router entry it calls \u2014 and this rule checks them against the tree: a mount must name a Portal, every HTTP endpoint of a mounted portal must lie under one of its prefixes, and every portal with HTTP endpoints must be mounted by some listener unless it is one. A path lies under a prefix when it equals it or continues it past a slash, so `/` covers only the root. Whether the named entry is really exported is the code's question, and export-conformance asks it.",
15905
16066
  codes: [
15906
16067
  {
@@ -16087,6 +16248,7 @@ var init_unused_detection = __esm({
16087
16248
  init_narrative_graph_projector();
16088
16249
  reachabilityRule = {
16089
16250
  name: "unused-detection",
16251
+ judges: "design",
16090
16252
  description: "Walks the narrative execution graph (call steps, register handoffs, dispatch-table routing, lifecycle flows) from every entrypoint (Portal/Observer/published component/lifecycle entrypoint/invokedBy-declared method) and flags components and methods no execution chain reaches. An interface method declaring invokedBy (a real caller outside the modeled graph) seeds the walk, so its narrative propagates reachability; a declaration the INTERNAL walk (without those seeds) already reaches is stale and is reported.",
16091
16253
  codes: [
16092
16254
  { code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
@@ -16178,6 +16340,7 @@ var init_invoked_by_description = __esm({
16178
16340
  init_models();
16179
16341
  invokedByDescriptionRule = {
16180
16342
  name: "invoked-by-description",
16343
+ judges: "design",
16181
16344
  description: 'An invokedBy declaration names a caller outside the modeled graph and silences unused-detection for its method, so the claim must stay reviewable: its "caller" prose is held to the intent floor (missing or placeholder-thin prose is reported), stating WHO invokes the method and when.',
16182
16345
  codes: [
16183
16346
  { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
@@ -16212,6 +16375,7 @@ var init_unused_types = __esm({
16212
16375
  init_models();
16213
16376
  unusedTypesRule = {
16214
16377
  name: "unused-types",
16378
+ judges: "design",
16215
16379
  description: "Flags types no field of any type, no interface method signature and no other type's method signature references. References are matched through the type-reference grammar (generic arguments, collections, qualified ids), and the declaring type's own generic parameters are left out \u2014 a type parameter is not a reference to a type. A type named only by its OWN methods stays unused, the way a function that only calls itself is.",
16216
16380
  codes: [
16217
16381
  { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields, signatures or type methods" }
@@ -16278,6 +16442,7 @@ var init_durability_round_trip = __esm({
16278
16442
  init_narrative_graph_projector();
16279
16443
  durabilityRule = {
16280
16444
  name: "durability-round-trip",
16445
+ judges: "design",
16281
16446
  description: "A durable Store (persisted RAM projection) must carry effect-tagged contract methods, and its writes require a hydration read-back reachable from a lifecycle init entrypoint. read-through is exempt (every read IS the read-back), as are ram-projection (rebuilt not restored) and cache (evictable, loss-safe). The flagship semantic check is opt-out by declaration, never silently absent \u2014 the declaration itself is enforced by durability-declaration.",
16282
16447
  codes: [
16283
16448
  { code: "MISSING_EFFECT_TAG", defaultSeverity: "warning", summary: "Durable Store contract method lacks an effect: read | write tag" },
@@ -16340,6 +16505,7 @@ var init_untyped_seams = __esm({
16340
16505
  BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
16341
16506
  untypedSeamRule = {
16342
16507
  name: "untyped-seams",
16508
+ judges: "design",
16343
16509
  description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown/object, judged through each method's type references so a prose signature is judged like structured params: cross-subsystem contracts are the swap seam and must be typed. Generic-dispatch portals carry per-capability types via their dispatch table instead.",
16344
16510
  codes: [
16345
16511
  { code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
@@ -16403,6 +16569,7 @@ var init_prose_claims = __esm({
16403
16569
  QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
16404
16570
  proseClaimRule = {
16405
16571
  name: "prose-claims",
16572
+ judges: "design",
16406
16573
  description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "persistence", "survives restart", "registered into") on a logic component whose narrative has no call, register or dispatch edge to any data-layer component (Store/Registry/Index/Adapter/Repository). Text inside quotes or backticks names a value or quotes a message and claims nothing. Data-layer components are exempt \u2014 they ARE the persistence.',
16407
16574
  codes: [
16408
16575
  { code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
@@ -16467,6 +16634,7 @@ var init_unique_invariant_ids = __esm({
16467
16634
  init_models();
16468
16635
  uniqueInvariantIdsRule = {
16469
16636
  name: "unique-invariant-ids",
16637
+ judges: "design",
16470
16638
  description: `An entity's declared invariant ids must be unique within the entity: a narrative step asserts an invariant by "<type-id>.<invariant-id>", so a repeated id makes one assertion stand for two properties and satisfying either silences both.`,
16471
16639
  codes: [
16472
16640
  { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
@@ -16571,6 +16739,7 @@ var init_invariant_backing = __esm({
16571
16739
  init_invariant_ref();
16572
16740
  invariantBackingRule = {
16573
16741
  name: "invariant-backing",
16742
+ judges: "design",
16574
16743
  description: 'The invariant registry: entities may declare domain invariants (type.invariants), anchored through their componentClass. Every write-effect contract method of the owning component must carry a narrative step asserting each invariant (step.assertsInvariants: "<type-id>.<invariant-id>") \u2014 the same declared-and-backed shape as semantic guarantees. This is an HONEST lint over declarations: a green run means every write path visibly claims the invariant, never that the narrative or code actually enforces it. An invariant with no resolvable owner or no declared write path is unanchored, and nothing further is claimed about it.',
16575
16744
  codes: [
16576
16745
  { code: "INVARIANT_UNANCHORED", defaultSeverity: "warning", summary: "An entity declares invariants but has no componentClass, its componentClass does not resolve, or the owning component declares no write-effect contract methods" },
@@ -16636,6 +16805,7 @@ var init_invariant_references = __esm({
16636
16805
  init_invariant_ref();
16637
16806
  invariantReferencesRule = {
16638
16807
  name: "invariant-references",
16808
+ judges: "design",
16639
16809
  description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere.",
16640
16810
  codes: [
16641
16811
  { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
@@ -16702,6 +16872,7 @@ var init_event_topology = __esm({
16702
16872
  "use strict";
16703
16873
  eventTopologyRule = {
16704
16874
  name: "event-topology",
16875
+ judges: "design",
16705
16876
  description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name, and a topic is reported once per component however many declarations bind it there. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
16706
16877
  codes: [
16707
16878
  { code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
@@ -16746,6 +16917,7 @@ var init_source_file_linkage = __esm({
16746
16917
  quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
16747
16918
  sourceFileLinkageRule = {
16748
16919
  name: "source-file-linkage",
16920
+ judges: "code",
16749
16921
  description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real, readable file inside the project root, and an implementation that names no file at all, or leaves a contract method without one, is linked to no code. A file that escapes the root, is missing or cannot be analyzed is reported once and blocks only the methods realized in it. A run in which TypeScript/JavaScript files were analyzed below exact grade reports that once (CONFORMANCE_DEGRADED), since dependency conformance then skips them. Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
16750
16922
  codes: [
16751
16923
  { code: "MISSING_SOURCE_PATH", defaultSeverity: "warning", summary: "An implementation names no source file at all, or a contract method is left without one \u2014 structural conformance cannot link it to code" },
@@ -16863,6 +17035,7 @@ var init_method_realization = __esm({
16863
17035
  init_models();
16864
17036
  methodRealizationRule = {
16865
17037
  name: "method-realization",
17038
+ judges: "code",
16866
17039
  description: "Code\u2194spec Level 1: every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier \u2014 declared | anchored | off; Portals default to anchored, everything else to declared, and a per-method `symbol` maps an intent-language name onto the code name. A method realized by a DECLARATION owes a function BODY as well: at exact grade one must be reachable under the symbol, here or through the imports and republications this file forwards it by, so a signature, an ambient or interface declaration or a plain value binding stops reading as an implementation (METHOD_BODY_NOT_FOUND). Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Methods whose file escapes the root, is missing or could not be analyzed are left to source-file-linkage, and implementations under chained subsystems (projectPath) validate standalone in their own project run.",
16867
17040
  codes: [
16868
17041
  { code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" },
@@ -16930,6 +17103,7 @@ var init_finding_realization = __esm({
16930
17103
  init_models();
16931
17104
  findingRealizationRule = {
16932
17105
  name: "finding-realization",
17106
+ judges: "code",
16933
17107
  description: "Code\u2194spec Level 1: every finding code an L3 contract method declares must be anchored in the method's own source file (the method's sourcePath, else the implementation's) \u2014 as a string literal or, at exact grade, a property-access name, so a code reported through a constants object counts. Below exact grade any identifier or word counts, and the grade rides on the finding. Respects the conformance dial (off skips), leaves unresolvable files to source-file-linkage, and skips implementations under chained subsystems (projectPath), which validate standalone in their own project run.",
16934
17108
  codes: [
16935
17109
  { code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" }
@@ -17062,6 +17236,7 @@ var init_call_conformance = __esm({
17062
17236
  claimNoun = (implMethod) => implMethod.narrative.length ? "narrative call step(s)" : "declared call(s)";
17063
17237
  callConformanceRule = {
17064
17238
  name: "call-conformance",
17239
+ judges: "code",
17065
17240
  description: "Code\u2194spec Level 3: the CLAIMED call \u2194 realized call relation, judged both ways against the method's own source file (its sourcePath, else the implementation's), at exact analysis grade only. A method claims a call two ways and they face one check: a narrative `call` step, and an entry of the declared `calls` a narrative-less method reaches its collaborators through. They assert the same thing, and the step number is no part of the claim \u2014 this check never reads order, so it is only how a finding points at one. Forward: every claim must be realized by a call whose callee RESOLVES TO one of the target method's own source files \u2014 the target's contract name or a per-method `symbol` override, closed transitively over the named helpers the realized function calls, and resolved against every file the call CAN have reached: a `this.<field>.<method>()` receiver followed through the field's DECLARED TYPE, a `new Class(...).<method>()` receiver through the module its CLASS NAME came from, and a plain `<name>.<method>()` receiver through the type the file ANNOTATES that name with \u2014 a parameter's, or an annotated variable's, which is how a module that wires its collaborators as closures writes every one of its calls. A matching call whose origin a pure model cannot resolve is reported apart as CALL_ORIGIN_UNRESOLVED, which NAMES the shape it could not follow and asks for nothing \u2014 a coverage hole in the reader, reported for the reason CONFORMANCE_DEGRADED is: a silently degraded gate is worse than a degraded one. A finding names a landing only from the PROVEN tier so that widening what a call reached can accept a claim but never accuse one, and a target that names no file of its own falls back to name membership. Converse: a call that resolves to a modelled method of ANOTHER component in the SAME file crosses a component boundary while looking local, so the method must claim it \u2014 in a step or in `calls` (UNDECLARED_COLOCATED_CALL) \u2014 judged on the proven tier alone, and a same-file private helper is no modelled method and is never reported. A method that claims nothing at all is judged in neither direction: what it leaves unsaid is UNUSED_*'s subject. Order, arguments and conditions stay unverified, dispatch steps (runtime-table routed) are no claim about a call site, a malformed declared reference is MALFORMED_DECLARED_CALL's finding and names no target here, the conformance dial (off) skips, and weaker analysis grades never guess.",
17066
17241
  codes: [
17067
17242
  { code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step or declared call realized by no call that resolves to the target method's own source file \u2014 the call is absent, or it lands in another module", carryable: true },
@@ -17194,6 +17369,7 @@ var init_hidden_state = __esm({
17194
17369
  isStatelessLogic = (component) => isLogic(component) && !holdsState(component);
17195
17370
  hiddenStateRule = {
17196
17371
  name: "hidden-state",
17372
+ judges: "code",
17197
17373
  description: "The fields-vs-Store criterion, statically approximated: module-scope mutable bindings (let/var) in a source file mapped EXCLUSIVELY to logic that holds no state (a component whose type isLogic() and not holdsState(): an Orchestrator, or a Specialist until it is retired) are flagged as hidden held state. A component maps every file its implementations and their methods name. State a logic component keeps for itself is invisible to the spec, the canvas, and every persistence rule: promote it to a Store (durability: cache for loss-safe memo state), or lint.allow with the reason it is genuinely wiring or ephemeral. Supervisor and Actor files may hold runtime state. Exact analysis grade only; files also mapped to data or boundary components are exempt (N:1 collapse); const-bound container mutation is beyond this check and the finding says so.",
17198
17374
  codes: [
17199
17375
  { code: "HIDDEN_STATE", defaultSeverity: "warning", summary: "Module-scope mutable binding in a file mapped only to stateless logic (Orchestrators) \u2014 held state hiding outside a Store, Supervisor or Actor" }
@@ -17221,11 +17397,11 @@ var init_hidden_state = __esm({
17221
17397
  byPath.set(key, list2);
17222
17398
  }
17223
17399
  }
17224
- for (const path83 of code.exactPaths) {
17225
- const facts = code.factsAt(path83);
17400
+ for (const path82 of code.exactPaths) {
17401
+ const facts = code.factsAt(path82);
17226
17402
  const bindings = facts.topLevelMutableBindings ?? [];
17227
17403
  if (bindings.length === 0) continue;
17228
- const mapped = byPath.get(path83) ?? [];
17404
+ const mapped = byPath.get(path82) ?? [];
17229
17405
  if (mapped.length === 0) continue;
17230
17406
  if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
17231
17407
  const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
@@ -17251,6 +17427,7 @@ var init_dependency_conformance = __esm({
17251
17427
  init_models();
17252
17428
  dependencyConformanceRule = {
17253
17429
  name: "dependency-conformance",
17430
+ judges: "code",
17254
17431
  description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning). Only exact-grade analyzed files participate; type-only imports are exempt; chained subprojects validate standalone.",
17255
17432
  codes: [
17256
17433
  { code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
@@ -17298,7 +17475,7 @@ var init_dependency_conformance = __esm({
17298
17475
  }
17299
17476
  return false;
17300
17477
  };
17301
- const draftAt = (path83) => realization.implementationsAt(path83).some((impl) => ctx.isImplementationDraft(impl));
17478
+ const draftAt = (path82) => realization.implementationsAt(path82).some((impl) => ctx.isImplementationDraft(impl));
17302
17479
  for (const fromPath of mappedPaths) {
17303
17480
  const fromComponents = realization.componentsAt(fromPath);
17304
17481
  for (const toPath of graph2.importsOf(fromPath)) {
@@ -17363,6 +17540,7 @@ var init_portal_imports = __esm({
17363
17540
  "use strict";
17364
17541
  portalImportsRule = {
17365
17542
  name: "portal-imports",
17543
+ judges: "code",
17366
17544
  description: "A runtime import that crosses a subsystem boundary must land on a file that realizes one of the target subsystem's PUBLISHED components \u2014 its portal file. dependency-conformance asks whether the hop is declared; this asks where it lands. An import that reaches past the portal into a concrete module can use anything that module exports, published or not, so the portal stops being the boundary and names no contract ever promised leak across it \u2014 which is how a surface grows that nobody designed. In-process it is cosmetic at runtime; it is not cosmetic for the contract. Only exact-grade component-mapped files participate; type-only imports are exempt; chained subprojects validate standalone.",
17367
17545
  codes: [
17368
17546
  {
@@ -17389,7 +17567,7 @@ var init_portal_imports = __esm({
17389
17567
  }
17390
17568
  return files;
17391
17569
  };
17392
- const draftAt = (path83) => realization.implementationsAt(path83).some((impl) => ctx.isImplementationDraft(impl));
17570
+ const draftAt = (path82) => realization.implementationsAt(path82).some((impl) => ctx.isImplementationDraft(impl));
17393
17571
  for (const fromPath of mappedPaths) {
17394
17572
  const fromSubsystems = new Set(realization.componentsAt(fromPath).map((c) => c.subsystem));
17395
17573
  for (const toPath of graph2.importsOf(fromPath)) {
@@ -17423,6 +17601,7 @@ var init_integration_sim_declaration = __esm({
17423
17601
  "use strict";
17424
17602
  integrationSimDeclarationRule = {
17425
17603
  name: "integration-sim-declaration",
17604
+ judges: "code",
17426
17605
  description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim, and a subsystem that has adopted nothing is never flooded.",
17427
17606
  codes: [
17428
17607
  { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
@@ -17466,6 +17645,7 @@ var init_integration_sim_file = __esm({
17466
17645
  init_models();
17467
17646
  integrationSimFileRule = {
17468
17647
  name: "integration-sim-file",
17648
+ judges: "code",
17469
17649
  description: "A declared L4 simPath must resolve to a readable, committed file inside the project root \u2014 the integration harness is a re-runnable file, not a claim. Execution stays CI's job.",
17470
17650
  codes: [
17471
17651
  { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
@@ -17502,6 +17682,7 @@ var init_integration_sim_wiring = __esm({
17502
17682
  init_models();
17503
17683
  integrationSimWiringRule = {
17504
17684
  name: "integration-sim-wiring",
17685
+ judges: "code",
17505
17686
  description: "A committed integration harness's import graph \u2014 closed transitively over the analyzed module set, exact grade only \u2014 must reach at least one of the component's own source modules (its implementation's or a method's) and at least one source module of each direct dependsOn/owns component (technology-boundary dependencies exempt: their contract-faithful fakes are sanctioned). Execution is CI's job \u2014 this proves wiring, never that the sim passes.",
17506
17687
  codes: [
17507
17688
  { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
@@ -17566,6 +17747,7 @@ var init_integration_sim_coverage = __esm({
17566
17747
  init_models();
17567
17748
  integrationSimCoverageRule = {
17568
17749
  name: "integration-sim-coverage",
17750
+ judges: "code",
17569
17751
  description: "Once a committed integration harness carries sim:<component-id>. anchors, that component claims path coverage: every narrated method's happy path and every labeled throw step needs its exact sim:<component>.<method>[:<label>] string-literal anchor. Anchors prove the path is NAMED \u2014 assertion quality and execution stay CI's job.",
17570
17752
  codes: [
17571
17753
  { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
@@ -17646,6 +17828,7 @@ var init_type_realization = __esm({
17646
17828
  };
17647
17829
  typeRealizationRule = {
17648
17830
  name: "type-realization",
17831
+ judges: "code",
17649
17832
  description: "Code\u2194spec Level 1 for the data model: a type that names a sourcePath is claiming code, so every file it names \u2014 its own and each method's \u2014 must resolve to a real, readable file inside the project root, its file must PUBLISH its declaration (an exported name at exact grade, the declaration tier below it, under its `symbol` when the code-level name differs from the type's name \u2014 and a pure re-export barrel publishes nothing of its own, so a claim on one is never realized), and each of its pure methods must appear in its own file (the method's sourcePath, else the type's) at the declaration tier, under the method's `symbol`, else the method's name. A type that names no sourcePath claims nothing and is never reported. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible, a file that escapes the root, is missing or could not be analyzed is reported once and blocks only what it would have realized, and types under chained subsystems (projectPath) validate standalone in their own project run.",
17650
17833
  codes: [
17651
17834
  { code: "UNREALIZED_TYPE", defaultSeverity: "warning", summary: "A type names a sourcePath but its declaration is nowhere in that file \u2014 the claim points at code that does not hold it" },
@@ -17721,6 +17904,7 @@ var init_type_shape = __esm({
17721
17904
  init_models();
17722
17905
  typeShapeRule = {
17723
17906
  name: "type-shape",
17907
+ judges: "code",
17724
17908
  description: "Code-to-contract for the DATA: a type spec's `fields` are compared, name by name, against the shape its `sourcePath` actually declares. `typeRealization` asks whether a type EXISTS in code; nothing asked whether it is the shape the spec claims, so a type spec could describe two fields of a six-field record \u2014 and call the two that are optional required \u2014 straight through a lock and a CI gate, while the ERD, the agent briefs and every implementer read it as truth. That is the worst of the code-to-spec gaps, because a wrong signature eventually breaks at a call site and a type spec that lies is only ever read by humans and agents. Two origins answer at exact grade: a DECLARED shape, whose members the file lists, and a DERIVED one, an alias followed one hop to the object literal its schema is built from, where the keys are the members. A shape that extends another is judged on what it shows and never on what it omits, since its inherited members are not in this file to count.",
17725
17909
  codes: [
17726
17910
  {
@@ -17861,6 +18045,7 @@ var init_param_conformance = __esm({
17861
18045
  init_models();
17862
18046
  paramConformanceRule = {
17863
18047
  name: "param-conformance",
18048
+ judges: "code",
17864
18049
  description: "Code-to-contract for the SIGNATURE, the last of the three readings a spec-driven gate never made: a contract declares `params`, and nothing ever compared them to the parameters of the function that realizes the method. A contract could promise an argument the code does not take, take one the contract never mentions \u2014 including a secret \u2014 or name the same argument two different things, and the brief handed to an implementer would carry the contract's version. Parameters are matched by POSITION against the tail of the realization's list, and the declared type is what tells a rename from a dropped argument: `seed(config: HostConfig)` realized as `bootstrapInstance(cfg: HostConfig)` is one parameter under two names, which anything matching on names alone reads as a parameter the code lost. What a realization takes BEFORE the contract's own parameters is wiring, and it is declared on the implementation as `injectedParams` rather than inferred, because an inferred prefix cannot be told from a renamed first argument. A method the named file only CALLS is left to `methodRealization`, which already reports that the body is not here.",
17865
18050
  codes: [
17866
18051
  {
@@ -17975,8 +18160,8 @@ var init_param_conformance = __esm({
17975
18160
  });
17976
18161
 
17977
18162
  // src/core/rules/conformance/route-coverage.ts
17978
- function liesUnder2(path83, prefix) {
17979
- return path83 === prefix || path83.startsWith(prefix + "/");
18163
+ function liesUnder2(path82, prefix) {
18164
+ return path82 === prefix || path82.startsWith(prefix + "/");
17980
18165
  }
17981
18166
  function normalizeSegment(segment) {
17982
18167
  return /^\{.+\}$/.test(segment) || segment.startsWith(":") ? "*" : segment;
@@ -18002,6 +18187,7 @@ var init_route_coverage = __esm({
18002
18187
  init_models();
18003
18188
  routeCoverageRule = {
18004
18189
  name: "route-coverage",
18190
+ judges: "code",
18005
18191
  description: "Code-to-contract for the ROUTES: does every route a router actually answers have a contract endpoint, and does every contract endpoint have a route that answers it? A portal's endpoints are what its contract promises; the router is what the code serves; nothing compared the two, so a route with no contract \u2014 including a write \u2014 could run for months without a single rule noticing. The listener's mount names the router function (its `via`), and the routes are read out of that function's guards; the path segment the router never checks is the one the mount's prefix guarantees. One idiom is read \u2014 a method comparison with comparisons on the path's split segments \u2014 and a mounted router that yields no route in it is reported as unread, never passed: a check that cannot see a router must say so rather than stay quiet.",
18006
18192
  codes: [
18007
18193
  {
@@ -18111,6 +18297,7 @@ var init_unclaimed_source = __esm({
18111
18297
  init_models();
18112
18298
  unclaimedSourceRule = {
18113
18299
  name: "unclaimed-source",
18300
+ judges: "code",
18114
18301
  description: "Code\u2194spec Level 1, asked the other way round: every source file under the project's declared source roots must be named by some spec \u2014 an implementation's sourcePath, a method's, a simPath, or a type's \u2014 or be carried in the frozen `rules.conformance.unclaimed` list. A pure re-export barrel declares nothing of its own and only republishes other modules, so it has no code to claim and is exempt (exact grade only: a weaker grade cannot tell a barrel from a file it failed to parse). Declaring no source roots leaves the check silent, which is what makes it opt-in; the unclaimed list is a one-way debt register, so an entry the walk no longer finds unclaimed must be deleted and the list can only shrink.",
18115
18302
  codes: [
18116
18303
  { code: "UNCLAIMED_SOURCE_FILE", defaultSeverity: "warning", summary: "A source file under a declared source root that no spec names and the frozen unclaimed list does not carry \u2014 code nobody designed" },
@@ -18165,6 +18352,7 @@ var init_export_conformance = __esm({
18165
18352
  NOTHING_PROMISED = /* @__PURE__ */ new Set();
18166
18353
  exportConformanceRule = {
18167
18354
  name: "export-conformance",
18355
+ judges: "code",
18168
18356
  description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a listener's mount names the router entry it calls to hand a portal its requests; both are declared publication, and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module.",
18169
18357
  codes: [
18170
18358
  {
@@ -18329,6 +18517,7 @@ var init_carried_debt = __esm({
18329
18517
  "use strict";
18330
18518
  carriedDebtRule = {
18331
18519
  name: "carried-debt",
18520
+ judges: "code",
18332
18521
  description: "The conformance debt register (`rules.conformance.carried`) holds the code\u2194spec findings this tree carries as declared, classified debt \u2014 `unclaimed`'s one-way shape, for findings about code a spec does name. It is not a second lint.allow: an allow says a finding is wrong here by design, an entry here says the finding is right and unpaid, and each entry states which of the three kinds it is (drift: spec and code disagree; undecided: the fix waits on a modelling decision nobody has taken; unreadable: the analysis cannot follow the shape the code is written in) and why. Only a code a rule declares CARRYABLE may appear, and naming any other is an error, so the register can never widen into general-purpose suppression. An entry carries a finding only when it lists EVERY unit that finding reports, so an aggregating finding cannot grow behind it; an entry that matched nothing, that duplicates another, that names a spec the tree does not hold, or that lists a unit no longer reported is STALE_CARRIED_FINDING, so the register only shrinks. The audit is silent in a run that could not have fired the findings at all: one that built no code model, the specs outside a scoped run, and a code the project switched off. Nothing can check a reason for TRUTH \u2014 a stale entry is one that stopped applying, never one that still applies for a reason that has become false \u2014 so a reason group may declare its own classification provisional (`revisit`: what would settle it), and every run counts those findings beside the kind totals.",
18333
18522
  codes: [
18334
18523
  { code: "UNCARRYABLE_FINDING", defaultSeverity: "error", summary: "The conformance debt register names an issue code no rule emits, or one no rule declares carryable \u2014 the register holds measured code\u2194spec debt, never a design rule somebody wants quiet" },
@@ -18446,6 +18635,7 @@ var init_method_cohesion = __esm({
18446
18635
  "use strict";
18447
18636
  methodCohesionRule = {
18448
18637
  name: "method-cohesion",
18638
+ judges: "design",
18449
18639
  description: "An Orchestrator's methods share collaborators; when they split into groups that call no component in common, the component holds more than one responsibility. Reported only where two or more groups each hold two or more methods, so a single specialized method is not a finding \u2014 and never on a pure forwarder, whose every narrated method is a single hand-off and whose responsibility therefore lives in what it forwards to.",
18450
18640
  codes: [
18451
18641
  { code: "INCOHESIVE_METHODS", defaultSeverity: "warning", summary: "Orchestrator's methods form two or more groups of two or more that share no called component" }
@@ -18498,6 +18688,7 @@ var init_coupling_health = __esm({
18498
18688
  DEFAULT_GOD_COMPONENT_THRESHOLD = 8;
18499
18689
  couplingRule = {
18500
18690
  name: "coupling-health",
18691
+ judges: "design",
18501
18692
  description: "Two subsystems depending on each other is a real architectural commitment (deployment affinity, backpressure, scaling coupling). It is allowed but must be acknowledged with a trustedLinks declaration on either side, stating the reason (e.g. a latency fast lane that bypasses the bus). A trustedLink on the SOURCE subsystem additionally licenses its direct edges into the peer without the client-Adapter shim; the published-Portal target requirement stays either way. Also flags components whose dependsOn fan-out suggests a god component.",
18502
18693
  codes: [
18503
18694
  { code: "MUTUAL_SUBSYSTEM_DEPENDENCY", defaultSeverity: "warning", summary: "Subsystems depend on each other without a declared trusted link" },
@@ -18633,6 +18824,7 @@ var init_signature_language_builtins = __esm({
18633
18824
  };
18634
18825
  signatureLanguageBuiltinsRule = {
18635
18826
  name: "signature-language-builtins",
18827
+ judges: "design",
18636
18828
  description: "Contracts must speak the declared target language: builtin types that unambiguously belong to another language family are flagged in method signatures (e.g. `usize` in a TypeScript system, `Promise` in a Rust one). Set targetLanguage on the system (L0) or override per subsystem (L1).",
18637
18829
  codes: [
18638
18830
  { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
@@ -18714,6 +18906,7 @@ var init_narrative_language_constructs = __esm({
18714
18906
  };
18715
18907
  narrativeLanguageConstructsRule = {
18716
18908
  name: "narrative-language-constructs",
18909
+ judges: "design",
18717
18910
  description: "Narratives must describe flows the declared target language can express: steps using constructs the language lacks (e.g. exceptions in Rust/Go, do-while in Python) are flagged with the idiomatic alternative. Set targetLanguage on the system (L0) or override per subsystem (L1).",
18718
18911
  codes: [
18719
18912
  { code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
@@ -18763,6 +18956,7 @@ var init_technology_binding = __esm({
18763
18956
  DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
18764
18957
  technologyBindingRule = {
18765
18958
  name: "technology-binding",
18959
+ judges: "design",
18766
18960
  description: "Only data-layer stereotypes (Adapter/Store/Registry/Index) should bind a technology directly: an L4 that declares `technologies` on logic has no swap seam. No hardcoded vendor lists \u2014 only declared tokens are policed, so the rule never fires on a tree that doesn't opt in.",
18767
18961
  codes: [
18768
18962
  { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
@@ -18827,6 +19021,7 @@ var init_technology_boundaries = __esm({
18827
19021
  init_models();
18828
19022
  technologyRule = {
18829
19023
  name: "technology-boundaries",
19024
+ judges: "design",
18830
19025
  description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface.",
18831
19026
  codes: [
18832
19027
  { code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
@@ -19039,6 +19234,7 @@ var init_naming_conventions = __esm({
19039
19234
  isAllUppercase = (s) => /^[A-Z0-9_]+$/.test(s);
19040
19235
  namingRule = {
19041
19236
  name: "naming-conventions",
19237
+ judges: "design",
19042
19238
  description: "Enforces naming conventions (casing styles or regular expressions) for subsystem, component, interface, type (differentiating entities and value-objects), method, variables/parameters, fields, and constants names/IDs, plus stereotype-specific naming patterns.",
19043
19239
  codes: [
19044
19240
  { code: "NAMING_CONVENTION_VIOLATION", defaultSeverity: "warning", summary: "Item name or ID does not match the configured casing pattern or regex" },
@@ -19205,6 +19401,7 @@ var init_complexity_and_metadata = __esm({
19205
19401
  init_method_cohesion();
19206
19402
  complexityRule = {
19207
19403
  name: "complexity-and-metadata",
19404
+ judges: "design",
19208
19405
  description: "Enforces metadata documentation completeness (checking if descriptions are missing or too short) and structural complexity caps (limits on methods per interface, parameters per method, dependencies per component, and components per subsystem) configured globally or in architectural profiles. A narrative's own length and shape are judged by narrative-complexity.",
19209
19406
  codes: [
19210
19407
  { code: "MISSING_DESCRIPTION", defaultSeverity: "warning", summary: "Required description field is missing or empty" },
@@ -19353,6 +19550,7 @@ var init_narrative_complexity = __esm({
19353
19550
  DEFAULT_MAX_STEPS = 25;
19354
19551
  narrativeComplexityRule = {
19355
19552
  name: "narrative-complexity",
19553
+ judges: "design",
19356
19554
  description: "Judges a narrative on two independent axes: the cognitive band its shape earns, from branching and nesting, and the number of steps it lists. A flat list of registrations is long but linear; a deeply nested guard is short but severe. Each axis warns above its configured level, and reports an error only where a maximum is configured.",
19357
19555
  codes: [
19358
19556
  { code: "NARRATIVE_COMPLEXITY", defaultSeverity: "warning", summary: "Narrative's cognitive band is above the configured level (default: above moderate)" },
@@ -19473,6 +19671,7 @@ var init_naming_discipline = __esm({
19473
19671
  MIN_CONCEPT_LENGTH = 4;
19474
19672
  namingDisciplineRule = {
19475
19673
  name: "naming-discipline",
19674
+ judges: "design",
19476
19675
  description: "A name says what a thing is and never repeats itself: a component's head noun names its own block, generic role words say nothing, a method does not repeat its component's head noun, and a component with one method is not merely that method's name. Adapters and Portals are exempt from the repetition check, because a forwarder's method mirrors the command or route it exposes.",
19477
19676
  codes: [
19478
19677
  { code: "MISLEADING_BLOCK_WORD", defaultSeverity: "warning", summary: "Component's head noun names a building block that is not its componentType" },
@@ -21736,8 +21935,25 @@ var init_pack_impact2 = __esm({
21736
21935
  });
21737
21936
 
21738
21937
  // src/core/rules/gate-identity.ts
21938
+ function ruleIdentities(rules) {
21939
+ return byKey(
21940
+ rules.map((r) => ({
21941
+ name: r.name,
21942
+ codes: byKey(r.codes.map((c) => ({ code: c.code, severity: c.defaultSeverity })), (c) => c.code)
21943
+ })),
21944
+ (r) => r.name
21945
+ );
21946
+ }
21947
+ function codeJudgedCodes(builtinRules) {
21948
+ return new Set(builtinRules.filter(judgesCode).flatMap((r) => r.codes.map((c) => c.code)));
21949
+ }
21950
+ function designRulesConfig(rules, codeCodes) {
21951
+ if (!rules) return null;
21952
+ const { conformance: _codeTuning, sddRuleSeverity, ...rest } = rules;
21953
+ const severities = Object.fromEntries(Object.entries(sddRuleSeverity ?? {}).filter(([code]) => !codeCodes.has(code)));
21954
+ return { ...rest, ...Object.keys(severities).length > 0 ? { sddRuleSeverity: severities } : {} };
21955
+ }
21739
21956
  function doctrineIdentity(doctrine2, builtinRules, gate) {
21740
- const byKey = (items, key) => [...items].sort((a, b) => compareOrdinal(key(a), key(b)));
21741
21957
  return {
21742
21958
  /**
21743
21959
  * The BUILTIN rule set, by identity rather than by wairon version.
@@ -21754,20 +21970,23 @@ function doctrineIdentity(doctrine2, builtinRules, gate) {
21754
21970
  * in the wairon version would catch it at the cost of churning every lock on
21755
21971
  * every release — the trade this projection deliberately declines.
21756
21972
  */
21757
- builtinRules: byKey(
21758
- builtinRules.map((r) => ({
21759
- name: r.name,
21760
- codes: byKey(r.codes.map((c) => ({ code: c.code, severity: c.defaultSeverity })), (c) => c.code)
21761
- })),
21762
- (r) => r.name
21763
- ),
21973
+ //
21974
+ // Only the rules that judge DESIGN: a code analyzer that gains a code or a
21975
+ // stricter check must never stale an approval (analyzer-upgrade-keeps-approval).
21976
+ builtinRules: ruleIdentities(builtinRules.filter((r) => !judgesCode(r))),
21764
21977
  /**
21765
- * Which profile GOVERNS, and the project's own rule tuning. Both decide
21766
- * verdicts — a projectType switch changes the doctrine family outright, and
21767
- * `rules` carries severity overrides, complexity caps, and designDepth.
21978
+ * Which profile GOVERNS, and the project's own design rule tuning. Both
21979
+ * decide verdicts — a projectType switch changes the doctrine family
21980
+ * outright, and `rules` carries design severity overrides, complexity caps
21981
+ * and designDepth. Its code-conformance tuning is the analyzer's.
21768
21982
  */
21769
21983
  projectType: gate.projectType ?? null,
21770
- rulesConfig: gate.rules ?? null,
21984
+ rulesConfig: designRulesConfig(gate.rules, codeJudgedCodes(builtinRules)),
21985
+ /**
21986
+ * What the project requires of the projects it contains (stage 5): a
21987
+ * changed requirement stales the requiring project's lock.
21988
+ */
21989
+ composition: gate.composition ?? null,
21771
21990
  packs: byKey(
21772
21991
  doctrine2.packs.map((p) => ({ name: p.name, version: p.version ?? null, scope: p.scope })),
21773
21992
  (p) => `${p.name}@${p.version ?? ""}#${p.scope}`
@@ -21788,22 +22007,35 @@ function doctrineIdentity(doctrine2, builtinRules, gate) {
21788
22007
  )
21789
22008
  };
21790
22009
  }
21791
- function computeGateIdentity(content, doctrine2, builtinRules, inputs, gate) {
22010
+ function computeGateIdentity(content, doctrine2, builtinRules, inputs, gate, members) {
21792
22011
  const payload = {
21793
22012
  content: content.digest,
21794
22013
  doctrine: doctrineIdentity(doctrine2, builtinRules, gate),
21795
- inputs: [...inputs].sort()
22014
+ inputs: [...inputs].sort(compareOrdinal),
22015
+ members: byKey(Object.entries(members), ([alias]) => alias).map(([alias, subject]) => ({ alias, subject }))
21796
22016
  };
21797
22017
  const digest2 = crypto3.createHash("sha256").update(canonicalize(payload)).digest("hex");
21798
22018
  return { algorithm: GATE_ALGORITHM, digest: digest2 };
21799
22019
  }
21800
- var crypto3, GATE_ALGORITHM;
22020
+ function analyzerDigest(builtinRules, gate) {
22021
+ const codeCodes = codeJudgedCodes(builtinRules);
22022
+ const overrides = Object.entries(gate.rules?.sddRuleSeverity ?? {}).filter(([code]) => codeCodes.has(code));
22023
+ const payload = {
22024
+ rules: ruleIdentities(builtinRules.filter(judgesCode)),
22025
+ conformance: gate.rules?.conformance ?? null,
22026
+ severities: byKey(overrides, ([code]) => code).map(([code, severity]) => ({ code, severity }))
22027
+ };
22028
+ return crypto3.createHash("sha256").update(canonicalize(payload)).digest("hex");
22029
+ }
22030
+ var crypto3, GATE_ALGORITHM, byKey;
21801
22031
  var init_gate_identity = __esm({
21802
22032
  "src/core/rules/gate-identity.ts"() {
21803
22033
  "use strict";
21804
22034
  crypto3 = __toESM(require("crypto"));
21805
22035
  init_canonical_json();
21806
- GATE_ALGORITHM = "sha256+content+doctrine+inputs";
22036
+ init_types();
22037
+ GATE_ALGORITHM = "sha256+content+doctrine+inputs+members";
22038
+ byKey = (items, key) => [...items].sort((a, b) => compareOrdinal(key(a), key(b)));
21807
22039
  }
21808
22040
  });
21809
22041
 
@@ -21934,6 +22166,9 @@ function memberOwning(owners, memberKeys, key) {
21934
22166
  return best;
21935
22167
  }
21936
22168
  function validateProject(rulesOrOptions, projectType = "backend") {
22169
+ return runOwnersGate(rulesOrOptions, projectType).result;
22170
+ }
22171
+ function runOwnersGate(rulesOrOptions, projectType = "backend") {
21937
22172
  let rules = rulesOrOptions;
21938
22173
  let scopeSubsystem;
21939
22174
  let extensions;
@@ -21984,7 +22219,7 @@ function validateProject(rulesOrOptions, projectType = "backend") {
21984
22219
  if (!system) {
21985
22220
  const pending = extensions.errors.length > 0 || extensions.selectionFailures.length > 0 ? " Extension packs were not checked yet either, and at least one problem is already known there \u2014 re-run once the L0 exists." : " Extension-pack configuration is not checked until the L0 exists.";
21986
22221
  issues.push(issue("error", "MISSING_SYSTEM_SPEC", `L0 System specification (.system.yaml) is missing.${pending}`));
21987
- return { valid: false, issues };
22222
+ return { result: { valid: false, issues }, codeModel };
21988
22223
  }
21989
22224
  if (scopeSubsystem) {
21990
22225
  const scopeMatches = subsystems.some(
@@ -22002,7 +22237,7 @@ function validateProject(rulesOrOptions, projectType = "backend") {
22002
22237
  scopeSubsystem
22003
22238
  )
22004
22239
  );
22005
- return { valid: false, issues };
22240
+ return { result: { valid: false, issues }, codeModel };
22006
22241
  }
22007
22242
  }
22008
22243
  for (const err of extensions.errors) {
@@ -22064,9 +22299,12 @@ function validateProject(rulesOrOptions, projectType = "backend") {
22064
22299
  const count = pinnedExternals.length;
22065
22300
  const hint = count > 0 ? `${count} external${count === 1 ? " was" : "s were"} judged against ${count === 1 ? "its pin" : "their pins"} alone; \`wairon validate --family\` composes ${count === 1 ? "it" : "them"} against the live producer${count === 1 ? "" : "s"}.` : void 0;
22066
22301
  return {
22067
- valid: issues.every((i) => i.severity !== "error"),
22068
- issues,
22069
- ...hint ? { hint } : {}
22302
+ result: {
22303
+ valid: issues.every((i) => i.severity !== "error"),
22304
+ issues,
22305
+ ...hint ? { hint } : {}
22306
+ },
22307
+ codeModel
22070
22308
  };
22071
22309
  } finally {
22072
22310
  statusBearing.forEach((s, i) => {
@@ -22099,7 +22337,33 @@ function settledStatusBearing(loaded) {
22099
22337
  return out;
22100
22338
  }
22101
22339
  function validateAsComplete(options) {
22102
- return validateProject({ ...options ?? {}, treatAllAsComplete: true });
22340
+ const { result, codeModel } = runOwnersGate({ ...options ?? {}, treatAllAsComplete: true });
22341
+ const sequence = ruleSequence();
22342
+ const doctrineDigest = analyzerDigest(sequence, { rules: options?.rules ?? projectRules() });
22343
+ return { ...result, analysis: codeAnalysisOf(result, sequence, codeModel, doctrineDigest) };
22344
+ }
22345
+ function projectRules() {
22346
+ try {
22347
+ return loadProjectConfig()?.rules;
22348
+ } catch {
22349
+ return void 0;
22350
+ }
22351
+ }
22352
+ function weakestGrade(codeModel) {
22353
+ const grades = codeModel.files.filter((f) => f.status === "analyzed" && f.analysisGrade).map((f) => GRADE_ORDER.indexOf(f.analysisGrade));
22354
+ return grades.length ? GRADE_ORDER[Math.min(...grades)] : "none";
22355
+ }
22356
+ function codeAnalysisOf(result, sequence, codeModel, doctrineDigest) {
22357
+ const codes = [...new Set(sequence.filter(judgesCode).flatMap((r) => r.codes.map((c) => c.code)))].sort();
22358
+ const codeSet = new Set(codes);
22359
+ const count = (severity) => result.issues.filter((i) => codeSet.has(i.code) && i.severity === severity).length;
22360
+ return {
22361
+ analyzer: { validatorVersion: WAIRON_VERSION, doctrineDigest, grade: weakestGrade(codeModel) },
22362
+ codes,
22363
+ errors: count("error"),
22364
+ warnings: count("warning"),
22365
+ notices: count("notice")
22366
+ };
22103
22367
  }
22104
22368
  function validateFamily(options) {
22105
22369
  return run(options);
@@ -22117,19 +22381,37 @@ function builtinProjectKinds() {
22117
22381
  return [...PROJECT_KINDS];
22118
22382
  }
22119
22383
  function computeGateStateId() {
22120
- const content = computeStateId();
22384
+ scanAllSpecs({ recursive: true });
22385
+ const content = computeOwnStateId();
22121
22386
  const extensions = loadProjectExtensions();
22122
22387
  let gate = {};
22123
22388
  try {
22124
22389
  const config = loadProjectConfig();
22125
- if (config) gate = { projectType: config.projectType, rules: config.rules };
22390
+ if (config) gate = { projectType: config.projectType, rules: config.rules, composition: config.composition ?? null };
22126
22391
  } catch {
22127
22392
  }
22128
22393
  const inputs = consumedContractInputs();
22394
+ const members = directMemberSubjects();
22129
22395
  registerBuiltinRules();
22130
- return computeGateIdentity(content, extensions, ruleSequence(), inputs, gate);
22396
+ return computeGateIdentity(content, extensions, ruleSequence(), inputs, gate, members);
22131
22397
  }
22132
- var path19;
22398
+ function directMemberSubjects() {
22399
+ const family = graph();
22400
+ const subjects = {};
22401
+ for (const node of family.nodes) {
22402
+ if (node.parent !== "" || node.mountAlias === void 0) continue;
22403
+ const record2 = approvalRecord(node.directory);
22404
+ subjects[node.mountAlias] = record2 ? `${record2.stateId.algorithm}:${record2.stateId.digest}` : NEVER_SUBJECT;
22405
+ }
22406
+ for (const problem of family.problems) {
22407
+ if (problem.kind === "member-absent" && problem.projects[0] === "" && problem.id) subjects[problem.id] = NEVER_SUBJECT;
22408
+ }
22409
+ return subjects;
22410
+ }
22411
+ function familyApprovals(depth) {
22412
+ return projectApprovals(depth);
22413
+ }
22414
+ var path19, GRADE_ORDER, NEVER_SUBJECT;
22133
22415
  var init_validation = __esm({
22134
22416
  "src/core/validation.ts"() {
22135
22417
  "use strict";
@@ -22143,9 +22425,12 @@ var init_validation = __esm({
22143
22425
  init_pack_impact2();
22144
22426
  init_gate_identity();
22145
22427
  init_types();
22428
+ init_defaults();
22146
22429
  init_source_analysis();
22147
22430
  init_fs();
22148
22431
  path19 = __toESM(require("path"));
22432
+ GRADE_ORDER = ["generic", "pattern", "exact"];
22433
+ NEVER_SUBJECT = "never";
22149
22434
  }
22150
22435
  });
22151
22436
 
@@ -23085,10 +23370,10 @@ function settle(owner, level, candidates, problems) {
23085
23370
  detail: `"${name}" is bound to ${targets.length} different targets (${targets.join(", ")}) by ${explicit.length ? "explicit entries" : "wildcard re-exports"}; it is left out of the table`
23086
23371
  });
23087
23372
  }
23088
- const byKey = /* @__PURE__ */ new Map();
23089
- for (const e of entries) byKey.set(nameKey(e.publicName), [...byKey.get(nameKey(e.publicName)) ?? [], e]);
23373
+ const byKey2 = /* @__PURE__ */ new Map();
23374
+ for (const e of entries) byKey2.set(nameKey(e.publicName), [...byKey2.get(nameKey(e.publicName)) ?? [], e]);
23090
23375
  const clashing = /* @__PURE__ */ new Set();
23091
- for (const group of byKey.values()) {
23376
+ for (const group of byKey2.values()) {
23092
23377
  const targets = [...new Set(group.map((e) => exportTargetKey(e)))];
23093
23378
  if (group.length < 2 || targets.length < 2) continue;
23094
23379
  group.forEach((e) => clashing.add(e));
@@ -23564,9 +23849,6 @@ function inspectChainedRoots(rootDir = getProjectRoot()) {
23564
23849
  walk2(root, "", /* @__PURE__ */ new Set([chainDirKey(root)]), 0);
23565
23850
  return inspection;
23566
23851
  }
23567
- function listChainedRoots(rootDir = getProjectRoot()) {
23568
- return inspectChainedRoots(rootDir).roots;
23569
- }
23570
23852
  function mapSpecReferences(kind, spec, map) {
23571
23853
  switch (kind) {
23572
23854
  case "subsystem": {
@@ -23824,10 +24106,10 @@ function keyedSystem(system, key, subsystems) {
23824
24106
  };
23825
24107
  }
23826
24108
  function resolveFamilyTables(raws) {
23827
- const byKey = new Map(raws.map((r) => [r.record.namespace, r]));
24109
+ const byKey2 = new Map(raws.map((r) => [r.record.namespace, r]));
23828
24110
  const done = /* @__PURE__ */ new Set();
23829
24111
  const resolving = /* @__PURE__ */ new Set();
23830
- const resolve39 = (raw) => {
24112
+ const resolve40 = (raw) => {
23831
24113
  const key = raw.record.namespace;
23832
24114
  if (done.has(key)) return raw.record.exports;
23833
24115
  if (resolving.has(key)) return null;
@@ -23841,8 +24123,8 @@ function resolveFamilyTables(raws) {
23841
24123
  const source = e.from ?? e.subsystem;
23842
24124
  const producerKey = source !== void 0 ? raw.record.aliases.get(source) : void 0;
23843
24125
  if (producerKey === void 0 || producerTables.has(producerKey)) continue;
23844
- const producer = byKey.get(producerKey);
23845
- producerTables.set(producerKey, producer ? resolve39(producer) : null);
24126
+ const producer = byKey2.get(producerKey);
24127
+ producerTables.set(producerKey, producer ? resolve40(producer) : null);
23846
24128
  audienceOf.set(producerKey, "project");
23847
24129
  }
23848
24130
  raw.record.subsystemExports = subsystemTables;
@@ -23858,7 +24140,7 @@ function resolveFamilyTables(raws) {
23858
24140
  resolving.delete(key);
23859
24141
  }
23860
24142
  };
23861
- raws.forEach((raw) => resolve39(raw));
24143
+ raws.forEach((raw) => resolve40(raw));
23862
24144
  }
23863
24145
  function bindAuthored(raw, raws, readables, authored) {
23864
24146
  const key = raw.record.namespace;
@@ -24467,13 +24749,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
24467
24749
  const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
24468
24750
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
24469
24751
  const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
24470
- const say = (path83, why) => {
24471
- out.push(`${path83} \u2014 ${why}`);
24752
+ const say = (path82, why) => {
24753
+ out.push(`${path82} \u2014 ${why}`);
24472
24754
  };
24473
- const walkValue = (d, m, s, path83, field) => {
24755
+ const walkValue = (d, m, s, path82, field) => {
24474
24756
  if (Array.isArray(d)) {
24475
24757
  if (!Array.isArray(m)) {
24476
- say(path83, "the level does not have this field, so the write dropped it");
24758
+ say(path82, "the level does not have this field, so the write dropped it");
24477
24759
  return;
24478
24760
  }
24479
24761
  if (field === "narrative" && d.some(carriesEditMarker)) return;
@@ -24481,32 +24763,32 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
24481
24763
  if (carriesEditMarker(element)) continue;
24482
24764
  const key = reportIdentityOf(field, element);
24483
24765
  if (key === null) {
24484
- if (!same(m, d)) say(path83, "the level does not keep this value, so the write dropped it");
24485
- else if (same(s, d)) say(path83, "the stored spec already held this value");
24766
+ if (!same(m, d)) say(path82, "the level does not keep this value, so the write dropped it");
24767
+ else if (same(s, d)) say(path82, "the stored spec already held this value");
24486
24768
  return;
24487
24769
  }
24488
24770
  const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
24489
24771
  const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
24490
24772
  if (mMatch === void 0) {
24491
- say(at(path83, key), "the write did not store this element");
24773
+ say(at(path82, key), "the write did not store this element");
24492
24774
  continue;
24493
24775
  }
24494
- walk2(element, mMatch, sMatch, at(path83, key), field);
24776
+ walk2(element, mMatch, sMatch, at(path82, key), field);
24495
24777
  }
24496
24778
  return;
24497
24779
  }
24498
24780
  if (isPlain(d)) {
24499
24781
  if (!isPlain(m)) {
24500
- say(path83, "the level does not have this field, so the write dropped it");
24782
+ say(path82, "the level does not have this field, so the write dropped it");
24501
24783
  return;
24502
24784
  }
24503
- walk2(d, m, s, path83, field);
24785
+ walk2(d, m, s, path82, field);
24504
24786
  return;
24505
24787
  }
24506
- if (!same(m, d)) say(path83, "the write did not store it \u2014 the level's schema does not keep this value");
24507
- else if (same(s, d)) say(path83, "the stored spec already held this value");
24788
+ if (!same(m, d)) say(path82, "the write did not store it \u2014 the level's schema does not keep this value");
24789
+ else if (same(s, d)) say(path82, "the stored spec already held this value");
24508
24790
  };
24509
- function walk2(d, m, s, path83, container) {
24791
+ function walk2(d, m, s, path82, container) {
24510
24792
  for (const [key, value] of Object.entries(d)) {
24511
24793
  if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
24512
24794
  if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
@@ -24516,13 +24798,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
24516
24798
  if (typeof name !== "string") continue;
24517
24799
  const had = isPlain(s) && s[name] !== void 0;
24518
24800
  const still = isPlain(m) && m[name] !== void 0;
24519
- if (!had) say(at(path83, name), "unset named a field the stored spec did not have");
24520
- else if (still) say(at(path83, name), "unset did not remove it");
24801
+ if (!had) say(at(path82, name), "unset named a field the stored spec did not have");
24802
+ else if (still) say(at(path82, name), "unset did not remove it");
24521
24803
  }
24522
24804
  continue;
24523
24805
  }
24524
24806
  if (value === void 0 || value === null) continue;
24525
- const here = at(path83, key);
24807
+ const here = at(path82, key);
24526
24808
  const mValue = isPlain(m) ? m[key] : void 0;
24527
24809
  const sValue = isPlain(s) ? s[key] : void 0;
24528
24810
  if (mValue === void 0) {
@@ -24822,9 +25104,7 @@ function consumedSurfaceInputsAt(rootDir) {
24822
25104
  return keys;
24823
25105
  }
24824
25106
  function consumedContractInputs() {
24825
- const root = getProjectRoot();
24826
- const roots = [root, ...listChainedRoots(root).map((rel2) => path21.join(root, rel2))];
24827
- return roots.flatMap(consumedSurfaceInputsAt);
25107
+ return consumedSurfaceInputsAt(getProjectRoot());
24828
25108
  }
24829
25109
  function readLockState(current2) {
24830
25110
  const record2 = readLockRecord();
@@ -25773,10 +26053,10 @@ var init_specs2 = __esm({
25773
26053
  return this.relativizeSpec("interface", spec);
25774
26054
  }
25775
26055
  prepareImplementationForWrite(spec, carry = true) {
25776
- const relative19 = this.relativizeSpec("implementation", spec, carry);
26056
+ const relative18 = this.relativizeSpec("implementation", spec, carry);
25777
26057
  const member = this.getSubprojectPrefix(spec.id);
25778
26058
  const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
25779
- return memberRoot ? childRelativeFilePaths(relative19, this.rootDir, memberRoot) : relative19;
26059
+ return memberRoot ? childRelativeFilePaths(relative18, this.rootDir, memberRoot) : relative18;
25780
26060
  }
25781
26061
  prepareTypeForWrite(spec) {
25782
26062
  return this.relativizeSpec("type", spec);
@@ -27932,13 +28212,13 @@ function scaffoldRequirements(requirements, installed) {
27932
28212
  }
27933
28213
  return { adopted, unadopted, ...profiles.length === 1 ? { projectType: profiles[0] } : {} };
27934
28214
  }
27935
- function createMember(alias, path83, description) {
27936
- if (!EXTERNAL_ALIAS_RE.test(alias) || typeof path83 !== "string" || path83.trim() === "") {
28215
+ function createMember(alias, path82, description) {
28216
+ if (!EXTERNAL_ALIAS_RE.test(alias) || typeof path82 !== "string" || path82.trim() === "") {
27937
28217
  throw new WaironError(
27938
28218
  `an alias and a path are required to create a member: the alias must fit [a-z0-9-_]+ (got "${alias}") and the path must not be empty.`
27939
28219
  );
27940
28220
  }
27941
- const relPath = toPosixPath(path83);
28221
+ const relPath = toPosixPath(path82);
27942
28222
  const memberDir = assertContainedProjectPath(getProjectRoot(), relPath);
27943
28223
  const requirements = requiredPolicies(projectConfigRepository.load() ?? {});
27944
28224
  const parentName = loadSystemSpec()?.name;
@@ -29280,6 +29560,16 @@ function buildVariantGuidance(comps, allComponents, variantsById) {
29280
29560
  return composeVariantGuidance(comps, allComponents, variantsById);
29281
29561
  }
29282
29562
  function resolveAgentTopology() {
29563
+ return resolveLayer(true);
29564
+ }
29565
+ function memberAgentIds(alias, memberDir) {
29566
+ try {
29567
+ return runWithProjectRoot(memberDir, () => resolveLayer(false).map((r) => `${alias}::${r.id}`));
29568
+ } catch {
29569
+ return [];
29570
+ }
29571
+ }
29572
+ function resolveLayer(delegate) {
29283
29573
  projectFilesCache.clear();
29284
29574
  const system = loadSystemSpec();
29285
29575
  if (!system) return [];
@@ -29335,6 +29625,8 @@ function resolveAgentTopology() {
29335
29625
  writePaths: [mountSpecPath],
29336
29626
  tags: ["owner", "subproject", "delegate", "sdd"],
29337
29627
  dependencies: [],
29628
+ // The member's own agents BY REFERENCE, never copied into this layer.
29629
+ ...delegate ? { delegatesTo: memberAgentIds(sub.id, path28.resolve(getProjectRoot(), sub.projectPath)) } : {},
29338
29630
  status: "active",
29339
29631
  targets: activeTargets,
29340
29632
  createdAt: sub.createdAt,
@@ -29470,6 +29762,11 @@ function resolveAgentTopology() {
29470
29762
  return agents;
29471
29763
  }
29472
29764
  function composeAgentBrief(agentId) {
29765
+ const hop = memberHop(agentId);
29766
+ if (hop) {
29767
+ const brief = runWithProjectRoot(hop.directory, () => composeAgentBrief(hop.rest));
29768
+ return throughMount(brief, hop.alias, hop.relative);
29769
+ }
29473
29770
  const records = resolveAgentTopology();
29474
29771
  const record2 = records.find((r) => r.id === agentId);
29475
29772
  if (!record2) {
@@ -29514,6 +29811,25 @@ ${guidance.trim()}
29514
29811
  budget
29515
29812
  };
29516
29813
  }
29814
+ function memberHop(agentId) {
29815
+ const at = agentId.indexOf("::");
29816
+ if (at < 0) return null;
29817
+ const alias = agentId.slice(0, at);
29818
+ const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
29819
+ if (!member) return null;
29820
+ const relative18 = path28.relative(getProjectRoot(), member.directory).split(path28.sep).join("/");
29821
+ return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative18 };
29822
+ }
29823
+ function throughMount(brief, alias, relative18) {
29824
+ const under = (p) => path28.posix.join(relative18, p);
29825
+ return {
29826
+ ...brief,
29827
+ agentId: `${alias}::${brief.agentId}`,
29828
+ ownedPaths: brief.ownedPaths.map(under),
29829
+ ...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
29830
+ root: brief.root ? under(brief.root) : relative18
29831
+ };
29832
+ }
29517
29833
  function loadRegistry() {
29518
29834
  assertProjectInitialized();
29519
29835
  if (!pathExists(AI_PATHS.specsSystem())) return createEmptyRegistry();
@@ -32846,7 +33162,7 @@ var require_dist = __commonJS({
32846
33162
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
32847
33163
  }
32848
33164
  var fs60 = __toESM2(require("fs"));
32849
- var path83 = __toESM2(require("path"));
33165
+ var path82 = __toESM2(require("path"));
32850
33166
  var import_fflate = require_node();
32851
33167
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
32852
33168
  function listEntries(archive) {
@@ -32884,8 +33200,8 @@ var require_dist = __commonJS({
32884
33200
  }
32885
33201
  function writeTree(destDir, files) {
32886
33202
  for (const file of files) {
32887
- const absolute = path83.join(destDir, file.path);
32888
- fs60.mkdirSync(path83.dirname(absolute), { recursive: true });
33203
+ const absolute = path82.join(destDir, file.path);
33204
+ fs60.mkdirSync(path82.dirname(absolute), { recursive: true });
32889
33205
  fs60.writeFileSync(absolute, file.contents);
32890
33206
  }
32891
33207
  }
@@ -32898,10 +33214,10 @@ var require_dist = __commonJS({
32898
33214
  for (const entry of fs60.readdirSync(current2, { withFileTypes: true })) {
32899
33215
  if (entry.isDirectory()) {
32900
33216
  if (SKIP_DIRS.has(entry.name)) continue;
32901
- walkPackDir(root, path83.join(current2, entry.name), out);
33217
+ walkPackDir(root, path82.join(current2, entry.name), out);
32902
33218
  } else if (entry.isFile()) {
32903
- const absolute = path83.join(current2, entry.name);
32904
- const relative22 = path83.relative(root, absolute).split(path83.sep).join("/");
33219
+ const absolute = path82.join(current2, entry.name);
33220
+ const relative22 = path82.relative(root, absolute).split(path82.sep).join("/");
32905
33221
  out.push({ path: relative22, contents: fs60.readFileSync(absolute) });
32906
33222
  }
32907
33223
  }
@@ -33102,8 +33418,8 @@ var require_dist = __commonJS({
33102
33418
  return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
33103
33419
  }
33104
33420
  function normalizeRootPath(relativePath) {
33105
- const posix2 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
33106
- return posix2 === "" || posix2 === "." ? "." : posix2;
33421
+ const posix3 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
33422
+ return posix3 === "" || posix3 === "." ? "." : posix3;
33107
33423
  }
33108
33424
  function slugify2(projectName) {
33109
33425
  const slug2 = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -33209,19 +33525,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
33209
33525
  const backupDir = path33.join(root, BACKUP_DIR, timestampSlug());
33210
33526
  let backedUp = false;
33211
33527
  for (const rootRel of roots) {
33212
- const relative19 = rootRel === "." ? "" : rootRel;
33213
- const stagedSpecDir = path33.join(stagingDir, relative19, ".wai");
33528
+ const relative18 = rootRel === "." ? "" : rootRel;
33529
+ const stagedSpecDir = path33.join(stagingDir, relative18, ".wai");
33214
33530
  if (!fs19.existsSync(stagedSpecDir)) continue;
33215
- const liveSpecDir = liveSpecDirFor(path33.join(root, relative19));
33531
+ const liveSpecDir = liveSpecDirFor(path33.join(root, relative18));
33216
33532
  if (backupExisting && liveSpecDir && fs19.existsSync(liveSpecDir)) {
33217
- const backupTarget = path33.join(backupDir, relative19, path33.basename(liveSpecDir));
33533
+ const backupTarget = path33.join(backupDir, relative18, path33.basename(liveSpecDir));
33218
33534
  fs19.mkdirSync(path33.dirname(backupTarget), { recursive: true });
33219
33535
  fs19.renameSync(liveSpecDir, backupTarget);
33220
33536
  backedUp = true;
33221
33537
  } else if (liveSpecDir && fs19.existsSync(liveSpecDir)) {
33222
33538
  fs19.rmSync(liveSpecDir, { recursive: true, force: true });
33223
33539
  }
33224
- const target = path33.join(root, relative19, ".wai");
33540
+ const target = path33.join(root, relative18, ".wai");
33225
33541
  fs19.mkdirSync(path33.dirname(target), { recursive: true });
33226
33542
  if (fs19.existsSync(target)) fs19.rmSync(target, { recursive: true, force: true });
33227
33543
  fs19.renameSync(stagedSpecDir, target);
@@ -33486,6 +33802,12 @@ function describeApprover(who) {
33486
33802
  const label4 = who.name ? `${who.name} (${who.id})` : who.id;
33487
33803
  return who.source === "hosted" ? `${label4} [authenticated]` : label4;
33488
33804
  }
33805
+ function designOnly(result) {
33806
+ if (!result.analysis) return result;
33807
+ const codeCodes = new Set(result.analysis.codes ?? []);
33808
+ const issues = result.issues.filter((i) => !codeCodes.has(i.code));
33809
+ return { ...result, issues, valid: issues.every((i) => i.severity !== "error") };
33810
+ }
33489
33811
  var init_lock = __esm({
33490
33812
  "src/models/lock.ts"() {
33491
33813
  "use strict";
@@ -33557,48 +33879,22 @@ function settledSpecPaths(root = getProjectRoot()) {
33557
33879
  const diff = diffAgainstApproval(root);
33558
33880
  return diff ? new Set(diff.unchangedPaths) : null;
33559
33881
  }
33560
- function pinOf(stateId) {
33561
- return `${stateId.algorithm}:${stateId.digest}`;
33562
- }
33563
- function currentChildPins(mounts, root = getProjectRoot()) {
33564
- const pins = {};
33565
- for (const mount of mounts) {
33566
- if (!mount.projectPath || mount.id.includes("::")) continue;
33567
- const child = readLockRecordAt(path34.resolve(root, mount.projectPath));
33568
- if (child) pins[mount.id] = pinOf(child.stateId);
33569
- }
33570
- return pins;
33571
- }
33572
- function movedChildren(mounts, root = getProjectRoot()) {
33573
- const pinned = approvalRecord(root)?.children;
33574
- if (!pinned) return [];
33575
- const now = currentChildPins(mounts, root);
33576
- const moved = [];
33577
- for (const [id, was] of Object.entries(pinned)) {
33578
- const current2 = now[id] ?? null;
33579
- if (current2 !== was) moved.push({ id, pinned: was, now: current2 });
33580
- }
33581
- return moved;
33582
- }
33583
- function memberMounts(root = getProjectRoot()) {
33584
- return graph().nodes.filter((n) => n.parent === "" && n.mountAlias !== void 0).map((n) => ({ id: n.mountAlias, projectPath: path34.relative(root, n.directory).split(path34.sep).join("/") }));
33585
- }
33586
- function approvalVerdict() {
33882
+ function approvalVerdict(approvals) {
33587
33883
  const quiet = { text: "", drifted: false };
33588
33884
  try {
33589
33885
  const approval = approvalRecord();
33590
33886
  if (!approval) return quiet;
33591
33887
  const by = describeApprover(approval.lockedBy);
33592
- const moved = movedChildren(memberMounts());
33593
- const childNote = moved.length ? `
33594
- ${moved.length} chained child project(s) moved since approval: ${moved.map((m) => m.id).join(", ")}.` : "";
33888
+ const notes = memberNotes(approvals ?? []);
33889
+ const pinDrift = notes.drift;
33890
+ const childNote = notes.text;
33595
33891
  const diff = diffAgainstApproval();
33596
33892
  if (!diff) {
33597
33893
  return {
33598
33894
  text: `
33599
33895
  Approved: ${approval.lockedAt} by ${by} \u2014 this lock predates per-spec approval, so drift is only visible at whole-tree level. Re-lock to record it.${childNote}
33600
33896
  `,
33601
- drifted: moved.length > 0
33897
+ drifted: pinDrift
33602
33898
  };
33603
33899
  }
33604
33900
  if (diffSize(diff) === 0) {
@@ -33606,7 +33902,7 @@ Approved: ${approval.lockedAt} by ${by} \u2014 this lock predates per-spec appro
33606
33902
  text: `
33607
33903
  Approved: ${approval.lockedAt} by ${by} \u2014 no spec has changed since.${childNote}
33608
33904
  `,
33609
- drifted: moved.length > 0
33905
+ drifted: pinDrift
33610
33906
  };
33611
33907
  }
33612
33908
  const parts = [];
@@ -33629,7 +33925,28 @@ ${headline} \u2014 approved ${approval.lockedAt} by ${by}:
33629
33925
  return quiet;
33630
33926
  }
33631
33927
  }
33632
- var crypto4, path34;
33928
+ function memberNotes(approvals) {
33929
+ const own = approvals.find((a) => a.key === "");
33930
+ const direct = approvals.filter((a) => a.parent === "");
33931
+ const moved = direct.filter((a) => a.pinned === "moved");
33932
+ const unapproved = direct.filter((a) => a.state !== "approved");
33933
+ const lines = [];
33934
+ if (own?.upgraded) {
33935
+ lines.push(`This approval was taken under an earlier gate identity \u2014 ${GATE_UPGRADE_NOTE}. Re-lock once.`);
33936
+ }
33937
+ if (moved.length) {
33938
+ lines.push(`${moved.length} member pin(s) moved since approval: ${moved.map((a) => a.alias ?? a.key).join(", ")} \u2014 re-lock to approve them.`);
33939
+ }
33940
+ if (unapproved.length) {
33941
+ lines.push(`Member(s) not approved at their own root: ${unapproved.map((a) => `${a.alias ?? a.key} (${a.state}${a.upgraded ? ", re-lock once" : ""})`).join(", ")}.`);
33942
+ }
33943
+ return {
33944
+ text: lines.map((l) => `
33945
+ ${l}`).join(""),
33946
+ drift: !!own?.upgraded || moved.length > 0
33947
+ };
33948
+ }
33949
+ var crypto4, path34, GATE_UPGRADE_NOTE;
33633
33950
  var init_approval = __esm({
33634
33951
  "src/core/approval.ts"() {
33635
33952
  "use strict";
@@ -33639,6 +33956,7 @@ var init_approval = __esm({
33639
33956
  init_specs2();
33640
33957
  init_lockfile();
33641
33958
  init_lock();
33959
+ GATE_UPGRADE_NOTE = "the gate identity gained inputs in stage 5 (members' composition subjects, `composition`; code conformance moved beside the claim)";
33642
33960
  }
33643
33961
  });
33644
33962
 
@@ -34002,6 +34320,11 @@ var init_context = __esm({
34002
34320
  });
34003
34321
 
34004
34322
  // src/core/status.ts
34323
+ function approvalTag(entry) {
34324
+ if (!entry) return "";
34325
+ const state = entry.upgraded ? `${entry.state}, re-lock once` : entry.state;
34326
+ return entry.pinned ? ` [${state} \xB7 pin ${entry.pinned}]` : ` [${state}]`;
34327
+ }
34005
34328
  function fillDecor(decor) {
34006
34329
  return {
34007
34330
  structure: decor?.structure ?? asWritten,
@@ -34161,12 +34484,21 @@ function getStatusReport(options = {}, decor) {
34161
34484
  const isLast = ownSubs.length + j === total - 1;
34162
34485
  const form = member.mountForm === "mount" ? mark.draft(" [mount form]") : "";
34163
34486
  const label4 = `[Project] ${member.mountAlias ?? member.namespace} (${member.id ?? "no id"})`;
34164
- output += `${mark.structure(indent + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "))}${mark.layer("project", label4)}${form}
34487
+ const approval = approvalTag(options.approvals?.find((a) => a.key === member.namespace));
34488
+ output += `${mark.structure(indent + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "))}${mark.layer("project", label4)}${form}${approval}
34165
34489
  `;
34166
34490
  renderProject(member.namespace, indent + (isLast ? " " : "\u2502 "));
34167
34491
  });
34168
34492
  };
34169
34493
  renderProject("", "");
34494
+ for (const absent of (options.approvals ?? []).filter((a) => a.key !== "" && !family.nodes.some((n) => n.namespace === a.key))) {
34495
+ output += `${mark.structure(" ")}${mark.missing(`[Project] ${absent.alias ?? absent.key} (no project on disk)`)}${approvalTag(absent)}
34496
+ `;
34497
+ }
34498
+ const own = options.approvals?.find((a) => a.key === "");
34499
+ const hasMembers = (options.approvals ?? []).some((a) => a.key !== "");
34500
+ if (own && (own.state !== "never" || hasMembers)) output += `${mark.layer("system", "Approval:")} this project is ${own.state}${own.upgraded ? " (locked under the pre-stage-5 gate identity \u2014 re-lock once)" : ""}
34501
+ `;
34170
34502
  return { text: output, failed: false };
34171
34503
  }
34172
34504
  var path36, asWritten;
@@ -35454,7 +35786,7 @@ function renderAgentBriefMarkdown(brief) {
35454
35786
  }
35455
35787
  function registerSkillResources(server, listChanged) {
35456
35788
  server.server.registerCapabilities({ resources: listChanged ? { listChanged: true } : {} });
35457
- server.server.setRequestHandler(import_types4.ListResourcesRequestSchema, () => ({
35789
+ server.server.setRequestHandler(import_types5.ListResourcesRequestSchema, () => ({
35458
35790
  resources: [
35459
35791
  ...listResources().map((d) => ({
35460
35792
  uri: d.resourceUri,
@@ -35465,14 +35797,14 @@ function registerSkillResources(server, listChanged) {
35465
35797
  ...listAgentBriefResources()
35466
35798
  ]
35467
35799
  }));
35468
- server.server.setRequestHandler(import_types4.ReadResourceRequestSchema, (request) => {
35800
+ server.server.setRequestHandler(import_types5.ReadResourceRequestSchema, (request) => {
35469
35801
  const uri = request.params.uri;
35470
35802
  if (uri.startsWith(AGENT_BRIEF_SCHEME)) {
35471
35803
  try {
35472
35804
  const brief = composeAgentBrief(uri.slice(AGENT_BRIEF_SCHEME.length));
35473
35805
  return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: renderAgentBriefMarkdown(brief) }] };
35474
35806
  } catch (e) {
35475
- throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
35807
+ throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
35476
35808
  }
35477
35809
  }
35478
35810
  try {
@@ -35480,20 +35812,20 @@ function registerSkillResources(server, listChanged) {
35480
35812
  return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: content }] };
35481
35813
  } catch (e) {
35482
35814
  const message = e instanceof Error ? e.message : String(e);
35483
- throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, message);
35815
+ throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, message);
35484
35816
  }
35485
35817
  });
35486
35818
  }
35487
35819
  function registerSkillPrompts(server, listChanged) {
35488
35820
  server.server.registerCapabilities({ prompts: listChanged ? { listChanged: true } : {} });
35489
- server.server.setRequestHandler(import_types4.ListPromptsRequestSchema, () => ({
35821
+ server.server.setRequestHandler(import_types5.ListPromptsRequestSchema, () => ({
35490
35822
  prompts: listResources().map((d) => ({
35491
35823
  name: d.id,
35492
35824
  title: d.name,
35493
35825
  description: d.description
35494
35826
  }))
35495
35827
  }));
35496
- server.server.setRequestHandler(import_types4.GetPromptRequestSchema, (request) => {
35828
+ server.server.setRequestHandler(import_types5.GetPromptRequestSchema, (request) => {
35497
35829
  const name = request.params.name;
35498
35830
  try {
35499
35831
  const content = readResource(name);
@@ -35502,7 +35834,7 @@ function registerSkillPrompts(server, listChanged) {
35502
35834
  messages: [{ role: "user", content: { type: "text", text: content } }]
35503
35835
  };
35504
35836
  } catch (e) {
35505
- throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
35837
+ throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
35506
35838
  }
35507
35839
  });
35508
35840
  }
@@ -36510,12 +36842,19 @@ ${testsBlock}` : ""}`, deletion);
36510
36842
  },
36511
36843
  ({ subsystem, recursive }) => {
36512
36844
  try {
36845
+ let approvals;
36846
+ try {
36847
+ approvals = familyApprovals(recursive === false ? 0 : void 0);
36848
+ } catch {
36849
+ approvals = void 0;
36850
+ }
36513
36851
  const report3 = getStatusReport({
36514
36852
  subsystem,
36515
- recursive: recursive ?? true
36853
+ recursive: recursive ?? true,
36854
+ approvals
36516
36855
  });
36517
36856
  const family = statusFamilyContext();
36518
- const verdict = approvalVerdict();
36857
+ const verdict = approvalVerdict(approvals);
36519
36858
  return text2(`${family}${report3.text}${verdict.text}`);
36520
36859
  } catch (e) {
36521
36860
  return errText(String(e));
@@ -36720,20 +37059,20 @@ async function startMcpServer() {
36720
37059
  await server.connect(transport);
36721
37060
  await scopeToClientWorkspace(server);
36722
37061
  try {
36723
- server.server.setNotificationHandler(import_types4.RootsListChangedNotificationSchema, async () => {
37062
+ server.server.setNotificationHandler(import_types5.RootsListChangedNotificationSchema, async () => {
36724
37063
  await scopeToClientWorkspace(server);
36725
37064
  });
36726
37065
  } catch {
36727
37066
  }
36728
37067
  }
36729
- var import_mcp, import_stdio, import_zod11, import_types4, fs23, path40, import_url, crypto5, TYPE_REF_GRAMMAR, staleServerOutput, externalPinsOutput, externalStatusesOutput, SPEC_KINDS, STATUS_VALUES, testsToRevisitOutput, specWriteReceiptOutput, specDeletionOutput, specChangeOutput, specChangeReportOutput, validationIssueOutput, memberCreationOutput, doctrineChangeOutput, impactTotalsOutput, packImpactOutput, validateTreeOutput, getSpecOutput, FINGERPRINT_VERSION, runningFingerprint, RECONNECT, assessedAsChanged, serverBuildStamps, SERVER_BUILD_STAMP, SPEC_WRITE_TOOLS, listChangedEmitters, statusInput, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
37068
+ var import_mcp, import_stdio, import_zod11, import_types5, fs23, path40, import_url, crypto5, TYPE_REF_GRAMMAR, staleServerOutput, externalPinsOutput, externalStatusesOutput, SPEC_KINDS, STATUS_VALUES, testsToRevisitOutput, specWriteReceiptOutput, specDeletionOutput, specChangeOutput, specChangeReportOutput, validationIssueOutput, memberCreationOutput, doctrineChangeOutput, impactTotalsOutput, packImpactOutput, validateTreeOutput, getSpecOutput, FINGERPRINT_VERSION, runningFingerprint, RECONNECT, assessedAsChanged, serverBuildStamps, SERVER_BUILD_STAMP, SPEC_WRITE_TOOLS, listChangedEmitters, statusInput, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
36730
37069
  var init_server = __esm({
36731
37070
  "src/mcp/server.ts"() {
36732
37071
  "use strict";
36733
37072
  import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
36734
37073
  import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
36735
37074
  import_zod11 = require("zod");
36736
- import_types4 = require("@modelcontextprotocol/sdk/types.js");
37075
+ import_types5 = require("@modelcontextprotocol/sdk/types.js");
36737
37076
  fs23 = __toESM(require("fs"));
36738
37077
  path40 = __toESM(require("path"));
36739
37078
  import_url = require("url");
@@ -37324,7 +37663,7 @@ var init_mcp = __esm({
37324
37663
  });
37325
37664
 
37326
37665
  // src/cli/index.ts
37327
- var path82 = __toESM(require("path"));
37666
+ var path81 = __toESM(require("path"));
37328
37667
  var import_commander = require("commander");
37329
37668
  init_defaults();
37330
37669
  init_logger();
@@ -38140,7 +38479,7 @@ function pruneStaleAgents(expectedPaths, scanDirs) {
38140
38479
  async function runGenerate(options = {}) {
38141
38480
  await generateLayer(options);
38142
38481
  const scoped = options.root || options.domain || options.domains;
38143
- if (options.recurse === false || options.dryRun || scoped) return;
38482
+ if (!options.family || options.dryRun || scoped) return;
38144
38483
  const children = listDirectChainedSubprojects(getProjectRoot());
38145
38484
  for (const child of children) {
38146
38485
  logger.blank();
@@ -38255,7 +38594,9 @@ init_core();
38255
38594
  init_project();
38256
38595
  init_lock();
38257
38596
  init_fs();
38597
+ init_errors();
38258
38598
  init_validation();
38599
+ init_lock();
38259
38600
  function checkApproval(strict) {
38260
38601
  if (!loadSystemSpec()) {
38261
38602
  return {
@@ -38266,6 +38607,7 @@ function checkApproval(strict) {
38266
38607
  }
38267
38608
  const lock = readLockState(computeGateStateId());
38268
38609
  const record2 = lock.record;
38610
+ if (lock.state === "stale" && record2.stateId.algorithm !== lock.current.algorithm) return upgradedCheck(record2);
38269
38611
  switch (lock.state) {
38270
38612
  case "locked":
38271
38613
  return {
@@ -38277,7 +38619,7 @@ function checkApproval(strict) {
38277
38619
  return {
38278
38620
  state: "stale",
38279
38621
  approved: false,
38280
- message: `The design changed after it was approved (the approval on record is ${record2.lockedAt} by ${describeApprover(record2.lockedBy)}). Nothing says a human has seen what is about to merge. Fix: run \`wairon lock\` on this branch and commit the updated .wai/lock.json.`
38622
+ message: `The design, or something it was approved under (doctrine, declared inputs, \`composition\`, a direct member's approval), changed after it was approved (the approval on record is ${record2.lockedAt} by ${describeApprover(record2.lockedBy)}). Nothing says a human has seen what is about to merge. Fix: run \`wairon lock\` on this branch and commit the updated .wai/lock.json.`
38281
38623
  };
38282
38624
  default:
38283
38625
  return {
@@ -38287,7 +38629,29 @@ function checkApproval(strict) {
38287
38629
  };
38288
38630
  }
38289
38631
  }
38290
- async function runLock(options = {}, gate) {
38632
+ function upgradedCheck(record2) {
38633
+ const diff = diffAgainstApproval();
38634
+ const specs = !diff ? "This approval predates per-spec digests, so whether a spec file moved since cannot be said." : diffSize(diff) === 0 ? "No own spec file has changed since the approval." : `${diffSize(diff)} own spec file(s) changed since the approval.`;
38635
+ return {
38636
+ state: "stale",
38637
+ approved: false,
38638
+ message: `The approval on record (${record2.lockedAt} by ${describeApprover(record2.lockedBy)}) was taken under an earlier gate identity \u2014 the gate identity gained inputs in stage 5: members' composition subjects, \`composition\`; code conformance moved beside the claim. ${specs} Fix: re-lock once (\`wairon lock\`) and commit the updated .wai/lock.json.`
38639
+ };
38640
+ }
38641
+ var LockRefusedError = class extends WaironError {
38642
+ constructor(message) {
38643
+ super(message);
38644
+ this.name = "LockRefusedError";
38645
+ }
38646
+ };
38647
+ function describeMemberState(entry) {
38648
+ return `${entry.alias ?? entry.key} (${entry.state}${entry.upgraded ? " \u2014 approved under the pre-stage-5 identity, re-lock it once" : ""})`;
38649
+ }
38650
+ function codeLine(code) {
38651
+ if (!code) return "code: no code analysis recorded";
38652
+ return `code: ${code.errors} error(s), ${code.warnings} warning(s) recorded beside the claim (analyzer ${code.analyzer.validatorVersion}, grade ${code.analyzer.grade}) \u2014 CI enforces them, not the lock`;
38653
+ }
38654
+ function summarize3(gate, members) {
38291
38655
  const diff = diffAgainstApproval();
38292
38656
  if (!diff) {
38293
38657
  logger.info("First approval of this tree \u2014 the whole spec tree becomes the approved baseline.");
@@ -38299,12 +38663,21 @@ async function runLock(options = {}, gate) {
38299
38663
  for (const p of diff.added) logger.info(` + ${p}`);
38300
38664
  for (const p of diff.removed) logger.info(` - ${p}`);
38301
38665
  }
38302
- const moved = movedChildren(memberMounts2());
38303
- if (moved.length > 0) {
38304
- logger.info(`${moved.length} chained child project(s) moved since the last approval:`);
38305
- for (const m of moved) {
38306
- logger.info(` ${m.id}: ${m.pinned.slice(0, 19)}\u2026 \u2192 ${m.now ? `${m.now.slice(0, 19)}\u2026` : "(no approval)"}`);
38307
- }
38666
+ logger.info(codeLine(gate.analysis));
38667
+ for (const m of members) {
38668
+ const pin2 = m.pinned === "moved" ? " \u2014 its pin moved since the last approval" : "";
38669
+ logger.info(` member ${m.alias ?? m.key}: ${m.state}${m.upgraded ? " (pre-stage-5 lock, re-lock once)" : ""}${pin2}`);
38670
+ }
38671
+ }
38672
+ async function runLock(options, gate, captured) {
38673
+ const members = familyApprovals(1).filter((a) => a.parent === "");
38674
+ summarize3(gate, members);
38675
+ const config = loadProjectConfig();
38676
+ const unapproved = members.filter((m) => m.state !== "approved");
38677
+ if (config?.composition?.requireApprovedMembers && unapproved.length > 0) {
38678
+ throw new LockRefusedError(
38679
+ `composition.requireApprovedMembers: direct member(s) not approved \u2014 ${unapproved.map(describeMemberState).join(", ")}. Lock each at its own root first (\`wairon lock\` there); a parent never approves below itself. Nothing was written.`
38680
+ );
38308
38681
  }
38309
38682
  logger.blank();
38310
38683
  logger.warn("This records the current design as approved and (re)generates the agent topology.");
@@ -38329,30 +38702,48 @@ async function runLock(options = {}, gate) {
38329
38702
  specPathsInScope(options.subsystem).map((p) => path44.relative(root, p).split(path44.sep).join("/"))
38330
38703
  )
38331
38704
  } : void 0;
38332
- const config = loadProjectConfig();
38333
38705
  const projectId = config ? effectiveProjectId(config) : null;
38706
+ const specs = captureApprovedSpecs(root, scope);
38707
+ const lockedBy = localApprover();
38708
+ const now = computeGateStateId();
38709
+ if (now.algorithm !== captured.algorithm || now.digest !== captured.digest) {
38710
+ throw new LockRefusedError(
38711
+ "The lock's inputs changed while it ran \u2014 a spec, the doctrine, an input, the composition block or a member's approval moved between the validation and the write. Nothing was written; run `wairon lock` again."
38712
+ );
38713
+ }
38714
+ const design = designOnly(gate);
38715
+ const count = (severity) => design.issues.filter((i) => i.severity === severity).length;
38334
38716
  const record2 = {
38335
- stateId: computeGateStateId(),
38717
+ // The record format: 2 from stage 5 on (members, code, no children).
38718
+ format: 2,
38719
+ stateId: captured,
38336
38720
  lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
38337
- lockedBy: localApprover(),
38721
+ lockedBy,
38338
38722
  validatorVersion: WAIRON_VERSION,
38339
- validationResult: {
38340
- valid: true,
38341
- errors: 0,
38342
- warnings: gate ? gate.issues.filter((i) => i.severity === "warning").length : 0,
38343
- notices: gate ? gate.issues.filter((i) => i.severity === "notice").length : 0
38344
- },
38723
+ validationResult: { valid: design.valid, errors: count("error"), warnings: count("warning"), notices: count("notice") },
38345
38724
  status: "ready",
38346
38725
  ...projectId !== null ? { projectId } : {},
38347
- specs: captureApprovedSpecs(root, scope),
38348
- children: currentChildPins(memberMounts2(), root)
38726
+ specs,
38727
+ members: memberPins(members),
38728
+ ...gate.analysis ? { code: withoutCodes(gate.analysis) } : {}
38349
38729
  };
38350
38730
  writeLockRecord(record2);
38351
38731
  return record2;
38352
38732
  }
38353
- function memberMounts2() {
38354
- const root = getProjectRoot();
38355
- return graph().nodes.filter((n) => n.parent === "" && n.mountAlias !== void 0).map((n) => ({ id: n.mountAlias, projectPath: path44.relative(root, n.directory).split(path44.sep).join("/") }));
38733
+ function memberPins(members) {
38734
+ const pins = {};
38735
+ for (const m of members) {
38736
+ pins[m.alias ?? m.key] = {
38737
+ ...m.projectId !== void 0 ? { project: m.projectId } : {},
38738
+ ...m.subject !== void 0 ? { subject: m.subject } : {},
38739
+ state: m.state
38740
+ };
38741
+ }
38742
+ return pins;
38743
+ }
38744
+ function withoutCodes(analysis) {
38745
+ const { codes: _codes, ...rest } = analysis;
38746
+ return rest;
38356
38747
  }
38357
38748
 
38358
38749
  // src/commands/validate.ts
@@ -38551,6 +38942,7 @@ async function runValidate(options = {}) {
38551
38942
  }
38552
38943
 
38553
38944
  // src/cli/index.ts
38945
+ init_lock();
38554
38946
  init_paths();
38555
38947
  init_fs();
38556
38948
 
@@ -38681,14 +39073,14 @@ var http = __toESM(require("http"));
38681
39073
  var https = __toESM(require("https"));
38682
39074
  init_defaults();
38683
39075
  function downloadFile(url, dest) {
38684
- return new Promise((resolve39, reject) => {
39076
+ return new Promise((resolve40, reject) => {
38685
39077
  const file = fs26.createWriteStream(dest);
38686
39078
  const get4 = url.startsWith("https://") ? https.get : http.get;
38687
39079
  get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
38688
39080
  if (res.statusCode === 301 || res.statusCode === 302) {
38689
39081
  file.close();
38690
39082
  res.destroy();
38691
- downloadFile(res.headers.location, dest).then(resolve39).catch(reject);
39083
+ downloadFile(res.headers.location, dest).then(resolve40).catch(reject);
38692
39084
  return;
38693
39085
  }
38694
39086
  if (res.statusCode !== 200) {
@@ -38700,7 +39092,7 @@ function downloadFile(url, dest) {
38700
39092
  res.pipe(file);
38701
39093
  file.on("finish", () => {
38702
39094
  res.destroy();
38703
- file.close(() => resolve39());
39095
+ file.close(() => resolve40());
38704
39096
  });
38705
39097
  file.on("error", (err) => {
38706
39098
  res.destroy();
@@ -38877,7 +39269,7 @@ function releaseChannelLabel(tag) {
38877
39269
  return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
38878
39270
  }
38879
39271
  function fetchReleases(repo) {
38880
- return new Promise((resolve39, reject) => {
39272
+ return new Promise((resolve40, reject) => {
38881
39273
  const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
38882
39274
  const options = {
38883
39275
  headers: {
@@ -38897,7 +39289,7 @@ function fetchReleases(repo) {
38897
39289
  return;
38898
39290
  }
38899
39291
  try {
38900
- resolve39(JSON.parse(data));
39292
+ resolve40(JSON.parse(data));
38901
39293
  } catch {
38902
39294
  reject(new Error("Failed to parse GitHub API response"));
38903
39295
  }
@@ -39040,16 +39432,26 @@ function refuseUnreadableTree(explanation) {
39040
39432
  function withoutTrailingNewline(text3) {
39041
39433
  return text3.endsWith("\n") ? text3.slice(0, -1) : text3;
39042
39434
  }
39435
+ function depthOf2(recursive) {
39436
+ if (recursive === false) return 0;
39437
+ return typeof recursive === "number" ? recursive : void 0;
39438
+ }
39043
39439
  async function runStatus(options = {}) {
39044
39440
  assertProjectInitialized();
39045
- const report3 = getStatusReport(options, TERMINAL_DECOR);
39441
+ let approvals;
39442
+ try {
39443
+ approvals = familyApprovals(depthOf2(options.recursive));
39444
+ } catch {
39445
+ approvals = void 0;
39446
+ }
39447
+ const report3 = getStatusReport({ ...options, approvals }, TERMINAL_DECOR);
39046
39448
  if (report3.failed) {
39047
39449
  refuseUnreadableTree(report3.text);
39048
39450
  }
39049
39451
  logger.header("Architecture Status Dashboard");
39050
39452
  logger.blank();
39051
39453
  console.log(withoutTrailingNewline(report3.text));
39052
- const lock = approvalVerdict();
39454
+ const lock = approvalVerdict(approvals);
39053
39455
  if (lock.text.trim()) {
39054
39456
  logger.blank();
39055
39457
  if (lock.drifted) logger.warn(lock.text.trim());
@@ -39125,7 +39527,7 @@ async function filteredCheckbox(config) {
39125
39527
  32,
39126
39528
  Math.max(...items.map((i) => i.label.length))
39127
39529
  );
39128
- return new Promise((resolve39) => {
39530
+ return new Promise((resolve40) => {
39129
39531
  const checked = /* @__PURE__ */ new Set();
39130
39532
  let cursor = 0;
39131
39533
  let filterIdx = 0;
@@ -39192,7 +39594,7 @@ async function filteredCheckbox(config) {
39192
39594
  );
39193
39595
  teardown();
39194
39596
  const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
39195
- resolve39(result);
39597
+ resolve40(result);
39196
39598
  }
39197
39599
  function abort() {
39198
39600
  process.stdout.write("\n");
@@ -40537,7 +40939,13 @@ function coverProject(node, family) {
40537
40939
  const line2 = {
40538
40940
  key: node.namespace,
40539
40941
  ...record2 ? { lockedBy: record2.validatorVersion } : {},
40540
- ...record2 ? { lockedTotals: totals2(record2.validationResult.errors, record2.validationResult.warnings, record2.validationResult.notices) } : {},
40942
+ // A format-2 record counts the design half and the code half apart; the
40943
+ // run below counts both, so the locked totals add them back together.
40944
+ ...record2 ? { lockedTotals: totals2(
40945
+ record2.validationResult.errors + (record2.code?.errors ?? 0),
40946
+ record2.validationResult.warnings + (record2.code?.warnings ?? 0),
40947
+ record2.validationResult.notices === void 0 ? void 0 : record2.validationResult.notices + (record2.code?.notices ?? 0)
40948
+ ) } : {},
40541
40949
  currentTotals: totals2(count("error"), count("warning"), count("notice")),
40542
40950
  predatesStage4: record2 ? predatesStage4(record2.validatorVersion) : false
40543
40951
  };
@@ -40841,12 +41249,19 @@ async function runDoctor(options = {}) {
40841
41249
  line(tally, "warn", "this lock predates per-spec approval \u2014 re-lock so `wairon status` can name what drifts");
40842
41250
  }
40843
41251
  logger.blank();
41252
+ } else if (lock.state === "stale" && lock.record.stateId.algorithm !== lock.current.algorithm) {
41253
+ console.log(import_chalk12.default.bold("Lock"));
41254
+ line(
41255
+ tally,
41256
+ "warn",
41257
+ `stale \u2014 the lock of ${lock.record.lockedAt} was taken under an earlier gate identity: the gate identity gained inputs in stage 5 (members' composition subjects, \`composition\`; code conformance moved beside the claim). Re-lock once (\`wairon lock\`).`
41258
+ );
40844
41259
  } else if (lock.state === "stale") {
40845
41260
  console.log(import_chalk12.default.bold("Lock"));
40846
41261
  line(
40847
41262
  tally,
40848
41263
  "warn",
40849
- `stale \u2014 the specs or the governing doctrine changed since ${lock.record.lockedAt}, so this lock no longer holds. Re-run \`wairon lock\` to freeze the current state.`
41264
+ `stale \u2014 the design or something it was approved under changed since ${lock.record.lockedAt}, so this lock no longer holds. Re-run \`wairon lock\` to freeze the current state.`
40850
41265
  );
40851
41266
  logger.blank();
40852
41267
  }
@@ -42182,7 +42597,7 @@ async function impactPack(spec) {
42182
42597
 
42183
42598
  // src/commands/host.ts
42184
42599
  var fs57 = __toESM(require("fs"));
42185
- var path79 = __toESM(require("path"));
42600
+ var path78 = __toESM(require("path"));
42186
42601
  var os11 = __toESM(require("os"));
42187
42602
  var crypto24 = __toESM(require("crypto"));
42188
42603
  var import_child_process7 = require("child_process");
@@ -42193,9 +42608,9 @@ init_errors();
42193
42608
 
42194
42609
  // src/server/admin.ts
42195
42610
  var crypto11 = __toESM(require("crypto"));
42196
- var path64 = __toESM(require("path"));
42197
42611
  init_fs();
42198
42612
  init_defaults();
42613
+ init_lock();
42199
42614
  init_project();
42200
42615
 
42201
42616
  // src/server/auth.ts
@@ -44463,6 +44878,7 @@ function list() {
44463
44878
  init_validation();
44464
44879
  init_errors();
44465
44880
  init_validation();
44881
+ init_validation();
44466
44882
  function validateAsComplete2() {
44467
44883
  const config = loadProjectConfig();
44468
44884
  if (!config) throw new ProjectNotInitializedError();
@@ -44609,57 +45025,115 @@ function hostedApprover(subject) {
44609
45025
  source: "hosted"
44610
45026
  };
44611
45027
  }
44612
- function directMembers(family, root) {
44613
- return family.nodes.filter((n) => n.parent === "" && n.mountAlias !== void 0).map((n) => ({ id: n.mountAlias, projectPath: path64.relative(root, n.directory).split(path64.sep).join("/") }));
44614
- }
44615
45028
  function boundLifecycleRoot(cfg, projectId, subproject) {
44616
45029
  const root = existingProjectRoot(cfg.dataDir, projectId);
44617
45030
  if (!root) throw new Error(`Unknown project "${projectId}".`);
44618
45031
  if (!subproject) return root;
44619
45032
  return resolveSubprojectMounts(projectId, root, subproject.split(SUBPROJECT_SEPARATOR));
44620
45033
  }
44621
- function executeApprovedLock(cfg, projectId, approver, subproject) {
45034
+ var LockRefusedError2 = class extends Error {
45035
+ constructor(message) {
45036
+ super(message);
45037
+ this.name = "LockRefusedError";
45038
+ }
45039
+ };
45040
+ var RequestedDesignMovedError = class extends LockRefusedError2 {
45041
+ constructor(requested, now) {
45042
+ super(`The design changed since the lock was requested (requested ${requested}, now ${now}); request the lock again. Nothing was written.`);
45043
+ this.requested = requested;
45044
+ this.now = now;
45045
+ }
45046
+ };
45047
+ function renderIdentity(stateId) {
45048
+ return `${stateId.algorithm}:${stateId.digest}`;
45049
+ }
45050
+ function gateIdentity(cfg, projectId, subproject) {
45051
+ const root = boundLifecycleRoot(cfg, projectId, subproject);
45052
+ return runWithProjectRoot(root, () => renderIdentity(computeGateStateId()));
45053
+ }
45054
+ function executeApprovedLock(cfg, projectId, approver, subproject, expected) {
44622
45055
  const root = boundLifecycleRoot(cfg, projectId, subproject);
44623
45056
  return runWithProjectRoot(root, () => {
44624
45057
  sync();
44625
- const result = validateAsComplete2();
44626
- const errors = result.issues.filter((i) => i.severity === "error");
44627
- if (errors.length) {
44628
- throw new LockValidationError(errors.map((e) => ({ code: e.code, message: e.message, specId: e.specId })));
44629
- }
44630
- const stateId = computeGateStateId();
44631
- const specs = captureApprovedSpecs();
44632
- const children = currentChildPins(directMembers(graph(), root));
45058
+ const captured = computeGateStateId();
45059
+ refuseUnrequestedDesign(captured, expected);
45060
+ const gate = validateAsComplete2();
45061
+ const design = refuseDesignErrors(gate);
45062
+ const members = familyApprovals(1).filter((a) => a.parent === "");
44633
45063
  const config = loadProjectConfig();
44634
- const projectId2 = config ? effectiveProjectId(config) : null;
44635
- const record2 = {
44636
- stateId,
44637
- lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
44638
- lockedBy: approver,
44639
- validatorVersion: WAIRON_VERSION,
44640
- validationResult: {
44641
- valid: true,
44642
- errors: 0,
44643
- warnings: result.issues.filter((i) => i.severity === "warning").length,
44644
- notices: result.issues.filter((i) => i.severity === "notice").length
44645
- },
44646
- status: "ready",
44647
- ...projectId2 !== null ? { projectId: projectId2 } : {},
44648
- specs,
44649
- children
44650
- };
45064
+ refuseUnapprovedMembers(config?.composition?.requireApprovedMembers === true, members);
45065
+ const specs = captureApprovedSpecs();
45066
+ refuseMovedInputs(captured, computeGateStateId());
45067
+ const record2 = lockRecordOf(captured, approver, design, gate, config, specs, members);
44651
45068
  writeLockRecord(record2);
44652
- const publish2 = publish(`wairon lock: ${projectId2} @ ${stateId}`);
44653
- if (!publish2.published) return record2;
44654
- const published = {
44655
- ...record2,
44656
- commitSha: publish2.commitSha,
44657
- compareUrl: publish2.compareUrl
44658
- };
44659
- writeLockRecord(published);
44660
- return published;
45069
+ return publishLock(record2);
44661
45070
  });
44662
45071
  }
45072
+ function refuseUnrequestedDesign(captured, expected) {
45073
+ if (expected === void 0 || renderIdentity(captured) === expected) return;
45074
+ throw new RequestedDesignMovedError(expected, renderIdentity(captured));
45075
+ }
45076
+ function refuseDesignErrors(gate) {
45077
+ const design = designOnly(gate);
45078
+ const errors = design.issues.filter((i) => i.severity === "error");
45079
+ if (errors.length) {
45080
+ throw new LockValidationError(errors.map((e) => ({ code: e.code, message: e.message, specId: e.specId })));
45081
+ }
45082
+ return design;
45083
+ }
45084
+ function refuseUnapprovedMembers(required, members) {
45085
+ const unapproved = members.filter((m) => m.state !== "approved");
45086
+ if (!required || unapproved.length === 0) return;
45087
+ const named2 = unapproved.map((m) => `${m.alias ?? m.key} (${m.state}${m.upgraded ? " \u2014 approved under the pre-stage-5 identity, re-lock it once" : ""})`);
45088
+ throw new LockRefusedError2(
45089
+ `composition.requireApprovedMembers: direct member(s) not approved \u2014 ${named2.join(", ")}. Lock each at its own root first; a parent never approves below itself. Nothing was written.`
45090
+ );
45091
+ }
45092
+ function refuseMovedInputs(captured, now) {
45093
+ if (now.algorithm === captured.algorithm && now.digest === captured.digest) return;
45094
+ throw new LockRefusedError2(
45095
+ "The lock's inputs changed while it ran \u2014 a spec, the doctrine, an input, the composition block or a member's approval moved between the validation and the write. Nothing was written; run the lock again."
45096
+ );
45097
+ }
45098
+ function lockRecordOf(captured, approver, design, gate, config, specs, members) {
45099
+ const count = (severity) => design.issues.filter((i) => i.severity === severity).length;
45100
+ const projectId = config ? effectiveProjectId(config) : null;
45101
+ return {
45102
+ format: 2,
45103
+ stateId: captured,
45104
+ lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
45105
+ lockedBy: approver,
45106
+ validatorVersion: WAIRON_VERSION,
45107
+ validationResult: { valid: design.valid, errors: count("error"), warnings: count("warning"), notices: count("notice") },
45108
+ status: "ready",
45109
+ ...projectId !== null ? { projectId } : {},
45110
+ specs,
45111
+ members: memberPins2(members),
45112
+ ...gate.analysis ? { code: withoutCodes2(gate.analysis) } : {}
45113
+ };
45114
+ }
45115
+ function memberPins2(members) {
45116
+ const pins = {};
45117
+ for (const m of members) {
45118
+ pins[m.alias ?? m.key] = {
45119
+ ...m.projectId !== void 0 ? { project: m.projectId } : {},
45120
+ ...m.subject !== void 0 ? { subject: m.subject } : {},
45121
+ state: m.state
45122
+ };
45123
+ }
45124
+ return pins;
45125
+ }
45126
+ function withoutCodes2(analysis) {
45127
+ const { codes: _codes, ...rest } = analysis;
45128
+ return rest;
45129
+ }
45130
+ function publishLock(record2) {
45131
+ const publish2 = publish(`wairon lock: ${record2.projectId ?? "project"} @ ${renderIdentity(record2.stateId)}`);
45132
+ if (!publish2.published) return record2;
45133
+ const published = { ...record2, commitSha: publish2.commitSha, compareUrl: publish2.compareUrl };
45134
+ writeLockRecord(published);
45135
+ return published;
45136
+ }
44663
45137
  function projectGitCredentialKey(project2) {
44664
45138
  return `git-project:${project2}`;
44665
45139
  }
@@ -44834,7 +45308,7 @@ function diagramViewLink(cfg, credential, project2) {
44834
45308
 
44835
45309
  // src/server/packs.ts
44836
45310
  var fs43 = __toESM(require("fs"));
44837
- var path65 = __toESM(require("path"));
45311
+ var path64 = __toESM(require("path"));
44838
45312
  init_fs();
44839
45313
  init_yaml();
44840
45314
 
@@ -44890,7 +45364,7 @@ function assertNotCodeArchive(info) {
44890
45364
  function extractDeclarativeArchive(archive, destDir) {
44891
45365
  const result = extractArchive(archive, destDir, HOSTED_STRICT_LIMITS);
44892
45366
  try {
44893
- assertDeclarative(fs43.readFileSync(path65.join(result.directory, result.entryPath), "utf8"));
45367
+ assertDeclarative(fs43.readFileSync(path64.join(result.directory, result.entryPath), "utf8"));
44894
45368
  } catch (e) {
44895
45369
  fs43.rmSync(destDir, { recursive: true, force: true });
44896
45370
  throw e;
@@ -44899,10 +45373,10 @@ function extractDeclarativeArchive(archive, destDir) {
44899
45373
  function probe(loadRef, baseRoot, scope, displayRef) {
44900
45374
  const loaded = loadExtensionPacks([{ ref: loadRef, scope }], baseRoot);
44901
45375
  if (loaded.errors.length) {
44902
- return { name: path65.basename(displayRef), scope, ref: displayRef, profiles: 0, languages: 0, rules: 0, error: loaded.errors[0] };
45376
+ return { name: path64.basename(displayRef), scope, ref: displayRef, profiles: 0, languages: 0, rules: 0, error: loaded.errors[0] };
44903
45377
  }
44904
45378
  return {
44905
- name: loaded.packNames[0] ?? path65.basename(displayRef),
45379
+ name: loaded.packNames[0] ?? path64.basename(displayRef),
44906
45380
  scope,
44907
45381
  ref: displayRef,
44908
45382
  profiles: Object.keys(loaded.profiles).length,
@@ -44916,20 +45390,20 @@ function probe(loadRef, baseRoot, scope, displayRef) {
44916
45390
  };
44917
45391
  }
44918
45392
  function writeFileAtomic(file, content) {
44919
- fs43.mkdirSync(path65.dirname(file), { recursive: true });
45393
+ fs43.mkdirSync(path64.dirname(file), { recursive: true });
44920
45394
  const tmp = `${file}.tmp`;
44921
45395
  fs43.writeFileSync(tmp, content);
44922
45396
  fs43.renameSync(tmp, file);
44923
45397
  }
44924
45398
  function stem(ref) {
44925
- return path65.basename(ref).replace(PACK_EXT_RE2, "");
45399
+ return path64.basename(ref).replace(PACK_EXT_RE2, "");
44926
45400
  }
44927
45401
  function imagePacksDir() {
44928
45402
  return process.env.WAIRON_IMAGE_PACKS_DIR ?? "/opt/wairon/packs";
44929
45403
  }
44930
45404
  function probeTier(dir, tier) {
44931
45405
  return discoverPacks(dir).map((full) => {
44932
- const d = probe(full, path65.dirname(full), "global", path65.basename(full));
45406
+ const d = probe(full, path64.dirname(full), "global", path64.basename(full));
44933
45407
  d.tier = tier;
44934
45408
  return d;
44935
45409
  });
@@ -44947,9 +45421,9 @@ function scanGlobalPackProfiles() {
44947
45421
  for (const dir of [globalPacksDir(), imagePacksDir()]) {
44948
45422
  for (const full of discoverPacks(dir)) {
44949
45423
  try {
44950
- const loaded = loadExtensionPacks([{ ref: full, scope: "global" }], path65.dirname(full));
45424
+ const loaded = loadExtensionPacks([{ ref: full, scope: "global" }], path64.dirname(full));
44951
45425
  if (loaded.errors.length) continue;
44952
- const source = loaded.packNames[0] ?? path65.basename(full);
45426
+ const source = loaded.packNames[0] ?? path64.basename(full);
44953
45427
  for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
44954
45428
  } catch {
44955
45429
  }
@@ -45005,7 +45479,7 @@ function readPackContent(full) {
45005
45479
  if (st.isFile()) return fs43.readFileSync(full, "utf8");
45006
45480
  if (st.isDirectory()) {
45007
45481
  for (const entry of ["pack.yaml", "pack.yml"]) {
45008
- const p = path65.join(full, entry);
45482
+ const p = path64.join(full, entry);
45009
45483
  if (fs43.existsSync(p) && fs43.statSync(p).isFile()) return fs43.readFileSync(p, "utf8");
45010
45484
  }
45011
45485
  }
@@ -45015,9 +45489,9 @@ function storeResolveGlobalPacks(names) {
45015
45489
  const index = /* @__PURE__ */ new Map();
45016
45490
  const indexTier = (dir, tier) => {
45017
45491
  for (const full of discoverPacks(dir)) {
45018
- const manifestName = probe(full, path65.dirname(full), "global", path65.basename(full)).name;
45492
+ const manifestName = probe(full, path64.dirname(full), "global", path64.basename(full)).name;
45019
45493
  const candidate = { full, manifestName, tier };
45020
- for (const key of [manifestName, stem(full), path65.basename(full)]) {
45494
+ for (const key of [manifestName, stem(full), path64.basename(full)]) {
45021
45495
  if (!index.has(key)) index.set(key, candidate);
45022
45496
  }
45023
45497
  }
@@ -45044,7 +45518,7 @@ function storeInstallGlobalPack(name, content) {
45044
45518
  assertName(name);
45045
45519
  assertDeclarative(content);
45046
45520
  const dir = globalPacksDir();
45047
- const file = path65.join(dir, `${name}.yaml`);
45521
+ const file = path64.join(dir, `${name}.yaml`);
45048
45522
  writeFileAtomic(file, content);
45049
45523
  const descriptor = probe(file, dir, "global", `${name}.yaml`);
45050
45524
  descriptor.tier = "instance";
@@ -45055,7 +45529,7 @@ function storeInstallGlobalPackArchive(archive, name) {
45055
45529
  assertNotCodeArchive(info);
45056
45530
  const packName = name ?? info.name;
45057
45531
  assertName(packName);
45058
- const dir = path65.join(globalPacksDir(), packName);
45532
+ const dir = path64.join(globalPacksDir(), packName);
45059
45533
  extractDeclarativeArchive(archive, dir);
45060
45534
  const descriptor = probe(dir, globalPacksDir(), "global", packName);
45061
45535
  descriptor.tier = "instance";
@@ -45066,7 +45540,7 @@ function declaredName2(loadRef, baseRoot, scope) {
45066
45540
  return d.error ? void 0 : d.name;
45067
45541
  }
45068
45542
  function pickByName(candidates, name, fileOf, manifestOf2) {
45069
- const byFile = candidates.find((c) => stem(fileOf(c)) === name || path65.basename(fileOf(c)) === name);
45543
+ const byFile = candidates.find((c) => stem(fileOf(c)) === name || path64.basename(fileOf(c)) === name);
45070
45544
  if (byFile !== void 0) return byFile;
45071
45545
  const byManifest = candidates.filter((c) => manifestOf2(c) === name);
45072
45546
  if (byManifest.length > 1) {
@@ -45079,11 +45553,11 @@ function pickByName(candidates, name, fileOf, manifestOf2) {
45079
45553
  function storeRemoveGlobalPack(name) {
45080
45554
  assertName(name);
45081
45555
  const dir = globalPacksDir();
45082
- const manifestOf2 = (ref) => declaredName2(ref, path65.dirname(ref), "global");
45083
- const match2 = pickByName(discoverPacks(dir), name, path65.basename, manifestOf2);
45556
+ const manifestOf2 = (ref) => declaredName2(ref, path64.dirname(ref), "global");
45557
+ const match2 = pickByName(discoverPacks(dir), name, path64.basename, manifestOf2);
45084
45558
  if (!match2) {
45085
45559
  const inImage = discoverPacks(imagePacksDir()).some(
45086
- (ref) => stem(ref) === name || path65.basename(ref) === name || manifestOf2(ref) === name
45560
+ (ref) => stem(ref) === name || path64.basename(ref) === name || manifestOf2(ref) === name
45087
45561
  );
45088
45562
  throw new Error(
45089
45563
  inImage ? `Pack "${name}" is an immutable image-layer pack (WAIRON_IMAGE_PACKS_DIR) and cannot be removed at runtime; rebuild the extension image without it.` : `No server-global instance pack named "${name}".`
@@ -45105,7 +45579,7 @@ function storeInstallProjectPack(name, content) {
45105
45579
  assertDeclarative(content);
45106
45580
  const root = getProjectRoot();
45107
45581
  const relRef = `.wai/packs/${name}.yaml`;
45108
- writeFileAtomic(path65.join(root, ".wai", "packs", `${name}.yaml`), content);
45582
+ writeFileAtomic(path64.join(root, ".wai", "packs", `${name}.yaml`), content);
45109
45583
  return probe(relRef, root, "project", relRef);
45110
45584
  }
45111
45585
  function storeInstallProjectPackArchive(archive, name) {
@@ -45115,15 +45589,15 @@ function storeInstallProjectPackArchive(archive, name) {
45115
45589
  assertName(packName);
45116
45590
  const root = getProjectRoot();
45117
45591
  const relRef = `.wai/packs/${packName}`;
45118
- extractDeclarativeArchive(archive, path65.join(root, ".wai", "packs", packName));
45592
+ extractDeclarativeArchive(archive, path64.join(root, ".wai", "packs", packName));
45119
45593
  return probe(relRef, root, "project", relRef);
45120
45594
  }
45121
45595
  function storeRemoveProjectPack(name, ref) {
45122
45596
  assertName(name);
45123
45597
  const root = getProjectRoot();
45124
- const resolved = path65.resolve(root, ref ?? path65.join(".wai", "packs", name));
45125
- const vendorDir = path65.resolve(root, ".wai", "packs");
45126
- if (resolved.startsWith(vendorDir + path65.sep)) {
45598
+ const resolved = path64.resolve(root, ref ?? path64.join(".wai", "packs", name));
45599
+ const vendorDir = path64.resolve(root, ".wai", "packs");
45600
+ if (resolved.startsWith(vendorDir + path64.sep)) {
45127
45601
  fs43.rmSync(resolved, { recursive: true, force: true });
45128
45602
  }
45129
45603
  }
@@ -45146,7 +45620,7 @@ function boundProject2(cfg, project2) {
45146
45620
  function findPackRegistration(config, name) {
45147
45621
  const relRef = `.wai/packs/${name}.yaml`;
45148
45622
  const entries = config?.extensions?.packs ?? [];
45149
- const direct = entries.find((entry) => typeof entry === "string" ? entry === relRef || stem(entry) === name || path65.basename(entry) === name : entry.name === name);
45623
+ const direct = entries.find((entry) => typeof entry === "string" ? entry === relRef || stem(entry) === name || path64.basename(entry) === name : entry.name === name);
45150
45624
  if (direct !== void 0) return direct;
45151
45625
  const root = getProjectRoot();
45152
45626
  const refs = entries.filter((entry) => typeof entry === "string");
@@ -45290,7 +45764,7 @@ function installProjectPackArchive(cfg, credential, project2, archive, name) {
45290
45764
 
45291
45765
  // src/server/audit.ts
45292
45766
  var fs44 = __toESM(require("fs"));
45293
- var path66 = __toESM(require("path"));
45767
+ var path65 = __toESM(require("path"));
45294
45768
  var crypto12 = __toESM(require("crypto"));
45295
45769
  var LEVEL_ORDER = {
45296
45770
  debug: 0,
@@ -45313,7 +45787,7 @@ var DEFAULT_AUDIT_POLICY = {
45313
45787
  metadataMode: "redacted"
45314
45788
  };
45315
45789
  function storePath9(dataDir) {
45316
- return path66.join(dataDir, "audit-events.json");
45790
+ return path65.join(dataDir, "audit-events.json");
45317
45791
  }
45318
45792
  function readEvents(dataDir) {
45319
45793
  const p = storePath9(dataDir);
@@ -45334,7 +45808,7 @@ function readEvents(dataDir) {
45334
45808
  }
45335
45809
  function persistEvents(dataDir, events) {
45336
45810
  const p = storePath9(dataDir);
45337
- fs44.mkdirSync(path66.dirname(p), { recursive: true });
45811
+ fs44.mkdirSync(path65.dirname(p), { recursive: true });
45338
45812
  const tmp = `${p}.tmp`;
45339
45813
  fs44.writeFileSync(tmp, JSON.stringify(events, null, 2) + "\n");
45340
45814
  fs44.renameSync(tmp, p);
@@ -46026,7 +46500,7 @@ function assertAllowedRedirectUri(provider, redirectUri) {
46026
46500
 
46027
46501
  // src/server/policy.ts
46028
46502
  var fs45 = __toESM(require("fs"));
46029
- var path67 = __toESM(require("path"));
46503
+ var path66 = __toESM(require("path"));
46030
46504
  init_fs();
46031
46505
  init_yaml();
46032
46506
  init_pack_impact();
@@ -46045,7 +46519,7 @@ function sendJson(res, status2, body) {
46045
46519
 
46046
46520
  // src/server/policy.ts
46047
46521
  function packPolicyPath(dataDir) {
46048
- return path67.join(dataDir, "pack-policy.json");
46522
+ return path66.join(dataDir, "pack-policy.json");
46049
46523
  }
46050
46524
  function getPackPolicyRecord(dataDir) {
46051
46525
  const p = packPolicyPath(dataDir);
@@ -46065,14 +46539,14 @@ function getPackPolicyRecord(dataDir) {
46065
46539
  function setPackPolicyRecord(dataDir, policy) {
46066
46540
  const stored = { ...policy, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
46067
46541
  const p = packPolicyPath(dataDir);
46068
- fs45.mkdirSync(path67.dirname(p), { recursive: true });
46542
+ fs45.mkdirSync(path66.dirname(p), { recursive: true });
46069
46543
  const tmp = `${p}.tmp`;
46070
46544
  fs45.writeFileSync(tmp, JSON.stringify(stored, null, 2) + "\n");
46071
46545
  fs45.renameSync(tmp, p);
46072
46546
  return stored;
46073
46547
  }
46074
46548
  function exposurePolicyPath(dataDir) {
46075
- return path67.join(dataDir, "exposure-policy.json");
46549
+ return path66.join(dataDir, "exposure-policy.json");
46076
46550
  }
46077
46551
  var COMPATIBLE_DEFAULT_EXPOSURE = {
46078
46552
  adminApiMode: "local_only",
@@ -46102,7 +46576,7 @@ function getExposurePolicyRecord(dataDir) {
46102
46576
  }
46103
46577
  function setExposurePolicyRecord(dataDir, policy) {
46104
46578
  const p = exposurePolicyPath(dataDir);
46105
- fs45.mkdirSync(path67.dirname(p), { recursive: true });
46579
+ fs45.mkdirSync(path66.dirname(p), { recursive: true });
46106
46580
  const tmp = `${p}.tmp`;
46107
46581
  fs45.writeFileSync(tmp, JSON.stringify(policy, null, 2) + "\n");
46108
46582
  fs45.renameSync(tmp, p);
@@ -46123,7 +46597,7 @@ function effectivePolicy(dataDir) {
46123
46597
  return getPackPolicyRecord(dataDir) ?? PERMISSIVE_DEFAULT_POLICY;
46124
46598
  }
46125
46599
  function identityProvidersPath(dataDir) {
46126
- return path67.join(dataDir, "identity-providers.json");
46600
+ return path66.join(dataDir, "identity-providers.json");
46127
46601
  }
46128
46602
  function sanitizeIdentityProvider(config) {
46129
46603
  const clean = {
@@ -46147,7 +46621,7 @@ function sanitizeIdentityProvider(config) {
46147
46621
  }
46148
46622
  function writeIdentityProviderRecords(dataDir, records) {
46149
46623
  const p = identityProvidersPath(dataDir);
46150
- fs45.mkdirSync(path67.dirname(p), { recursive: true });
46624
+ fs45.mkdirSync(path66.dirname(p), { recursive: true });
46151
46625
  const tmp = `${p}.tmp`;
46152
46626
  fs45.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
46153
46627
  fs45.renameSync(tmp, p);
@@ -47239,10 +47713,10 @@ init_fs();
47239
47713
 
47240
47714
  // src/server/relations.ts
47241
47715
  var fs46 = __toESM(require("fs"));
47242
- var path68 = __toESM(require("path"));
47716
+ var path67 = __toESM(require("path"));
47243
47717
  var crypto15 = __toESM(require("crypto"));
47244
47718
  function storePath10(dataDir) {
47245
- return path68.join(dataDir, "relations.json");
47719
+ return path67.join(dataDir, "relations.json");
47246
47720
  }
47247
47721
  function readRelations(dataDir) {
47248
47722
  const p = storePath10(dataDir);
@@ -47263,7 +47737,7 @@ function readRelations(dataDir) {
47263
47737
  }
47264
47738
  function persistRelations(dataDir, relations) {
47265
47739
  const p = storePath10(dataDir);
47266
- fs46.mkdirSync(path68.dirname(p), { recursive: true });
47740
+ fs46.mkdirSync(path67.dirname(p), { recursive: true });
47267
47741
  const tmp = `${p}.tmp`;
47268
47742
  fs46.writeFileSync(tmp, JSON.stringify(relations, null, 2) + "\n");
47269
47743
  fs46.renameSync(tmp, p);
@@ -47377,9 +47851,9 @@ function listProjectRelations(dataDir, sourceProjectId, targetProjectId, status2
47377
47851
 
47378
47852
  // src/server/surfaces.ts
47379
47853
  var fs47 = __toESM(require("fs"));
47380
- var path69 = __toESM(require("path"));
47854
+ var path68 = __toESM(require("path"));
47381
47855
  function storePath11(dataDir) {
47382
- return path69.join(dataDir, "public-surfaces.json");
47856
+ return path68.join(dataDir, "public-surfaces.json");
47383
47857
  }
47384
47858
  function readSnapshots(dataDir) {
47385
47859
  const p = storePath11(dataDir);
@@ -47400,7 +47874,7 @@ function readSnapshots(dataDir) {
47400
47874
  }
47401
47875
  function persistSnapshots(dataDir, snapshots) {
47402
47876
  const p = storePath11(dataDir);
47403
- fs47.mkdirSync(path69.dirname(p), { recursive: true });
47877
+ fs47.mkdirSync(path68.dirname(p), { recursive: true });
47404
47878
  const tmp = `${p}.tmp`;
47405
47879
  fs47.writeFileSync(tmp, JSON.stringify(snapshots, null, 2) + "\n");
47406
47880
  fs47.renameSync(tmp, p);
@@ -47546,12 +48020,12 @@ function resolveVisibility(observerProjectId, units, placements) {
47546
48020
  const best = /* @__PURE__ */ new Map();
47547
48021
  for (const placement of placements) {
47548
48022
  if (placement.projectId === observerProjectId) continue;
47549
- const path83 = chainOf(placement.unitId, unitById);
47550
- if (!path83.length) continue;
47551
- const closedOk = path83.every((u) => effectivePosture(u, unitById) !== "closed" || observerInside(u.id) || grantedTo(u));
48023
+ const path82 = chainOf(placement.unitId, unitById);
48024
+ if (!path82.length) continue;
48025
+ const closedOk = path82.every((u) => effectivePosture(u, unitById) !== "closed" || observerInside(u.id) || grantedTo(u));
47552
48026
  if (!closedOk) continue;
47553
- const crossTenant = !tenantRoots.has(path83[path83.length - 1].id);
47554
- if (crossTenant && !path83.some(grantedTo)) continue;
48027
+ const crossTenant = !tenantRoots.has(path82[path82.length - 1].id);
48028
+ if (crossTenant && !path82.some(grantedTo)) continue;
47555
48029
  if (crossTenant && directUnits.length === 0) continue;
47556
48030
  const distance = crossTenant ? "partner" : sameBranch(placement.unitId) ? "department" : "instance";
47557
48031
  const existing = best.get(placement.projectId);
@@ -48171,7 +48645,7 @@ function handleLandscapeRequest(cfg, credential, req, res, body, url) {
48171
48645
 
48172
48646
  // src/server/migration.ts
48173
48647
  var fs48 = __toESM(require("fs"));
48174
- var path70 = __toESM(require("path"));
48648
+ var path69 = __toESM(require("path"));
48175
48649
  var LEGACY_CAPABILITY_MAP = {
48176
48650
  "mcp:read": "project:read",
48177
48651
  "operations:read": "project:read",
@@ -48209,7 +48683,7 @@ function readRaw(file) {
48209
48683
  }
48210
48684
  }
48211
48685
  function writeRaw(file, value) {
48212
- fs48.mkdirSync(path70.dirname(file), { recursive: true });
48686
+ fs48.mkdirSync(path69.dirname(file), { recursive: true });
48213
48687
  const tmp = `${file}.tmp`;
48214
48688
  fs48.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n");
48215
48689
  fs48.renameSync(tmp, file);
@@ -48246,13 +48720,13 @@ function migratePermissionModel(dataDir, apply3) {
48246
48720
  findings.push({ area, detail });
48247
48721
  };
48248
48722
  const grid = readRaw(
48249
- path70.join(dataDir, "permissions.json")
48723
+ path69.join(dataDir, "permissions.json")
48250
48724
  ) ?? [];
48251
48725
  if (!getInstanceIdentity(dataDir)) {
48252
48726
  found("instance", "instance identity is not seeded \u2014 the built-in admin/dev subjects have no persisted UUIDs");
48253
48727
  if (apply3) ensureInstanceIdentity(dataDir);
48254
48728
  }
48255
- const orgPath = path70.join(dataDir, "organization.json");
48729
+ const orgPath = path69.join(dataDir, "organization.json");
48256
48730
  const org = readRaw(orgPath);
48257
48731
  if (org?.units?.length) {
48258
48732
  const units = org.units;
@@ -48306,7 +48780,7 @@ function migratePermissionModel(dataDir, apply3) {
48306
48780
  found("units", `${remap.length} unit id(s) would be remapped across placements, exposeTo, assignments, and users`);
48307
48781
  }
48308
48782
  }
48309
- const usersPath = path70.join(dataDir, "users.json");
48783
+ const usersPath = path69.join(dataDir, "users.json");
48310
48784
  const users = readRaw(usersPath);
48311
48785
  if (users) {
48312
48786
  let changed = false;
@@ -48333,7 +48807,7 @@ function migratePermissionModel(dataDir, apply3) {
48333
48807
  }
48334
48808
  if (apply3 && changed) writeRaw(usersPath, users);
48335
48809
  }
48336
- const keysPath = path70.join(dataDir, "auth", "credentials.json");
48810
+ const keysPath = path69.join(dataDir, "auth", "credentials.json");
48337
48811
  const keys = readRaw(keysPath);
48338
48812
  if (keys) {
48339
48813
  let changed = false;
@@ -48383,7 +48857,7 @@ function migratePermissionModel(dataDir, apply3) {
48383
48857
  }
48384
48858
  if (apply3 && changed) writeRaw(keysPath, keys);
48385
48859
  }
48386
- const sessionsPath = path70.join(dataDir, "web-sessions.json");
48860
+ const sessionsPath = path69.join(dataDir, "web-sessions.json");
48387
48861
  const sessions = readRaw(sessionsPath);
48388
48862
  if (sessions) {
48389
48863
  let changed = false;
@@ -48463,7 +48937,7 @@ function migratePermissionModel(dataDir, apply3) {
48463
48937
  // src/server/http.ts
48464
48938
  var http2 = __toESM(require("http"));
48465
48939
  var fs56 = __toESM(require("fs"));
48466
- var path78 = __toESM(require("path"));
48940
+ var path77 = __toESM(require("path"));
48467
48941
 
48468
48942
  // src/server/request.ts
48469
48943
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
@@ -48477,10 +48951,10 @@ function createScopedServer() {
48477
48951
 
48478
48952
  // src/server/approvals.ts
48479
48953
  var fs49 = __toESM(require("fs"));
48480
- var path71 = __toESM(require("path"));
48954
+ var path70 = __toESM(require("path"));
48481
48955
  var crypto16 = __toESM(require("crypto"));
48482
48956
  function storePath12(dataDir) {
48483
- return path71.join(dataDir, "approvals.json");
48957
+ return path70.join(dataDir, "approvals.json");
48484
48958
  }
48485
48959
  function readRequests(dataDir) {
48486
48960
  const p = storePath12(dataDir);
@@ -48501,7 +48975,7 @@ function readRequests(dataDir) {
48501
48975
  }
48502
48976
  function persistRequests(dataDir, requests) {
48503
48977
  const p = storePath12(dataDir);
48504
- fs49.mkdirSync(path71.dirname(p), { recursive: true });
48978
+ fs49.mkdirSync(path70.dirname(p), { recursive: true });
48505
48979
  const tmp = `${p}.tmp`;
48506
48980
  fs49.writeFileSync(tmp, JSON.stringify(requests, null, 2) + "\n");
48507
48981
  fs49.renameSync(tmp, p);
@@ -48634,6 +49108,33 @@ var ApprovalRegistry = class {
48634
49108
  this.store.replaceAll(next);
48635
49109
  return completed2;
48636
49110
  }
49111
+ /**
49112
+ * Locate the request by id (not-found error when absent) and transition it to
49113
+ * cancelled only from an approved status (any other status is a validation
49114
+ * error), recording the reason as its executionSummary, then persist and
49115
+ * refresh the store. Used when the approved workflow refused because the
49116
+ * request no longer describes what would be done; a cancelled request is
49117
+ * final and is never executed.
49118
+ */
49119
+ cancel(id, reason) {
49120
+ const requests = this.store.all();
49121
+ const idx = requests.findIndex((r) => r.id === id);
49122
+ if (idx === -1) {
49123
+ throw new Error(`Approval request "${id}" not found.`);
49124
+ }
49125
+ const current2 = requests[idx];
49126
+ if (current2.status !== "approved") {
49127
+ throw new Error(
49128
+ `Approval request "${id}" is ${current2.status}, not approved; only an approved request can be cancelled.`
49129
+ );
49130
+ }
49131
+ const cancelled = { ...current2, status: "cancelled", executionSummary: reason };
49132
+ const next = [...requests];
49133
+ next[idx] = cancelled;
49134
+ persistRequests(this.dataDir, next);
49135
+ this.store.replaceAll(next);
49136
+ return cancelled;
49137
+ }
48637
49138
  /**
48638
49139
  * Flip every pending request whose expiresAt is at or before the supplied
48639
49140
  * now-timestamp to expired and return the count flipped. A set with no overdue
@@ -48705,6 +49206,11 @@ function markApprovalCompleted(dataDir, id, executionSummary) {
48705
49206
  store.load();
48706
49207
  return new ApprovalRegistry(dataDir, store).markCompleted(id, executionSummary);
48707
49208
  }
49209
+ function cancelApprovalRequest(dataDir, id, reason) {
49210
+ const store = new ApprovalStore(dataDir);
49211
+ store.load();
49212
+ return new ApprovalRegistry(dataDir, store).cancel(id, reason);
49213
+ }
48708
49214
  function expirePendingApprovals(dataDir, now) {
48709
49215
  const store = new ApprovalStore(dataDir);
48710
49216
  store.load();
@@ -48863,12 +49369,16 @@ function lockProject2(cfg, credential, projectId, subproject) {
48863
49369
  return {
48864
49370
  status: "completed",
48865
49371
  action: "project:lock",
48866
- summary: `Locked project "${projectId}"${subprojectSuffix(subproject)} (status: ${lock.status}).`,
49372
+ summary: `Locked project "${projectId}"${subprojectSuffix(subproject)} (status: ${lock.status}). ${codeBesideClaim(lock)}.`,
48867
49373
  lock
48868
49374
  };
48869
49375
  }
48870
49376
  });
48871
49377
  }
49378
+ function codeBesideClaim(lock) {
49379
+ if (!lock.code) return "code: no code analysis recorded";
49380
+ return `code: ${lock.code.errors} errors recorded beside the claim (${lock.code.warnings} warnings; analyzer ${lock.code.analyzer.validatorVersion}, grade ${lock.code.analyzer.grade})`;
49381
+ }
48872
49382
  function subprojectSuffix(subproject) {
48873
49383
  return subproject ? ` subproject "${subproject}"` : "";
48874
49384
  }
@@ -48909,12 +49419,13 @@ function lifecycleAction(cfg, credential, projectId, opts) {
48909
49419
  if (!existingProjectRoot(cfg.dataDir, projectId)) {
48910
49420
  throw new Error(`Unknown project "${projectId}".`);
48911
49421
  }
49422
+ const gateStateId = gateIdentity(cfg, projectId, opts.subproject);
48912
49423
  const pending = buildPendingRequest(
48913
49424
  principal,
48914
49425
  opts.action,
48915
49426
  `${opts.verb} project ${projectId}${subprojectSuffix(opts.subproject)}`,
48916
49427
  projectId,
48917
- subprojectScopePayload(opts.subproject)
49428
+ { ...subprojectScopePayload(opts.subproject), gateStateId }
48918
49429
  );
48919
49430
  return createPendingOutcome(cfg, principal, pending, opts.action);
48920
49431
  }
@@ -48997,13 +49508,31 @@ function executeApproved(cfg, req) {
48997
49508
  }
48998
49509
  case "project:lock": {
48999
49510
  const scope = readSubprojectScope(req);
49000
- const lock = executeApprovedLock(cfg, req.projectId ?? "", hostedApprover(req.decidedBy), scope);
49001
- return { outcome: `Locked project "${req.projectId}"${subprojectSuffix(scope)} (status: ${lock.status}).` };
49511
+ const lock = lockAsRequested(cfg, req, scope);
49512
+ return {
49513
+ outcome: `Locked project "${req.projectId}"${subprojectSuffix(scope)} (status: ${lock.status}). ${codeBesideClaim(lock)}.`
49514
+ };
49002
49515
  }
49003
49516
  default:
49004
49517
  throw new Error(`Unsupported approval kind "${req.kind}".`);
49005
49518
  }
49006
49519
  }
49520
+ function lockAsRequested(cfg, req, scope) {
49521
+ try {
49522
+ return executeApprovedLock(cfg, req.projectId ?? "", hostedApprover(req.decidedBy), scope, req.gateStateId);
49523
+ } catch (err) {
49524
+ const moved = designMovedSince(err);
49525
+ if (!moved) throw err;
49526
+ const reason = `the design changed since it was requested (requested ${moved.requested}, now ${moved.now}); request the lock again`;
49527
+ cancelApprovalRequest(cfg.dataDir, req.id, reason);
49528
+ throw new Error(`Approval request "${req.id}" was cancelled: ${reason}.`);
49529
+ }
49530
+ }
49531
+ function designMovedSince(err) {
49532
+ if (!(err instanceof Error) || err.name !== "LockRefusedError") return null;
49533
+ const { requested, now } = err;
49534
+ return typeof requested === "string" && typeof now === "string" ? { requested, now } : null;
49535
+ }
49007
49536
  function executeApprovedRequest(cfg, credential, requestId) {
49008
49537
  const principal = requirePrincipal7(cfg, credential);
49009
49538
  const req = getApprovalRequestById(cfg.dataDir, requestId);
@@ -49042,7 +49571,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
49042
49571
  let current2 = req;
49043
49572
  while (current2.status === "pending" && Date.now() < deadline) {
49044
49573
  const remaining = deadline - Date.now();
49045
- await new Promise((resolve39) => setTimeout(resolve39, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
49574
+ await new Promise((resolve40) => setTimeout(resolve40, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
49046
49575
  expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
49047
49576
  current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
49048
49577
  }
@@ -49330,11 +49859,11 @@ function handleOperationsRequest(cfg, credential, req, res, url) {
49330
49859
 
49331
49860
  // src/server/gitbacking.ts
49332
49861
  var fs50 = __toESM(require("fs"));
49333
- var path72 = __toESM(require("path"));
49862
+ var path71 = __toESM(require("path"));
49334
49863
  var crypto17 = __toESM(require("crypto"));
49335
49864
  var import_child_process6 = require("child_process");
49336
49865
  function storePath13(dataDir) {
49337
- return path72.join(dataDir, "git-backing.json");
49866
+ return path71.join(dataDir, "git-backing.json");
49338
49867
  }
49339
49868
  var GitBackingStore = class {
49340
49869
  constructor(dataDir) {
@@ -49365,7 +49894,7 @@ var GitBackingStore = class {
49365
49894
  /** Swap the persisted collection wholesale via write-temp-then-rename. */
49366
49895
  replaceAll(bindings) {
49367
49896
  const p = storePath13(this.dataDir);
49368
- fs50.mkdirSync(path72.dirname(p), { recursive: true });
49897
+ fs50.mkdirSync(path71.dirname(p), { recursive: true });
49369
49898
  const tmp = `${p}.tmp`;
49370
49899
  fs50.writeFileSync(tmp, JSON.stringify(bindings, null, 2) + "\n");
49371
49900
  fs50.renameSync(tmp, p);
@@ -49473,7 +50002,7 @@ function checkoutBranch(workdir, branch) {
49473
50002
  }
49474
50003
  }
49475
50004
  function cloneOrOpen(remote, branch, workdir, credentialRef) {
49476
- if (!fs50.existsSync(path72.join(workdir, ".git"))) {
50005
+ if (!fs50.existsSync(path71.join(workdir, ".git"))) {
49477
50006
  fs50.mkdirSync(workdir, { recursive: true });
49478
50007
  git2(["clone", authRemote2(remote, credentialRef), "."], workdir);
49479
50008
  git2(["config", "user.name", process.env["WAIRON_GIT_NAME"] || "wairon-bot"], workdir);
@@ -49489,7 +50018,7 @@ function mirrorTree(sourceDir, targetDir) {
49489
50018
  fs50.rmSync(targetDir, { recursive: true, force: true });
49490
50019
  const stat = fs50.statSync(sourceDir, { throwIfNoEntry: false });
49491
50020
  if (!stat) return;
49492
- fs50.mkdirSync(path72.dirname(targetDir), { recursive: true });
50021
+ fs50.mkdirSync(path71.dirname(targetDir), { recursive: true });
49493
50022
  fs50.cpSync(sourceDir, targetDir, { recursive: true });
49494
50023
  }
49495
50024
  function commitAndPush(workdir, message) {
@@ -49612,7 +50141,7 @@ function unbindScope(cfg, credential, bindingId) {
49612
50141
  tryAppendAudit7(cfg, buildAuditEvent6(principal, "git.backing.unbind", "security", bindingId));
49613
50142
  }
49614
50143
  function runMirrorSync(cfg, binding) {
49615
- const workdir = path72.join(cfg.dataDir, "git-backing", binding.id);
50144
+ const workdir = path71.join(cfg.dataDir, "git-backing", binding.id);
49616
50145
  cloneOrOpen(binding.remote, binding.branch, workdir, binding.credentialRef);
49617
50146
  if (binding.scopeKind === "unit") {
49618
50147
  const units = listOrganizationUnits(cfg.dataDir);
@@ -49620,23 +50149,23 @@ function runMirrorSync(cfg, binding) {
49620
50149
  const placedIds = new Set(
49621
50150
  listProjectPlacements(cfg.dataDir).filter((p) => subtree.has(p.unitId)).map((p) => p.projectId)
49622
50151
  );
49623
- const projectsDir = path72.join(workdir, "projects");
50152
+ const projectsDir = path71.join(workdir, "projects");
49624
50153
  fs50.rmSync(projectsDir, { recursive: true, force: true });
49625
50154
  for (const rec of listProjectRecords(cfg.dataDir)) {
49626
50155
  if (!placedIds.has(rec.id)) continue;
49627
- mirrorTree(path72.join(rec.rootPath, ".wai"), path72.join(projectsDir, rec.id, ".wai"));
50156
+ mirrorTree(path71.join(rec.rootPath, ".wai"), path71.join(projectsDir, rec.id, ".wai"));
49628
50157
  }
49629
50158
  } else {
49630
- const instanceDir = path72.join(workdir, "instance");
50159
+ const instanceDir = path71.join(workdir, "instance");
49631
50160
  fs50.rmSync(instanceDir, { recursive: true, force: true });
49632
50161
  const files = binding.includeCredentials ? INSTANCE_STRUCTURE_FILES : INSTANCE_STRUCTURE_FILES.filter((rel2) => rel2 !== "auth/credentials.json");
49633
50162
  for (const rel2 of files) {
49634
- mirrorTree(path72.join(cfg.dataDir, rel2), path72.join(instanceDir, rel2));
50163
+ mirrorTree(path71.join(cfg.dataDir, rel2), path71.join(instanceDir, rel2));
49635
50164
  }
49636
- const projectsDir = path72.join(workdir, "projects");
50165
+ const projectsDir = path71.join(workdir, "projects");
49637
50166
  fs50.rmSync(projectsDir, { recursive: true, force: true });
49638
50167
  for (const rec of listProjectRecords(cfg.dataDir)) {
49639
- mirrorTree(path72.join(rec.rootPath, ".wai"), path72.join(projectsDir, rec.id, ".wai"));
50168
+ mirrorTree(path71.join(rec.rootPath, ".wai"), path71.join(projectsDir, rec.id, ".wai"));
49640
50169
  }
49641
50170
  }
49642
50171
  const published = commitAndPush(
@@ -49960,7 +50489,7 @@ function encodeFrame(opcode, payload) {
49960
50489
  // src/server/web.ts
49961
50490
  var crypto23 = __toESM(require("crypto"));
49962
50491
  var fs55 = __toESM(require("fs"));
49963
- var path77 = __toESM(require("path"));
50492
+ var path76 = __toESM(require("path"));
49964
50493
 
49965
50494
  // src/server/webadmin.ts
49966
50495
  function listUsers3(cfg, sessionId, project2) {
@@ -50215,14 +50744,14 @@ var crypto22 = __toESM(require("crypto"));
50215
50744
 
50216
50745
  // src/server/sharesnapshots.ts
50217
50746
  var fs51 = __toESM(require("fs"));
50218
- var path73 = __toESM(require("path"));
50747
+ var path72 = __toESM(require("path"));
50219
50748
  var crypto19 = __toESM(require("crypto"));
50220
50749
  init_fs();
50221
50750
  function snapshotDir(dataDir) {
50222
- return path73.join(dataDir, "share-snapshots");
50751
+ return path72.join(dataDir, "share-snapshots");
50223
50752
  }
50224
50753
  function snapshotPath2(dataDir, id) {
50225
- return path73.join(snapshotDir(dataDir), `${id}.json`);
50754
+ return path72.join(snapshotDir(dataDir), `${id}.json`);
50226
50755
  }
50227
50756
  var ShareSnapshotStore = class {
50228
50757
  constructor(dataDir) {
@@ -50231,7 +50760,7 @@ var ShareSnapshotStore = class {
50231
50760
  write(snapshot) {
50232
50761
  const p = snapshotPath2(this.dataDir, snapshot.id);
50233
50762
  if (fs51.existsSync(p)) return;
50234
- fs51.mkdirSync(path73.dirname(p), { recursive: true });
50763
+ fs51.mkdirSync(path72.dirname(p), { recursive: true });
50235
50764
  const tmp = `${p}.tmp`;
50236
50765
  fs51.writeFileSync(tmp, JSON.stringify(snapshot, null, 2) + "\n");
50237
50766
  fs51.renameSync(tmp, p);
@@ -50333,10 +50862,10 @@ function captureSnapshot(dataDir, principal, projectId, view, artifacts) {
50333
50862
 
50334
50863
  // src/server/sharelinks.ts
50335
50864
  var fs52 = __toESM(require("fs"));
50336
- var path74 = __toESM(require("path"));
50865
+ var path73 = __toESM(require("path"));
50337
50866
  var crypto20 = __toESM(require("crypto"));
50338
50867
  function storePath14(dataDir) {
50339
- return path74.join(dataDir, "share-links.json");
50868
+ return path73.join(dataDir, "share-links.json");
50340
50869
  }
50341
50870
  var ShareLinkStore = class {
50342
50871
  constructor(dataDir) {
@@ -50364,7 +50893,7 @@ var ShareLinkStore = class {
50364
50893
  }
50365
50894
  replaceAll(links) {
50366
50895
  const p = storePath14(this.dataDir);
50367
- fs52.mkdirSync(path74.dirname(p), { recursive: true });
50896
+ fs52.mkdirSync(path73.dirname(p), { recursive: true });
50368
50897
  const tmp = `${p}.tmp`;
50369
50898
  fs52.writeFileSync(tmp, JSON.stringify(links, null, 2) + "\n");
50370
50899
  fs52.renameSync(tmp, p);
@@ -50436,10 +50965,10 @@ function listProjectLinks(dataDir, projectId) {
50436
50965
 
50437
50966
  // src/server/shareaccesslog.ts
50438
50967
  var fs53 = __toESM(require("fs"));
50439
- var path75 = __toESM(require("path"));
50968
+ var path74 = __toESM(require("path"));
50440
50969
  var crypto21 = __toESM(require("crypto"));
50441
50970
  function storePath15(dataDir) {
50442
- return path75.join(dataDir, "share-access.json");
50971
+ return path74.join(dataDir, "share-access.json");
50443
50972
  }
50444
50973
  var ShareAccessStore = class {
50445
50974
  constructor(dataDir) {
@@ -50467,7 +50996,7 @@ var ShareAccessStore = class {
50467
50996
  }
50468
50997
  append(entry) {
50469
50998
  const p = storePath15(this.dataDir);
50470
- fs53.mkdirSync(path75.dirname(p), { recursive: true });
50999
+ fs53.mkdirSync(path74.dirname(p), { recursive: true });
50471
51000
  const next = [...this.load(), entry];
50472
51001
  const tmp = `${p}.tmp`;
50473
51002
  fs53.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n");
@@ -50623,15 +51152,15 @@ init_fs();
50623
51152
 
50624
51153
  // src/server/swagger.ts
50625
51154
  var fs54 = __toESM(require("fs"));
50626
- var path76 = __toESM(require("path"));
51155
+ var path75 = __toESM(require("path"));
50627
51156
  var cache;
50628
51157
  function loadAssets() {
50629
51158
  if (cache !== void 0) return cache;
50630
51159
  try {
50631
- const dir = path76.dirname(require.resolve("swagger-ui-dist/package.json"));
51160
+ const dir = path75.dirname(require.resolve("swagger-ui-dist/package.json"));
50632
51161
  cache = {
50633
- css: fs54.readFileSync(path76.join(dir, "swagger-ui.css"), "utf8"),
50634
- js: fs54.readFileSync(path76.join(dir, "swagger-ui-bundle.js"), "utf8")
51162
+ css: fs54.readFileSync(path75.join(dir, "swagger-ui.css"), "utf8"),
51163
+ js: fs54.readFileSync(path75.join(dir, "swagger-ui-bundle.js"), "utf8")
50635
51164
  };
50636
51165
  } catch {
50637
51166
  cache = null;
@@ -51043,11 +51572,11 @@ function nonceMatches(a, b) {
51043
51572
  }
51044
51573
  function loadReactBundle() {
51045
51574
  const candidates = [
51046
- path77.resolve(__dirname, "webapp.html"),
51575
+ path76.resolve(__dirname, "webapp.html"),
51047
51576
  // dist/index.js -> dist/webapp.html
51048
- path77.resolve(__dirname, "..", "webapp.html"),
51577
+ path76.resolve(__dirname, "..", "webapp.html"),
51049
51578
  // dist/cli/index.js -> dist/webapp.html
51050
- path77.resolve(__dirname, "..", "..", "web", "dist", "index.html")
51579
+ path76.resolve(__dirname, "..", "..", "web", "dist", "index.html")
51051
51580
  // tsx dev: src/server -> web/dist
51052
51581
  ];
51053
51582
  for (const candidate of candidates) {
@@ -53618,6 +54147,7 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
53618
54147
  const errors = err.errors;
53619
54148
  return sendJson(res, 409, { error: err.message, errors });
53620
54149
  }
54150
+ if (err instanceof Error && err.name === "LockRefusedError") return sendJson(res, 409, { error: err.message });
53621
54151
  const msg = err instanceof Error ? err.message : String(err);
53622
54152
  if (/unsupported graph tier/i.test(msg)) return sendJson(res, 400, { error: msg });
53623
54153
  if (/not found|unknown/i.test(msg)) return sendJson(res, 404, { error: msg });
@@ -53644,8 +54174,8 @@ var RealtimeHub = class {
53644
54174
  * complete the handshake, and register the connection. A bad path or session
53645
54175
  * destroys the socket. */
53646
54176
  handleUpgrade(cfg, req, socket) {
53647
- const path83 = (req.url ?? "/").split("?")[0];
53648
- if (path83 !== REALTIME_PATH || !isWebSocketUpgrade(req)) {
54177
+ const path82 = (req.url ?? "/").split("?")[0];
54178
+ if (path82 !== REALTIME_PATH || !isWebSocketUpgrade(req)) {
53649
54179
  socket.destroy();
53650
54180
  return;
53651
54181
  }
@@ -54653,7 +55183,7 @@ function routeData(cfg, req, res) {
54653
55183
  }
54654
55184
  function readExposurePolicyFile(dataDir) {
54655
55185
  try {
54656
- const raw = fs56.readFileSync(path78.join(dataDir, "exposure-policy.json"), "utf8");
55186
+ const raw = fs56.readFileSync(path77.join(dataDir, "exposure-policy.json"), "utf8");
54657
55187
  const parsed = JSON.parse(raw);
54658
55188
  return parsed && typeof parsed === "object" ? parsed : void 0;
54659
55189
  } catch {
@@ -54842,6 +55372,7 @@ async function routeAdmin(cfg, req, res) {
54842
55372
  if (err instanceof ForbiddenError) return sendJson(res, 403, { error: err.message });
54843
55373
  if (err instanceof AdminAuthError) return sendJson(res, 403, { error: "forbidden" });
54844
55374
  if (err instanceof LockValidationError) return sendJson(res, 409, { error: err.message, errors: err.errors });
55375
+ if (err instanceof Error && err.name === "LockRefusedError") return sendJson(res, 409, { error: err.message });
54845
55376
  sendJson(res, 400, { error: err instanceof Error ? err.message : String(err) });
54846
55377
  }
54847
55378
  }
@@ -55352,9 +55883,9 @@ function seedDemoTree() {
55352
55883
 
55353
55884
  // src/commands/host.ts
55354
55885
  function resolveHostConfig(options) {
55355
- const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] || path79.join(os11.homedir(), ".wairon", "data");
55886
+ const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] || path78.join(os11.homedir(), ".wairon", "data");
55356
55887
  if (!process.env["WAIRON_PACKS_DIR"]) {
55357
- process.env["WAIRON_PACKS_DIR"] = path79.join(dataDir, "packs");
55888
+ process.env["WAIRON_PACKS_DIR"] = path78.join(dataDir, "packs");
55358
55889
  }
55359
55890
  const cfg = {
55360
55891
  host: options.host || "0.0.0.0",
@@ -55411,11 +55942,11 @@ async function runServe(options = {}) {
55411
55942
  logger.info(` data dir: ${import_chalk17.default.gray(cfg.dataDir)}`);
55412
55943
  logger.blank();
55413
55944
  logger.info("Press Ctrl+C to stop.");
55414
- await new Promise((resolve39) => {
55945
+ await new Promise((resolve40) => {
55415
55946
  const shutdown = () => {
55416
55947
  logger.info("Shutting down\u2026");
55417
55948
  handle.close();
55418
- resolve39();
55949
+ resolve40();
55419
55950
  };
55420
55951
  process.on("SIGINT", shutdown);
55421
55952
  process.on("SIGTERM", shutdown);
@@ -55435,13 +55966,13 @@ function openBrowser(url) {
55435
55966
  }
55436
55967
  async function runDev(options = {}) {
55437
55968
  const cwd = process.cwd();
55438
- if (!fs57.existsSync(path79.join(cwd, ".wai"))) {
55969
+ if (!fs57.existsSync(path78.join(cwd, ".wai"))) {
55439
55970
  throw new WaironError(
55440
55971
  "No .wai/ found in the current directory. Run `wairon dev` from a wairon project root (or run `wairon init` first)."
55441
55972
  );
55442
55973
  }
55443
55974
  const hash = crypto24.createHash("sha256").update(cwd).digest("hex").slice(0, 16);
55444
- const dataDir = path79.join(os11.tmpdir(), "wairon-dev", hash);
55975
+ const dataDir = path78.join(os11.tmpdir(), "wairon-dev", hash);
55445
55976
  fs57.mkdirSync(dataDir, { recursive: true });
55446
55977
  const port = options.port ? Number(options.port) : 8080;
55447
55978
  const exposurePolicy = {
@@ -55480,11 +56011,11 @@ async function runDev(options = {}) {
55480
56011
  logger.blank();
55481
56012
  logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
55482
56013
  if (options.open) openBrowser(url);
55483
- await new Promise((resolve39) => {
56014
+ await new Promise((resolve40) => {
55484
56015
  const shutdown = () => {
55485
56016
  logger.info("Shutting down\u2026");
55486
56017
  handle.close();
55487
- resolve39();
56018
+ resolve40();
55488
56019
  };
55489
56020
  process.on("SIGINT", shutdown);
55490
56021
  process.on("SIGTERM", shutdown);
@@ -55880,8 +56411,8 @@ async function runHostPacks(action, options = {}) {
55880
56411
  }
55881
56412
  case "install": {
55882
56413
  if (!options.file) throw new WaironError("`--file <path>` (a declarative pack YAML) is required for install.");
55883
- const name = options.name ?? path79.basename(options.file).replace(/\.(ya?ml)$/i, "");
55884
- const content = fs57.readFileSync(path79.resolve(options.file), "utf8");
56414
+ const name = options.name ?? path78.basename(options.file).replace(/\.(ya?ml)$/i, "");
56415
+ const content = fs57.readFileSync(path78.resolve(options.file), "utf8");
55885
56416
  if (project2 && !hostPackWriteUnattended(options.yes)) {
55886
56417
  printHostPackImpact(previewProjectPack(cfg, cred, project2, name, content));
55887
56418
  if (!await askHostPackWrite(`Install the pack "${name}" into ${scope}?`)) {
@@ -56184,7 +56715,7 @@ async function runExternals(action, aliases, options = {}) {
56184
56715
 
56185
56716
  // src/commands/remote.ts
56186
56717
  var fs59 = __toESM(require("fs"));
56187
- var path81 = __toESM(require("path"));
56718
+ var path80 = __toESM(require("path"));
56188
56719
  var import_chalk21 = __toESM(require("chalk"));
56189
56720
  init_logger();
56190
56721
  init_errors();
@@ -56195,15 +56726,15 @@ init_mcp();
56195
56726
  // src/commands/remotebinding.ts
56196
56727
  var fs58 = __toESM(require("fs"));
56197
56728
  var os12 = __toESM(require("os"));
56198
- var path80 = __toESM(require("path"));
56729
+ var path79 = __toESM(require("path"));
56199
56730
  init_paths();
56200
56731
  var BINDING_FILE = "remote.json";
56201
56732
  var CREDENTIALS_FILE = "credentials.json";
56202
56733
  function bindingPath(root) {
56203
- return path80.join(aiPathsAt(root).root(), BINDING_FILE);
56734
+ return path79.join(aiPathsAt(root).root(), BINDING_FILE);
56204
56735
  }
56205
56736
  function credentialsPath() {
56206
- return path80.join(os12.homedir(), ".wairon", CREDENTIALS_FILE);
56737
+ return path79.join(os12.homedir(), ".wairon", CREDENTIALS_FILE);
56207
56738
  }
56208
56739
  function normalizeInstanceUrl(url) {
56209
56740
  return url.trim().replace(/\/+$/, "").toLowerCase();
@@ -56217,7 +56748,7 @@ function readJson(file) {
56217
56748
  }
56218
56749
  }
56219
56750
  function writeJsonAtomic(file, value, mode) {
56220
- fs58.mkdirSync(path80.dirname(file), { recursive: true });
56751
+ fs58.mkdirSync(path79.dirname(file), { recursive: true });
56221
56752
  const tmp = `${file}.tmp`;
56222
56753
  fs58.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n", mode !== void 0 ? { mode } : void 0);
56223
56754
  fs58.renameSync(tmp, file);
@@ -56559,7 +57090,7 @@ async function runRemote(action, options = {}) {
56559
57090
  allowPartial: options.allowPartial === true
56560
57091
  };
56561
57092
  if (options.archive) transfer.archivePath = options.archive;
56562
- if (options.dir) transfer.destDir = path81.resolve(options.dir);
57093
+ if (options.dir) transfer.destDir = path80.resolve(options.dir);
56563
57094
  report2(await pullTree(target, transfer));
56564
57095
  return;
56565
57096
  }
@@ -56643,9 +57174,9 @@ function topProjectId2(selector) {
56643
57174
  }
56644
57175
  function writeArchiveIfAsked(archive, archivePath, suggestedFileName) {
56645
57176
  if (!archivePath) return void 0;
56646
- const resolved = path81.resolve(archivePath);
56647
- const target = fs59.existsSync(resolved) && fs59.statSync(resolved).isDirectory() ? path81.join(resolved, suggestedFileName) : resolved;
56648
- fs59.mkdirSync(path81.dirname(target), { recursive: true });
57177
+ const resolved = path80.resolve(archivePath);
57178
+ const target = fs59.existsSync(resolved) && fs59.statSync(resolved).isDirectory() ? path80.join(resolved, suggestedFileName) : resolved;
57179
+ fs59.mkdirSync(path80.dirname(target), { recursive: true });
56649
57180
  fs59.writeFileSync(target, Buffer.from(archive));
56650
57181
  return target;
56651
57182
  }
@@ -56791,14 +57322,15 @@ program.command("init").description("Initialize wairon and bootstrap the SDD Spe
56791
57322
  await addPack(source, false, opts.yes);
56792
57323
  }
56793
57324
  });
56794
- program.command("generate").description("Generate agent output files from the spec tree").option("--target <type>", "limit to a specific target (claude|gemini|custom)").option("--domain <id>", "limit to agents in a single domain").option("--domains <ids>", "limit to a comma-separated list of domain ids").option("--root", "only generate root-level agents").option("--no-recurse", "only generate this project's layer; do not cascade into chained subprojects").option("--no-prune", "do not remove wairon-managed agent files that are no longer in the topology").option("--dry-run", "preview what would be generated without writing files").action(async (opts) => {
57325
+ program.command("generate").description("Generate agent output files from the spec tree").option("--target <type>", "limit to a specific target (claude|gemini|custom)").option("--domain <id>", "limit to agents in a single domain").option("--domains <ids>", "limit to a comma-separated list of domain ids").option("--root", "only generate root-level agents").option("--family", "also generate each member's own layer in its own root (generate never cascades otherwise)").option("--no-recurse", "accepted for one release: generating only this project's layer is now the default").option("--no-prune", "do not remove wairon-managed agent files that are no longer in the topology").option("--dry-run", "preview what would be generated without writing files").action(async (opts) => {
56795
57326
  await runGenerate({
56796
57327
  target: opts.target,
56797
57328
  domain: opts.domain,
56798
57329
  domains: opts.domains,
56799
57330
  root: opts.root,
56800
- // commander maps --no-recurse / --no-prune to opts.recurse/opts.prune === false
56801
- recurse: opts.recurse,
57331
+ // --family walks the members explicitly; --no-recurse is the default now
57332
+ // and is accepted for one release. commander maps --no-prune to opts.prune === false.
57333
+ family: opts.family === true,
56802
57334
  prune: opts.prune,
56803
57335
  dryRun: opts.dryRun
56804
57336
  });
@@ -56811,16 +57343,17 @@ async function runLock2(options) {
56811
57343
  }
56812
57344
  const projectConfig = loadProjectConfig();
56813
57345
  if (!projectConfig) throw new ProjectNotInitializedError();
57346
+ const captured = computeGateStateId();
56814
57347
  logger.info("Analyzing and validating specifications in-memory...");
56815
57348
  const dry = validateAsComplete({
56816
57349
  rules: projectConfig.rules,
56817
57350
  projectType: projectConfig.projectType,
56818
- scopeSubsystem: options.subsystem,
56819
- recursive: options.recursive ?? true
57351
+ scopeSubsystem: options.subsystem
56820
57352
  });
56821
- const errors = dry.issues.filter((i) => i.severity === "error");
57353
+ const design = designOnly(dry);
57354
+ const errors = design.issues.filter((i) => i.severity === "error");
56822
57355
  if (errors.length > 0) {
56823
- logger.header("Cannot lock \u2014 the spec tree does not validate as complete");
57356
+ logger.header("Cannot lock \u2014 the design does not validate as complete");
56824
57357
  let errorCount = 0;
56825
57358
  const MAX_PRINT = 100;
56826
57359
  let skippedErrors = 0;
@@ -56840,7 +57373,7 @@ async function runLock2(options) {
56840
57373
  process.exit(1);
56841
57374
  }
56842
57375
  logger.header("Lock SDD specs");
56843
- const record2 = await runLock(options, dry);
57376
+ const record2 = await runLock(options, dry, captured);
56844
57377
  if (!record2) {
56845
57378
  logger.info("Cancelled. Nothing was changed.");
56846
57379
  return;
@@ -56850,6 +57383,11 @@ async function runLock2(options) {
56850
57383
  logger.blank();
56851
57384
  logger.success("Specs locked and generated outputs reconciled.");
56852
57385
  logger.info(`Lock record written (.wai/lock.json): stateId ${record2.stateId.algorithm}:${record2.stateId.digest} \u2014 status ${record2.status}.`);
57386
+ for (const [alias, pin2] of Object.entries(record2.members ?? {})) {
57387
+ logger.info(` member ${alias}: ${pin2.state}${pin2.subject ? ` (${pin2.subject.slice(0, 26)}\u2026)` : ""}`);
57388
+ }
57389
+ const code = record2.code;
57390
+ logger.info(code ? `code: ${code.errors} error(s), ${code.warnings} warning(s) recorded beside the claim (analyzer ${code.analyzer.validatorVersion}, grade ${code.analyzer.grade}) \u2014 CI enforces them, not the lock` : "code: no code analysis recorded");
56853
57391
  logger.info(
56854
57392
  "Live agent briefs (sdd_get_agent_brief / wairon-agent://) are composed per call and already current \u2014 no session restart needed."
56855
57393
  );
@@ -57067,7 +57605,7 @@ async function runAgent(action, id) {
57067
57605
  }
57068
57606
  case "customize": {
57069
57607
  const brief = composeAgentBrief(id);
57070
- const guidancePath = path82.join(AI_PATHS.root(), "agents", `${id}.md`);
57608
+ const guidancePath = path81.join(AI_PATHS.root(), "agents", `${id}.md`);
57071
57609
  const relPath = `.wai/agents/${id}.md`;
57072
57610
  if (pathExists(guidancePath)) {
57073
57611
  throw new GuidanceFileExistsError(relPath);