@wairon/cli 5.1.1-dev.26 → 5.1.1-dev.27

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/index.js CHANGED
@@ -1113,7 +1113,7 @@ var init_defaults = __esm({
1113
1113
  copilot: ".github/prompts",
1114
1114
  codex: ".codex/agents"
1115
1115
  };
1116
- WAIRON_VERSION = "5.1.1-dev.26";
1116
+ WAIRON_VERSION = "5.1.1-dev.27";
1117
1117
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1118
1118
  ARCHITECT_AGENT_ID = "agent-architect";
1119
1119
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -2253,8 +2253,8 @@ function extractTypeIdentifiers(typeStr) {
2253
2253
  cleaned = cleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
2254
2254
  cleaned = cleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
2255
2255
  cleaned = cleaned.replace(/(["'`])(?:\\.|[^\\])*?\1/g, " ");
2256
- const matches2 = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
2257
- return matches2.filter((t) => {
2256
+ const matches = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
2257
+ return matches.filter((t) => {
2258
2258
  if (!/[a-zA-Z0-9]/.test(t)) return false;
2259
2259
  if (/^\d+$/.test(t)) return false;
2260
2260
  return true;
@@ -8877,6 +8877,37 @@ function buildDependencyEdges(ctx) {
8877
8877
  }
8878
8878
  return { all, matrix };
8879
8879
  }
8880
+ function buildSpecIds(ctx) {
8881
+ const out = [];
8882
+ for (const s of ctx.subsystems) out.push({ id: s.id, kind: "Subsystem" });
8883
+ for (const c of ctx.components) out.push({ id: c.id, kind: "Component" });
8884
+ for (const i of ctx.interfaces) out.push({ id: i.id, kind: "Interface" });
8885
+ for (const im of ctx.implementations) out.push({ id: im.id, kind: "Implementation" });
8886
+ for (const t of ctx.types) out.push({ id: t.id, kind: "Type" });
8887
+ return out;
8888
+ }
8889
+ function buildImplementationMethods(ctx) {
8890
+ const out = [];
8891
+ for (const impl of ctx.implementations) {
8892
+ const contract = ctx.interfaceMap.get(impl.contract);
8893
+ if (!contract) continue;
8894
+ const component = ctx.componentMap.get(contract.component);
8895
+ if (!component) continue;
8896
+ if (ctx.isInChainedSubproject(component.subsystem)) continue;
8897
+ const draftContext = ctx.isImplementationDraft(impl);
8898
+ for (const method of impl.methods) {
8899
+ const sourceFile = methodSourceFile(method, impl.sourcePath);
8900
+ out.push({
8901
+ implementation: impl,
8902
+ method,
8903
+ component,
8904
+ ...sourceFile !== void 0 ? { sourceFile } : {},
8905
+ draftContext
8906
+ });
8907
+ }
8908
+ }
8909
+ return out;
8910
+ }
8880
8911
  function offTreeEdge(ctx, from, ref, fromDraft) {
8881
8912
  const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
8882
8913
  const external = ctx.isExternalNamespaceRef(ref);
@@ -8977,48 +9008,58 @@ var init_hierarchy_integrity = __esm({
8977
9008
  }
8978
9009
  });
8979
9010
 
8980
- // src/core/rules/integrity/namespace-hygiene.ts
8981
- var namespaceHygieneRule;
8982
- var init_namespace_hygiene = __esm({
8983
- "src/core/rules/integrity/namespace-hygiene.ts"() {
9011
+ // src/core/rules/integrity/reserved-id-segments.ts
9012
+ var reservedIdSegmentsRule;
9013
+ var init_reserved_id_segments = __esm({
9014
+ "src/core/rules/integrity/reserved-id-segments.ts"() {
8984
9015
  "use strict";
8985
- namespaceHygieneRule = {
8986
- name: "namespace-hygiene",
8987
- description: 'Ids must stay resolvable in the :: namespace grammar: no id segment may be the reserved keyword "super", and a subproject-local name must not shadow a root-level subsystem id \u2014 a bare reference to a shadowed name silently anchors to the ROOT subsystem, so the local spec becomes unaddressable.',
9016
+ reservedIdSegmentsRule = {
9017
+ name: "reserved-id-segments",
9018
+ 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.',
9019
+ codes: [
9020
+ { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
9021
+ ],
9022
+ check(ctx) {
9023
+ for (const spec of ctx.specIds()) {
9024
+ if (!ctx.isSpecInScope(spec.id)) continue;
9025
+ if (!spec.id.split("::").includes("super")) continue;
9026
+ ctx.addIssue(
9027
+ "error",
9028
+ "RESERVED_ID_SEGMENT",
9029
+ `${spec.kind} id "${spec.id}" uses the reserved namespace keyword "super" \u2014 stored references to it would be consumed as a namespace hop and resolve to a different spec.`,
9030
+ spec.id
9031
+ );
9032
+ }
9033
+ }
9034
+ };
9035
+ }
9036
+ });
9037
+
9038
+ // src/core/rules/integrity/namespace-shadowing.ts
9039
+ var namespaceShadowingRule;
9040
+ var init_namespace_shadowing = __esm({
9041
+ "src/core/rules/integrity/namespace-shadowing.ts"() {
9042
+ "use strict";
9043
+ namespaceShadowingRule = {
9044
+ name: "namespace-shadowing",
9045
+ description: "A subproject-local name must not shadow a root-level subsystem id \u2014 inside its subproject a bare reference to the shadowed name anchors to the ROOT subsystem, so the local spec cannot be addressed reliably.",
8988
9046
  codes: [
8989
- { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' },
8990
9047
  { code: "NAMESPACE_SHADOWING", defaultSeverity: "error", summary: "Subproject-local id shadows a root subsystem id (bare references anchor to the root)" }
8991
9048
  ],
8992
9049
  check(ctx) {
8993
9050
  const rootSubs = new Set(ctx.subsystems.filter((s) => !s.id.includes("::")).map((s) => s.id));
8994
- const checkId = (id, kindLabel) => {
8995
- if (!ctx.isSpecInScope(id)) return;
8996
- const segments = id.split("::");
8997
- if (segments.includes("super")) {
8998
- ctx.addIssue(
8999
- "error",
9000
- "RESERVED_ID_SEGMENT",
9001
- `${kindLabel} id "${id}" uses the reserved namespace keyword "super" \u2014 stored references to it would be consumed as a namespace hop and resolve to a different spec.`,
9002
- id
9003
- );
9004
- }
9005
- if (segments.length > 1) {
9006
- const local = segments[segments.length - 1];
9007
- if (rootSubs.has(local)) {
9008
- ctx.addIssue(
9009
- "error",
9010
- "NAMESPACE_SHADOWING",
9011
- `${kindLabel} "${id}" shadows root subsystem "${local}" \u2014 inside its subproject a bare reference to "${local}" anchors to the ROOT subsystem, so this spec cannot be addressed reliably. Rename one of them.`,
9012
- id
9013
- );
9014
- }
9015
- }
9016
- };
9017
- for (const s of ctx.subsystems) checkId(s.id, "Subsystem");
9018
- for (const c of ctx.components) checkId(c.id, "Component");
9019
- for (const i of ctx.interfaces) checkId(i.id, "Interface");
9020
- for (const im of ctx.implementations) checkId(im.id, "Implementation");
9021
- for (const t of ctx.types) checkId(t.id, "Type");
9051
+ for (const spec of ctx.specIds()) {
9052
+ if (!ctx.isSpecInScope(spec.id)) continue;
9053
+ const segments = spec.id.split("::");
9054
+ const local = segments[segments.length - 1];
9055
+ if (segments.length <= 1 || !rootSubs.has(local)) continue;
9056
+ ctx.addIssue(
9057
+ "error",
9058
+ "NAMESPACE_SHADOWING",
9059
+ `${spec.kind} "${spec.id}" shadows root subsystem "${local}" \u2014 inside its subproject a bare reference to "${local}" anchors to the ROOT subsystem, so this spec cannot be addressed reliably. Rename one of them.`,
9060
+ spec.id
9061
+ );
9062
+ }
9022
9063
  }
9023
9064
  };
9024
9065
  }
@@ -9163,10 +9204,48 @@ var init_signature_type_references = __esm({
9163
9204
  }
9164
9205
  });
9165
9206
 
9166
- // src/core/rules/integrity/public-surface.ts
9167
- var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceRule;
9168
- var init_public_surface = __esm({
9169
- "src/core/rules/integrity/public-surface.ts"() {
9207
+ // src/core/rules/integrity/public-surface-binding.ts
9208
+ var publicSurfaceBindingRule;
9209
+ var init_public_surface_binding = __esm({
9210
+ "src/core/rules/integrity/public-surface-binding.ts"() {
9211
+ "use strict";
9212
+ init_models();
9213
+ publicSurfaceBindingRule = {
9214
+ name: "public-surface-binding",
9215
+ 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.",
9216
+ codes: [
9217
+ { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9218
+ { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9219
+ { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" }
9220
+ ],
9221
+ check(ctx) {
9222
+ for (const sub of ctx.subsystems) {
9223
+ const isDraftCtx = isDraftSubsystem(sub);
9224
+ for (const pi of sub.publicInterfaces) {
9225
+ if (!pi.component) {
9226
+ ctx.addIssue("error", "PUBLIC_INTERFACE_UNBOUND", `Subsystem "${sub.id}" declares a ${pi.type} public interface with no backing component. Bind it to the component that realizes it (publicInterfaces[].component).`, sub.id, isDraftCtx);
9227
+ continue;
9228
+ }
9229
+ const backing = ctx.componentMap.get(pi.component);
9230
+ if (!backing) {
9231
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9232
+ continue;
9233
+ }
9234
+ const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9235
+ if (!isSubsystemOwner) {
9236
+ ctx.addIssue("error", "PUBLIC_INTERFACE_FOREIGN_COMPONENT", `Subsystem "${sub.id}" publishes component "${pi.component}", but it belongs to subsystem "${backing.subsystem}". A subsystem may only publish its own components.`, sub.id, isDraftCtx);
9237
+ }
9238
+ }
9239
+ }
9240
+ }
9241
+ };
9242
+ }
9243
+ });
9244
+
9245
+ // src/core/rules/integrity/public-surface-declared-type.ts
9246
+ var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceDeclaredTypeRule;
9247
+ var init_public_surface_declared_type = __esm({
9248
+ "src/core/rules/integrity/public-surface-declared-type.ts"() {
9170
9249
  "use strict";
9171
9250
  init_models();
9172
9251
  pubTypeMatches = (piType, ct, portalType) => {
@@ -9200,45 +9279,22 @@ var init_public_surface = __esm({
9200
9279
  return "a compatible component";
9201
9280
  }
9202
9281
  };
9203
- publicSurfaceRule = {
9204
- name: "public-surface",
9205
- description: "Every declared publicInterface is bound to an existing component of this subsystem whose stereotype can realize the declared interface type; Custom entries whose prose implies eventing must be backed by an event-capable component.",
9282
+ publicSurfaceDeclaredTypeRule = {
9283
+ name: "public-surface-declared-type",
9284
+ 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.",
9206
9285
  codes: [
9207
- { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9208
- { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9209
- { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" },
9210
9286
  { code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
9211
- { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" },
9212
9287
  { code: "PUBLIC_INTERFACE_EVENT_MISTYPED", defaultSeverity: "warning", summary: "Custom interface describing eventing backed by a non-event component" }
9213
9288
  ],
9214
9289
  check(ctx) {
9215
9290
  for (const sub of ctx.subsystems) {
9216
9291
  const isDraftCtx = isDraftSubsystem(sub);
9217
9292
  for (const pi of sub.publicInterfaces) {
9218
- if (!pi.component) {
9219
- ctx.addIssue("error", "PUBLIC_INTERFACE_UNBOUND", `Subsystem "${sub.id}" declares a ${pi.type} public interface with no backing component. Bind it to the component that realizes it (publicInterfaces[].component).`, sub.id, isDraftCtx);
9220
- continue;
9221
- }
9222
- const backing = ctx.componentMap.get(pi.component);
9223
- if (!backing) {
9224
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9225
- continue;
9226
- }
9227
- const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9228
- if (!isSubsystemOwner) {
9229
- ctx.addIssue("error", "PUBLIC_INTERFACE_FOREIGN_COMPONENT", `Subsystem "${sub.id}" publishes component "${pi.component}", but it belongs to subsystem "${backing.subsystem}". A subsystem may only publish its own components.`, sub.id, isDraftCtx);
9230
- }
9293
+ const backing = pi.component ? ctx.componentMap.get(pi.component) : void 0;
9294
+ if (!backing) continue;
9231
9295
  if (!pubTypeMatches(pi.type, backing.componentType, backing.portalType)) {
9232
9296
  ctx.addIssue("error", "PUBLIC_INTERFACE_TYPE_MISMATCH", `Subsystem "${sub.id}" declares a ${pi.type} public interface backed by "${pi.component}" (${backing.componentType}${backing.portalType ? `/${backing.portalType}` : ""}), which cannot realize ${pi.type}. Expected ${expectedFor(pi.type)}.`, sub.id, isDraftCtx);
9233
9297
  }
9234
- if (pi.interface) {
9235
- const intf = ctx.interfaceMap.get(pi.interface);
9236
- if (!intf) {
9237
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9238
- } else if (intf.component !== pi.component) {
9239
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" binds interface "${pi.interface}" to component "${pi.component}", but that interface belongs to component "${intf.component}".`, sub.id, isDraftCtx);
9240
- }
9241
- }
9242
9298
  if (pi.type === "Custom" && EVENT_VOCAB.test(pi.details)) {
9243
9299
  const eventCapable = backing.componentType === "Observer" || backing.componentType === "Portal" && backing.portalType === "MessageBus";
9244
9300
  if (!eventCapable) {
@@ -9252,6 +9308,37 @@ var init_public_surface = __esm({
9252
9308
  }
9253
9309
  });
9254
9310
 
9311
+ // src/core/rules/integrity/public-surface-bound-contract.ts
9312
+ var publicSurfaceBoundContractRule;
9313
+ var init_public_surface_bound_contract = __esm({
9314
+ "src/core/rules/integrity/public-surface-bound-contract.ts"() {
9315
+ "use strict";
9316
+ init_models();
9317
+ publicSurfaceBoundContractRule = {
9318
+ name: "public-surface-bound-contract",
9319
+ description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
9320
+ codes: [
9321
+ { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
9322
+ ],
9323
+ check(ctx) {
9324
+ for (const sub of ctx.subsystems) {
9325
+ const isDraftCtx = isDraftSubsystem(sub);
9326
+ for (const pi of sub.publicInterfaces) {
9327
+ if (!pi.interface) continue;
9328
+ if (!pi.component || !ctx.componentMap.has(pi.component)) continue;
9329
+ const intf = ctx.interfaceMap.get(pi.interface);
9330
+ if (!intf) {
9331
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9332
+ } else if (intf.component !== pi.component) {
9333
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" binds interface "${pi.interface}" to component "${pi.component}", but that interface belongs to component "${intf.component}".`, sub.id, isDraftCtx);
9334
+ }
9335
+ }
9336
+ }
9337
+ }
9338
+ };
9339
+ }
9340
+ });
9341
+
9255
9342
  // src/core/rules/integrity/lint-allows.ts
9256
9343
  var lintAllowsRule;
9257
9344
  var init_lint_allows = __esm({
@@ -9857,7 +9944,7 @@ var init_narrative_jump_edges = __esm({
9857
9944
  }
9858
9945
  });
9859
9946
 
9860
- // src/core/rules/narrative/narrative-antipatterns.ts
9947
+ // src/core/rules/narrative/completed-step-graph.ts
9861
9948
  function completedStepGraph(steps) {
9862
9949
  const numbers = steps.flatMap((s) => [
9863
9950
  s.stepNumber,
@@ -9887,6 +9974,14 @@ function isUnavoidable(steps, target) {
9887
9974
  if (s.type === "return" || s.type === "throw") return true;
9888
9975
  const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
9889
9976
  if (arms.length) return arms.every((e) => completes.has(e));
9977
+ const guaranteed = s.endStep !== void 0 && (s.type === "try" || s.type === "loop" && s.loopKind === "doWhile");
9978
+ if (guaranteed) {
9979
+ const bodyEntry = graph.nextOf(n);
9980
+ if (bodyEntry !== void 0 && bodyEntry <= s.endStep) {
9981
+ if (s.type === "loop") return completes.has(bodyEntry);
9982
+ if (bodyEntry === target) return false;
9983
+ }
9984
+ }
9890
9985
  return graph.successorsOf(n).some((t) => completes.has(t));
9891
9986
  };
9892
9987
  const order = [...graph.stepNumbers].reverse();
@@ -9950,25 +10045,27 @@ function stronglyConnected(nums, successorsOf) {
9950
10045
  }
9951
10046
  return sccs;
9952
10047
  }
9953
- var byCodeUnit, narrativeAntipatternsRule;
9954
- var init_narrative_antipatterns = __esm({
9955
- "src/core/rules/narrative/narrative-antipatterns.ts"() {
10048
+ var init_completed_step_graph = __esm({
10049
+ "src/core/rules/narrative/completed-step-graph.ts"() {
9956
10050
  "use strict";
9957
10051
  init_models();
9958
- byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
9959
- narrativeAntipatternsRule = {
9960
- name: "narrative-antipatterns",
9961
- description: "Provable narrative bugs, caught at spec level before implementation: a step-graph cycle with no exit edge and no return/throw member never terminates by construction (INESCAPABLE_CYCLE); a branch or switch whose arms all target the same step decides nothing (MEANINGLESS_BRANCH); a method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path is unbounded recursion (UNCONDITIONAL_CALL_CYCLE \u2014 dispatch steps resolved through their portal tables; a cycle with even one guarded edge is NOT flagged, since prose conditions are never judged). Structure-only claims \u2014 warnings, lint.allow-suppressible.",
10052
+ }
10053
+ });
10054
+
10055
+ // src/core/rules/narrative/meaningless-branches.ts
10056
+ var meaninglessBranchesRule;
10057
+ var init_meaningless_branches = __esm({
10058
+ "src/core/rules/narrative/meaningless-branches.ts"() {
10059
+ "use strict";
10060
+ init_completed_step_graph();
10061
+ meaninglessBranchesRule = {
10062
+ name: "meaningless-branches",
10063
+ 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.",
9962
10064
  codes: [
9963
- { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" },
9964
- { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" },
9965
- { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10065
+ { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
9966
10066
  ],
9967
10067
  check(ctx) {
9968
- const callEdges = [];
9969
10068
  for (const impl of ctx.implementations) {
9970
- const contract = ctx.interfaceMap.get(impl.contract);
9971
- const component = contract ? ctx.componentMap.get(contract.component) : void 0;
9972
10069
  const isDraftCtx = ctx.isImplementationDraft(impl);
9973
10070
  for (const implMethod of impl.methods) {
9974
10071
  const steps = implMethod.narrative;
@@ -10007,6 +10104,33 @@ var init_narrative_antipatterns = __esm({
10007
10104
  }
10008
10105
  }
10009
10106
  }
10107
+ }
10108
+ }
10109
+ }
10110
+ };
10111
+ }
10112
+ });
10113
+
10114
+ // src/core/rules/narrative/inescapable-cycles.ts
10115
+ var inescapableCyclesRule;
10116
+ var init_inescapable_cycles = __esm({
10117
+ "src/core/rules/narrative/inescapable-cycles.ts"() {
10118
+ "use strict";
10119
+ init_completed_step_graph();
10120
+ inescapableCyclesRule = {
10121
+ name: "inescapable-cycles",
10122
+ 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.",
10123
+ codes: [
10124
+ { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
10125
+ ],
10126
+ check(ctx) {
10127
+ for (const impl of ctx.implementations) {
10128
+ const isDraftCtx = ctx.isImplementationDraft(impl);
10129
+ for (const implMethod of impl.methods) {
10130
+ const steps = implMethod.narrative;
10131
+ if (!steps.length) continue;
10132
+ const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
10133
+ const graph = completedStepGraph(steps);
10010
10134
  for (const scc of stronglyConnected(graph.stepNumbers, graph.successorsOf)) {
10011
10135
  const inScc = new Set(scc);
10012
10136
  const isCycle = scc.length > 1 || graph.successorsOf(scc[0]).includes(scc[0]);
@@ -10027,7 +10151,35 @@ var init_narrative_antipatterns = __esm({
10027
10151
  );
10028
10152
  }
10029
10153
  }
10030
- if (!component) continue;
10154
+ }
10155
+ }
10156
+ }
10157
+ };
10158
+ }
10159
+ });
10160
+
10161
+ // src/core/rules/narrative/unconditional-call-cycles.ts
10162
+ var byCodeUnit, unconditionalCallCyclesRule;
10163
+ var init_unconditional_call_cycles = __esm({
10164
+ "src/core/rules/narrative/unconditional-call-cycles.ts"() {
10165
+ "use strict";
10166
+ init_completed_step_graph();
10167
+ byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
10168
+ unconditionalCallCyclesRule = {
10169
+ name: "unconditional-call-cycles",
10170
+ 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.",
10171
+ codes: [
10172
+ { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10173
+ ],
10174
+ check(ctx) {
10175
+ const callEdges = [];
10176
+ for (const impl of ctx.implementations) {
10177
+ const contract = ctx.interfaceMap.get(impl.contract);
10178
+ const component = contract ? ctx.componentMap.get(contract.component) : void 0;
10179
+ if (!component) continue;
10180
+ for (const implMethod of impl.methods) {
10181
+ const steps = implMethod.narrative;
10182
+ if (!steps.length) continue;
10031
10183
  const fromKey = `${component.id}::${implMethod.name}`;
10032
10184
  for (const s of steps) {
10033
10185
  let toComponent;
@@ -10089,34 +10241,29 @@ var init_narrative_antipatterns = __esm({
10089
10241
  });
10090
10242
 
10091
10243
  // src/core/rules/narrative/narrative-detail.ts
10092
- var DEFAULT_MAX_UNNARRATED_COMPLEXITY, narrativeDetailRule;
10244
+ var narrativeDetailRule;
10093
10245
  var init_narrative_detail = __esm({
10094
10246
  "src/core/rules/narrative/narrative-detail.ts"() {
10095
10247
  "use strict";
10096
10248
  init_models();
10097
- DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
10098
10249
  narrativeDetailRule = {
10099
10250
  name: "narrative-detail",
10100
- 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. Detail sufficiency rides along: 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) may not hide below detail: full without a narrative (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). Both are honest lints over declarations \u2014 they never claim the narrative or prose is CORRECT.",
10251
+ 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.",
10101
10252
  codes: [
10102
10253
  { code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
10103
- { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" },
10104
- { 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)" },
10105
- { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10254
+ { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
10106
10255
  ],
10107
10256
  check(ctx) {
10108
- const code = ctx.codeIndex();
10109
10257
  for (const impl of ctx.implementations) {
10110
10258
  const contract = ctx.interfaceMap.get(impl.contract);
10111
10259
  if (!contract) continue;
10112
10260
  const component = ctx.componentMap.get(contract.component);
10113
- const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract.status === "draft" || contract.status === "design" || ctx.isComponentDraft(contract.component);
10261
+ const isDraftCtx = ctx.isImplementationDraft(impl);
10114
10262
  for (const implMethod of impl.methods) {
10115
10263
  const contractMethod = contract.methods.find((m) => m.name === implMethod.name);
10116
10264
  if (!contractMethod) continue;
10265
+ if (implMethod.narrative.length > 0) continue;
10117
10266
  const eff = effectiveDetail(implMethod, impl, component);
10118
- const hasNarrative = implMethod.narrative.length > 0;
10119
- if (hasNarrative) continue;
10120
10267
  if (eff.level === "full") {
10121
10268
  ctx.addIssue(
10122
10269
  eff.explicit ? "error" : "warning",
@@ -10127,34 +10274,6 @@ var init_narrative_detail = __esm({
10127
10274
  );
10128
10275
  continue;
10129
10276
  }
10130
- let complexityFired = false;
10131
- const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
10132
- const file = methodSourceFile(implMethod, impl.sourcePath);
10133
- if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
10134
- const facts = code.factsAt(file);
10135
- const symbol = implMethod.symbol ?? implMethod.name;
10136
- const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
10137
- const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
10138
- if (complexity !== void 0 && complexity > limit) {
10139
- complexityFired = true;
10140
- ctx.addIssue(
10141
- "warning",
10142
- "UNNARRATED_COMPLEXITY",
10143
- `Method "${implMethod.name}" in implementation "${impl.id}" sits at detail: ${eff.level} with no narrative, but its realized function "${symbol}" in "${file}" measures cyclomatic complexity ${complexity} (limit ${limit}) \u2014 real branching is hiding behind ${eff.level}. Write the narrative, or keep the dial with a lint.allow stating why the branching needs no choreography.`,
10144
- impl.id,
10145
- isDraftCtx
10146
- );
10147
- }
10148
- }
10149
- if (!complexityFired && eff.explicit && component && isLogic(component)) {
10150
- ctx.addIssue(
10151
- "warning",
10152
- "DETAIL_BELOW_STEREOTYPE",
10153
- `Method "${implMethod.name}" in implementation "${impl.id}" is explicitly dialed to detail: ${eff.level}, below the full narrative floor of its ${component.componentType} stereotype, and has no narrative. Logic behavior belongs in a narrative \u2014 write one, or keep the dial with a lint.allow stating why.`,
10154
- impl.id,
10155
- isDraftCtx
10156
- );
10157
- }
10158
10277
  const prose = implMethod.intent ?? contractMethod.description;
10159
10278
  if (!passesIntentFloor(prose, implMethod.name)) {
10160
10279
  ctx.addIssue(
@@ -10172,6 +10291,71 @@ var init_narrative_detail = __esm({
10172
10291
  }
10173
10292
  });
10174
10293
 
10294
+ // src/core/rules/narrative/detail-sufficiency.ts
10295
+ var DEFAULT_MAX_UNNARRATED_COMPLEXITY, detailSufficiencyRule;
10296
+ var init_detail_sufficiency = __esm({
10297
+ "src/core/rules/narrative/detail-sufficiency.ts"() {
10298
+ "use strict";
10299
+ init_models();
10300
+ DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
10301
+ detailSufficiencyRule = {
10302
+ name: "detail-sufficiency",
10303
+ 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.",
10304
+ codes: [
10305
+ { 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)" },
10306
+ { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10307
+ ],
10308
+ check(ctx) {
10309
+ const code = ctx.codeIndex();
10310
+ const dialedDown = [];
10311
+ for (const impl of ctx.implementations) {
10312
+ const contract = ctx.interfaceMap.get(impl.contract);
10313
+ if (!contract) continue;
10314
+ const component = ctx.componentMap.get(contract.component);
10315
+ const draftContext = ctx.isImplementationDraft(impl);
10316
+ for (const method of impl.methods) {
10317
+ const detail = effectiveDetail(method, impl, component);
10318
+ if (contract.methods.some((m) => m.name === method.name) && method.narrative.length === 0 && detail.level !== "full") {
10319
+ dialedDown.push({ implementation: impl, method, component, detail, draftContext });
10320
+ }
10321
+ }
10322
+ }
10323
+ for (const entry of dialedDown) {
10324
+ const { implementation: impl, method, component, detail } = entry;
10325
+ const tier = method.conformance ?? impl.conformance ?? defaultConformanceTier(component);
10326
+ const file = methodSourceFile(method, impl.sourcePath);
10327
+ const symbol = method.symbol ?? method.name;
10328
+ const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
10329
+ let complexity;
10330
+ if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
10331
+ const facts = code.factsAt(file);
10332
+ complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
10333
+ }
10334
+ if (complexity !== void 0 && complexity > limit) {
10335
+ ctx.addIssue(
10336
+ "warning",
10337
+ "UNNARRATED_COMPLEXITY",
10338
+ `Method "${method.name}" in implementation "${impl.id}" sits at detail: ${detail.level} with no narrative, but its realized function "${symbol}" in "${file}" measures cyclomatic complexity ${complexity} (limit ${limit}) \u2014 real branching is hiding behind ${detail.level}. Write the narrative, or keep the dial with a lint.allow stating why the branching needs no choreography.`,
10339
+ impl.id,
10340
+ entry.draftContext
10341
+ );
10342
+ continue;
10343
+ }
10344
+ if (detail.explicit && component && isLogic(component)) {
10345
+ ctx.addIssue(
10346
+ "warning",
10347
+ "DETAIL_BELOW_STEREOTYPE",
10348
+ `Method "${method.name}" in implementation "${impl.id}" is explicitly dialed to detail: ${detail.level}, below the full narrative floor of its ${component.componentType} stereotype, and has no narrative. Logic behavior belongs in a narrative \u2014 write one, or keep the dial with a lint.allow stating why.`,
10349
+ impl.id,
10350
+ entry.draftContext
10351
+ );
10352
+ }
10353
+ }
10354
+ }
10355
+ };
10356
+ }
10357
+ });
10358
+
10175
10359
  // src/core/rules/intrinsic/portal-fields.ts
10176
10360
  var portalFieldsRule;
10177
10361
  var init_portal_fields = __esm({
@@ -10356,45 +10540,61 @@ var init_portal_endpoints = __esm({
10356
10540
  };
10357
10541
  portalsRule = {
10358
10542
  name: "portal-endpoints",
10359
- description: "A Portal binds every interface method to a concrete endpoint of the matching transport, and only a Portal may carry endpoints at all.",
10543
+ description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
10360
10544
  codes: [
10361
10545
  { code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
10362
- { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" },
10363
- { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10546
+ { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
10364
10547
  ],
10365
10548
  check(ctx) {
10366
10549
  for (const comp of ctx.components) {
10550
+ if (comp.componentType !== "Portal") continue;
10551
+ const expected = comp.portalType ? PORTAL_TRANSPORT[comp.portalType] : void 0;
10552
+ if (!expected) continue;
10367
10553
  const isDraftCtx = ctx.isComponentDraft(comp.id);
10368
- const compInterfaces = ctx.interfaces.filter((i) => i.component === comp.id);
10369
- if (comp.componentType === "Portal") {
10370
- if (!comp.portalType) continue;
10371
- const expected = PORTAL_TRANSPORT[comp.portalType];
10372
- if (!expected) continue;
10373
- for (const intf of compInterfaces) {
10374
- const isIntfDraft = intf.status === "draft" || intf.status === "design";
10375
- for (const m of intf.methods) {
10376
- if (!m.endpoint) {
10377
- ctx.addIssue(
10378
- "error",
10379
- "MISSING_ENDPOINT",
10380
- `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10381
- intf.id,
10382
- isDraftCtx || isIntfDraft
10383
- );
10384
- } else if (m.endpoint.transport !== expected) {
10385
- ctx.addIssue(
10386
- "error",
10387
- "ENDPOINT_TRANSPORT_MISMATCH",
10388
- `Method "${m.name}" on interface "${intf.id}" declares a "${m.endpoint.transport}" endpoint, but its Portal "${comp.id}" is portalType "${comp.portalType}" (expects transport "${expected}").`,
10389
- intf.id,
10390
- isDraftCtx || isIntfDraft
10391
- );
10392
- }
10554
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10555
+ const isIntfDraft = intf.status === "draft" || intf.status === "design";
10556
+ for (const m of intf.methods) {
10557
+ if (!m.endpoint) {
10558
+ ctx.addIssue(
10559
+ "error",
10560
+ "MISSING_ENDPOINT",
10561
+ `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10562
+ intf.id,
10563
+ isDraftCtx || isIntfDraft
10564
+ );
10565
+ } else if (m.endpoint.transport !== expected) {
10566
+ ctx.addIssue(
10567
+ "error",
10568
+ "ENDPOINT_TRANSPORT_MISMATCH",
10569
+ `Method "${m.name}" on interface "${intf.id}" declares a "${m.endpoint.transport}" endpoint, but its Portal "${comp.id}" is portalType "${comp.portalType}" (expects transport "${expected}").`,
10570
+ intf.id,
10571
+ isDraftCtx || isIntfDraft
10572
+ );
10393
10573
  }
10394
10574
  }
10395
- continue;
10396
10575
  }
10397
- for (const intf of compInterfaces) {
10576
+ }
10577
+ }
10578
+ };
10579
+ }
10580
+ });
10581
+
10582
+ // src/core/rules/doctrine/non-portal-endpoints.ts
10583
+ var nonPortalEndpointsRule;
10584
+ var init_non_portal_endpoints = __esm({
10585
+ "src/core/rules/doctrine/non-portal-endpoints.ts"() {
10586
+ "use strict";
10587
+ nonPortalEndpointsRule = {
10588
+ name: "non-portal-endpoints",
10589
+ 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.",
10590
+ codes: [
10591
+ { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10592
+ ],
10593
+ check(ctx) {
10594
+ for (const comp of ctx.components) {
10595
+ if (comp.componentType === "Portal") continue;
10596
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10597
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10398
10598
  const isIntfDraft = intf.status === "draft" || intf.status === "design";
10399
10599
  for (const m of intf.methods) {
10400
10600
  if (m.endpoint) {
@@ -10415,20 +10615,16 @@ var init_portal_endpoints = __esm({
10415
10615
  });
10416
10616
 
10417
10617
  // src/core/rules/doctrine/portal-call-auth.ts
10418
- var COMPONENT_REF_PREFIX, portalCallAuthRule;
10618
+ var portalCallAuthRule;
10419
10619
  var init_portal_call_auth = __esm({
10420
10620
  "src/core/rules/doctrine/portal-call-auth.ts"() {
10421
10621
  "use strict";
10422
- COMPONENT_REF_PREFIX = "component:";
10423
10622
  portalCallAuthRule = {
10424
10623
  name: "portal-call-auth",
10425
- 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. When `auth.from` is a modeled reference (`component:<id>`) it must resolve to an Adapter/Store the presenter is wired to (UNKNOWN_AUTH_SOURCE / AUTH_SOURCE_NOT_PROVIDER / AUTH_SOURCE_UNWIRED). 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.",
10624
+ 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.",
10426
10625
  codes: [
10427
10626
  { code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
10428
- { code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" },
10429
- { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10430
- { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10431
- { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10627
+ { code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" }
10432
10628
  ],
10433
10629
  check(ctx) {
10434
10630
  for (const impl of ctx.implementations) {
@@ -10451,8 +10647,7 @@ var init_portal_call_auth = __esm({
10451
10647
  draft
10452
10648
  );
10453
10649
  }
10454
- const from = step.auth?.from;
10455
- if (!from) {
10650
+ if (!step.auth?.from) {
10456
10651
  ctx.addIssue(
10457
10652
  "warning",
10458
10653
  "PORTAL_AUTH_UNMET",
@@ -10460,40 +10655,73 @@ var init_portal_call_auth = __esm({
10460
10655
  impl.id,
10461
10656
  draft
10462
10657
  );
10463
- continue;
10464
10658
  }
10465
- if (from.startsWith(COMPONENT_REF_PREFIX)) {
10466
- const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10467
- const src = ctx.componentMap.get(srcId);
10468
- if (!src) {
10469
- ctx.addIssue(
10470
- "warning",
10471
- "UNKNOWN_AUTH_SOURCE",
10472
- `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10473
- impl.id,
10474
- draft
10475
- );
10476
- } else {
10477
- if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10478
- ctx.addIssue(
10479
- "warning",
10480
- "AUTH_SOURCE_NOT_PROVIDER",
10481
- `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
10482
- impl.id,
10483
- draft
10484
- );
10485
- }
10486
- const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10487
- if (presenter && !wired) {
10488
- ctx.addIssue(
10489
- "warning",
10490
- "AUTH_SOURCE_UNWIRED",
10491
- `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
10492
- impl.id,
10493
- draft
10494
- );
10495
- }
10496
- }
10659
+ }
10660
+ }
10661
+ }
10662
+ }
10663
+ };
10664
+ }
10665
+ });
10666
+
10667
+ // src/core/rules/doctrine/auth-source-wiring.ts
10668
+ var COMPONENT_REF_PREFIX, authSourceWiringRule;
10669
+ var init_auth_source_wiring = __esm({
10670
+ "src/core/rules/doctrine/auth-source-wiring.ts"() {
10671
+ "use strict";
10672
+ COMPONENT_REF_PREFIX = "component:";
10673
+ authSourceWiringRule = {
10674
+ name: "auth-source-wiring",
10675
+ 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.",
10676
+ codes: [
10677
+ { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10678
+ { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10679
+ { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10680
+ ],
10681
+ check(ctx) {
10682
+ for (const impl of ctx.implementations) {
10683
+ const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
10684
+ const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
10685
+ const draft = ctx.isImplementationDraft(impl);
10686
+ for (const method of impl.methods ?? []) {
10687
+ for (const step of method.narrative ?? []) {
10688
+ if (step.type !== "call") continue;
10689
+ if (!step.targetComponent || step.targetComponent === ownComponent) continue;
10690
+ const target = ctx.componentMap.get(step.targetComponent);
10691
+ if (!target || target.componentType !== "Portal") continue;
10692
+ if (!target.auth || target.auth.scheme === "none") continue;
10693
+ const from = step.auth?.from;
10694
+ if (!from || !from.startsWith(COMPONENT_REF_PREFIX)) continue;
10695
+ const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10696
+ const src = ctx.componentMap.get(srcId);
10697
+ if (!src) {
10698
+ ctx.addIssue(
10699
+ "warning",
10700
+ "UNKNOWN_AUTH_SOURCE",
10701
+ `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10702
+ impl.id,
10703
+ draft
10704
+ );
10705
+ continue;
10706
+ }
10707
+ if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10708
+ ctx.addIssue(
10709
+ "warning",
10710
+ "AUTH_SOURCE_NOT_PROVIDER",
10711
+ `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
10712
+ impl.id,
10713
+ draft
10714
+ );
10715
+ }
10716
+ const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10717
+ if (presenter && !wired) {
10718
+ ctx.addIssue(
10719
+ "warning",
10720
+ "AUTH_SOURCE_UNWIRED",
10721
+ `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
10722
+ impl.id,
10723
+ draft
10724
+ );
10497
10725
  }
10498
10726
  }
10499
10727
  }
@@ -11369,17 +11597,135 @@ var init_component_variants = __esm({
11369
11597
  }
11370
11598
  });
11371
11599
 
11372
- // src/core/rules/extension/declarative-assertions.ts
11600
+ // src/core/rules/extension/declared-assertion.ts
11601
+ function assertionsOfKind(ctx, kind) {
11602
+ return ctx.ext.assertions.filter(
11603
+ (a) => a.kind === kind
11604
+ );
11605
+ }
11373
11606
  function globToRegExp(glob) {
11374
11607
  const escaped = glob.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? ".*" : `\\${ch}`);
11375
11608
  return new RegExp(`^${escaped}$`);
11376
11609
  }
11377
- function matches(sel, comp, ctx) {
11610
+ function matchesSelector(sel, comp, ctx) {
11378
11611
  if (sel.componentType?.length && !sel.componentType.includes(comp.componentType)) return false;
11379
11612
  if (sel.profile?.length && !sel.profile.includes(ctx.getComponentProfile(comp.id))) return false;
11380
11613
  if (sel.id && !globToRegExp(sel.id).test(comp.id)) return false;
11381
11614
  return true;
11382
11615
  }
11616
+ function reportAssertion(ctx, a, message, specId, isDraftCtx) {
11617
+ const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11618
+ ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11619
+ }
11620
+ var init_declared_assertion = __esm({
11621
+ "src/core/rules/extension/declared-assertion.ts"() {
11622
+ "use strict";
11623
+ }
11624
+ });
11625
+
11626
+ // src/core/rules/extension/assertion-forbidden-edges.ts
11627
+ var assertionForbiddenEdgesRule;
11628
+ var init_assertion_forbidden_edges = __esm({
11629
+ "src/core/rules/extension/assertion-forbidden-edges.ts"() {
11630
+ "use strict";
11631
+ init_declared_assertion();
11632
+ assertionForbiddenEdgesRule = {
11633
+ name: "assertion-forbidden-edges",
11634
+ 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.",
11635
+ // Static codes are unknown here — packs bring their own. The validator
11636
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11637
+ codes: [],
11638
+ check(ctx) {
11639
+ for (const a of assertionsOfKind(ctx, "forbid-edge")) {
11640
+ for (const comp of ctx.components) {
11641
+ if (!matchesSelector(a.from, comp, ctx)) continue;
11642
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11643
+ const edges = a.relation.flatMap(
11644
+ (relation) => comp[relation].map((targetId) => ({ relation, targetId }))
11645
+ );
11646
+ for (const { relation, targetId } of edges) {
11647
+ const target = ctx.componentMap.get(targetId);
11648
+ if (!target || !matchesSelector(a.to, target, ctx)) continue;
11649
+ reportAssertion(
11650
+ ctx,
11651
+ a,
11652
+ `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`,
11653
+ comp.id,
11654
+ isDraftCtx
11655
+ );
11656
+ }
11657
+ }
11658
+ }
11659
+ }
11660
+ };
11661
+ }
11662
+ });
11663
+
11664
+ // src/core/rules/extension/assertion-required-fields.ts
11665
+ function fieldValue(spec, field) {
11666
+ const segments = field.split(".");
11667
+ if (segments[0] !== "ext") {
11668
+ return segments.length === 1 ? spec[field] : void 0;
11669
+ }
11670
+ let cur = spec.ext;
11671
+ for (const seg of segments.slice(1)) {
11672
+ if (cur === null || typeof cur !== "object") return void 0;
11673
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11674
+ cur = cur[seg];
11675
+ }
11676
+ return cur;
11677
+ }
11678
+ function holdersAt(ctx, level) {
11679
+ const holders = [];
11680
+ if (level === "component") {
11681
+ for (const c of ctx.components) {
11682
+ holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11683
+ }
11684
+ } else if (level === "interface") {
11685
+ for (const i of ctx.interfaces) {
11686
+ const comp = ctx.componentMap.get(i.component);
11687
+ if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11688
+ }
11689
+ } else {
11690
+ for (const impl of ctx.implementations) {
11691
+ const contract = ctx.interfaceMap.get(impl.contract);
11692
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11693
+ if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11694
+ }
11695
+ }
11696
+ return holders;
11697
+ }
11698
+ var assertionRequiredFieldsRule;
11699
+ var init_assertion_required_fields = __esm({
11700
+ "src/core/rules/extension/assertion-required-fields.ts"() {
11701
+ "use strict";
11702
+ init_declared_assertion();
11703
+ assertionRequiredFieldsRule = {
11704
+ name: "assertion-required-fields",
11705
+ 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.",
11706
+ // Static codes are unknown here — packs bring their own. The validator
11707
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11708
+ codes: [],
11709
+ check(ctx) {
11710
+ for (const a of assertionsOfKind(ctx, "require-field")) {
11711
+ for (const h of holdersAt(ctx, a.level)) {
11712
+ if (!matchesSelector(a.on, h.comp, ctx)) continue;
11713
+ const v = fieldValue(h.spec, a.field);
11714
+ if (v === void 0 || v === null) {
11715
+ reportAssertion(ctx, a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11716
+ continue;
11717
+ }
11718
+ if (a.values && !a.values.includes(String(v))) {
11719
+ reportAssertion(ctx, a, `${a.level} spec "${h.specId}" declares "${a.field}" = "${String(v)}", outside the allowed set (${a.values.join(", ")}) of assertion ${a.code}`, h.specId, h.draft);
11720
+ }
11721
+ }
11722
+ }
11723
+ }
11724
+ };
11725
+ }
11726
+ });
11727
+
11728
+ // src/core/rules/extension/assertion-endpoint-shapes.ts
11383
11729
  function endpointAddress(ep) {
11384
11730
  switch (ep.transport) {
11385
11731
  case "HTTP":
@@ -11400,92 +11746,42 @@ function endpointAddress(ep) {
11400
11746
  return ep.address;
11401
11747
  }
11402
11748
  }
11403
- function fieldValue(spec, field) {
11404
- const segments = field.split(".");
11405
- if (segments[0] !== "ext") {
11406
- return segments.length === 1 ? spec[field] : void 0;
11407
- }
11408
- let cur = spec.ext;
11409
- for (const seg of segments.slice(1)) {
11410
- if (cur === null || typeof cur !== "object") return void 0;
11411
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11412
- cur = cur[seg];
11749
+ function boundEndpoints(ctx, on) {
11750
+ const out = [];
11751
+ for (const intf of ctx.interfaces) {
11752
+ const comp = ctx.componentMap.get(intf.component);
11753
+ if (!comp || !matchesSelector(on, comp, ctx)) continue;
11754
+ const draft = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11755
+ for (const method of intf.methods) {
11756
+ if (!method.endpoint) continue;
11757
+ out.push({ intf, method, endpoint: method.endpoint, draft });
11758
+ }
11413
11759
  }
11414
- return cur;
11760
+ return out;
11415
11761
  }
11416
- var declarativeAssertionsRule;
11417
- var init_declarative_assertions = __esm({
11418
- "src/core/rules/extension/declarative-assertions.ts"() {
11762
+ var assertionEndpointShapesRule;
11763
+ var init_assertion_endpoint_shapes = __esm({
11764
+ "src/core/rules/extension/assertion-endpoint-shapes.ts"() {
11419
11765
  "use strict";
11420
- declarativeAssertionsRule = {
11421
- name: "declarative-assertions",
11422
- description: "Evaluates the declarative rule assertions loaded packs declare (forbid-edge / require-field / endpoint-shape) \u2014 closed kinds instantiated with pack data, the hosted-safe doctrine channel. 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.",
11766
+ init_declared_assertion();
11767
+ assertionEndpointShapesRule = {
11768
+ name: "assertion-endpoint-shapes",
11769
+ 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.",
11423
11770
  // Static codes are unknown here — packs bring their own. The validator
11424
11771
  // gathers every loaded assertion's fullCode into knownIssueCodes.
11425
11772
  codes: [],
11426
11773
  check(ctx) {
11427
- const assertions = ctx.ext.assertions;
11428
- if (!assertions.length) return;
11429
- const emit = (a, message, specId, isDraftCtx) => {
11430
- const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11431
- ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11432
- };
11433
- for (const a of assertions) {
11434
- if (a.kind === "forbid-edge") {
11435
- for (const comp of ctx.components) {
11436
- if (!matches(a.from, comp, ctx)) continue;
11437
- const isDraftCtx = ctx.isComponentDraft(comp.id);
11438
- for (const relation of a.relation) {
11439
- for (const targetId of comp[relation]) {
11440
- const target = ctx.componentMap.get(targetId);
11441
- if (!target || !matches(a.to, target, ctx)) continue;
11442
- emit(a, `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`, comp.id, isDraftCtx);
11443
- }
11444
- }
11445
- }
11446
- } else if (a.kind === "require-field") {
11447
- const holders = [];
11448
- if (a.level === "component") {
11449
- for (const c of ctx.components) holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11450
- } else if (a.level === "interface") {
11451
- for (const i of ctx.interfaces) {
11452
- const comp = ctx.componentMap.get(i.component);
11453
- if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11454
- }
11455
- } else {
11456
- for (const impl of ctx.implementations) {
11457
- const contract = ctx.interfaceMap.get(impl.contract);
11458
- const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11459
- if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11460
- }
11461
- }
11462
- for (const h of holders) {
11463
- if (!matches(a.on, h.comp, ctx)) continue;
11464
- const v = fieldValue(h.spec, a.field);
11465
- if (v === void 0 || v === null) {
11466
- emit(a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11467
- } else if (a.values && !a.values.includes(String(v))) {
11468
- emit(a, `${a.level} spec "${h.specId}" declares "${a.field}" = "${String(v)}", outside the allowed set (${a.values.join(", ")}) of assertion ${a.code}`, h.specId, h.draft);
11469
- }
11774
+ for (const a of assertionsOfKind(ctx, "endpoint-shape")) {
11775
+ const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11776
+ for (const bound2 of boundEndpoints(ctx, a.on)) {
11777
+ const { intf, method, endpoint } = bound2;
11778
+ if (a.transport?.length && !a.transport.includes(endpoint.transport)) {
11779
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method.name}" uses transport ${endpoint.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, bound2.draft);
11780
+ continue;
11470
11781
  }
11471
- } else if (a.kind === "endpoint-shape") {
11472
- const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11473
- for (const intf of ctx.interfaces) {
11474
- const comp = ctx.componentMap.get(intf.component);
11475
- if (!comp || !matches(a.on, comp, ctx)) continue;
11476
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11477
- for (const method of intf.methods) {
11478
- if (!method.endpoint) continue;
11479
- const ep = method.endpoint;
11480
- if (a.transport?.length && !a.transport.includes(ep.transport)) {
11481
- emit(a, `Endpoint of "${intf.id}.${method.name}" uses transport ${ep.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, isDraftCtx);
11482
- continue;
11483
- }
11484
- const address = endpointAddress(ep);
11485
- if (pattern && !pattern.test(address)) {
11486
- emit(a, `Endpoint of "${intf.id}.${method.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, isDraftCtx);
11487
- }
11488
- }
11782
+ const address = endpointAddress(endpoint);
11783
+ if (pattern && !pattern.test(address)) {
11784
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, bound2.draft);
11489
11785
  }
11490
11786
  }
11491
11787
  }
@@ -11925,16 +12221,13 @@ var reachabilityRule;
11925
12221
  var init_unused_detection = __esm({
11926
12222
  "src/core/rules/wiring/unused-detection.ts"() {
11927
12223
  "use strict";
11928
- init_models();
11929
12224
  init_narrative_graph_projector();
11930
12225
  reachabilityRule = {
11931
12226
  name: "unused-detection",
11932
- 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, plus types no field or signature references. An interface method declaring invokedBy (a real caller outside the modeled graph) seeds the walk, so its narrative propagates reachability; the declaration itself is audited (thin caller prose, or a method the internal walk already reaches).",
12227
+ 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.",
11933
12228
  codes: [
11934
12229
  { code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
11935
12230
  { code: "UNUSED_METHOD", defaultSeverity: "warning", summary: "Method never called by any narrative step" },
11936
- { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" },
11937
- { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" },
11938
12231
  { code: "INVOKED_BY_REDUNDANT", defaultSeverity: "warning", summary: "invokedBy declaration on a method the internal narrative walk already reaches \u2014 stale, remove it" }
11939
12232
  ],
11940
12233
  check(ctx) {
@@ -11962,17 +12255,8 @@ var init_unused_detection = __esm({
11962
12255
  if (!m.invokedBy) continue;
11963
12256
  invokedBySeeds.push({ compId: intf.component, methodName: m.name });
11964
12257
  if (!ctx.isSpecInScope(intf.id)) continue;
11965
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
11966
- if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
11967
- ctx.addIssue(
11968
- "warning",
11969
- "INVOKED_BY_UNDESCRIBED",
11970
- `Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
11971
- intf.id,
11972
- isDraftCtx
11973
- );
11974
- }
11975
12258
  if (baseWalk.reachesMethod(intf.component, m.name)) {
12259
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
11976
12260
  ctx.addIssue(
11977
12261
  "warning",
11978
12262
  "INVOKED_BY_REDUNDANT",
@@ -11995,23 +12279,75 @@ var init_unused_detection = __esm({
11995
12279
  comp.id,
11996
12280
  isDraftCtx
11997
12281
  );
11998
- } else {
11999
- for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
12000
- for (const m of intf.methods) {
12001
- if (!reach.reachesMethod(comp.id, m.name)) {
12002
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
12003
- ctx.addIssue(
12004
- "warning",
12005
- "UNUSED_METHOD",
12006
- `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
12007
- intf.id,
12008
- isDraftCtx
12009
- );
12010
- }
12282
+ continue;
12283
+ }
12284
+ for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
12285
+ for (const m of intf.methods) {
12286
+ if (!reach.reachesMethod(comp.id, m.name)) {
12287
+ const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
12288
+ ctx.addIssue(
12289
+ "warning",
12290
+ "UNUSED_METHOD",
12291
+ `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
12292
+ intf.id,
12293
+ isDraftCtx
12294
+ );
12011
12295
  }
12012
12296
  }
12013
12297
  }
12014
12298
  }
12299
+ }
12300
+ };
12301
+ }
12302
+ });
12303
+
12304
+ // src/core/rules/wiring/invoked-by-description.ts
12305
+ var invokedByDescriptionRule;
12306
+ var init_invoked_by_description = __esm({
12307
+ "src/core/rules/wiring/invoked-by-description.ts"() {
12308
+ "use strict";
12309
+ init_models();
12310
+ invokedByDescriptionRule = {
12311
+ name: "invoked-by-description",
12312
+ 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.',
12313
+ codes: [
12314
+ { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
12315
+ ],
12316
+ check(ctx) {
12317
+ for (const intf of ctx.interfaces) {
12318
+ if (!ctx.componentMap.has(intf.component)) continue;
12319
+ if (!ctx.isSpecInScope(intf.id)) continue;
12320
+ for (const m of intf.methods) {
12321
+ if (!m.invokedBy) continue;
12322
+ if (passesIntentFloor(m.invokedBy.caller, m.name)) continue;
12323
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
12324
+ ctx.addIssue(
12325
+ "warning",
12326
+ "INVOKED_BY_UNDESCRIBED",
12327
+ `Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
12328
+ intf.id,
12329
+ isDraftCtx
12330
+ );
12331
+ }
12332
+ }
12333
+ }
12334
+ };
12335
+ }
12336
+ });
12337
+
12338
+ // src/core/rules/wiring/unused-types.ts
12339
+ var unusedTypesRule;
12340
+ var init_unused_types = __esm({
12341
+ "src/core/rules/wiring/unused-types.ts"() {
12342
+ "use strict";
12343
+ init_models();
12344
+ unusedTypesRule = {
12345
+ name: "unused-types",
12346
+ description: "Flags types no field of any type and no interface 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.",
12347
+ codes: [
12348
+ { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" }
12349
+ ],
12350
+ check(ctx) {
12015
12351
  const referencedTypes = /* @__PURE__ */ new Set();
12016
12352
  const markTypeReferenced = (ref) => {
12017
12353
  if (ctx.isBuiltinType(ref)) return;
@@ -12245,7 +12581,44 @@ var init_prose_claims = __esm({
12245
12581
  }
12246
12582
  });
12247
12583
 
12248
- // src/core/rules/wiring/invariant-backing.ts
12584
+ // src/core/rules/wiring/unique-invariant-ids.ts
12585
+ var uniqueInvariantIdsRule;
12586
+ var init_unique_invariant_ids = __esm({
12587
+ "src/core/rules/wiring/unique-invariant-ids.ts"() {
12588
+ "use strict";
12589
+ init_models();
12590
+ uniqueInvariantIdsRule = {
12591
+ name: "unique-invariant-ids",
12592
+ 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.`,
12593
+ codes: [
12594
+ { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
12595
+ ],
12596
+ check(ctx) {
12597
+ for (const t of ctx.types) {
12598
+ const invariants = t.invariants ?? [];
12599
+ if (invariants.length === 0) continue;
12600
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
12601
+ const entityDraft = sub !== void 0 && isDraftSubsystem(sub);
12602
+ const seen = /* @__PURE__ */ new Set();
12603
+ for (const inv of invariants) {
12604
+ if (seen.has(inv.id)) {
12605
+ ctx.addIssue(
12606
+ "error",
12607
+ "DUPLICATE_INVARIANT_ID",
12608
+ `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
12609
+ t.id,
12610
+ entityDraft
12611
+ );
12612
+ }
12613
+ seen.add(inv.id);
12614
+ }
12615
+ }
12616
+ }
12617
+ };
12618
+ }
12619
+ });
12620
+
12621
+ // src/core/rules/wiring/invariant-ref.ts
12249
12622
  function splitInvariantRef(ref) {
12250
12623
  const at = ref.lastIndexOf(".");
12251
12624
  if (at <= 0 || at === ref.length - 1) return null;
@@ -12269,6 +12642,14 @@ function refMatchesInvariant(ref, type, invariantId) {
12269
12642
  if (!(type.invariants ?? []).some((inv) => inv.id === invariantId)) return false;
12270
12643
  return typeMatchesRef(type, parts.typeRef);
12271
12644
  }
12645
+ var init_invariant_ref = __esm({
12646
+ "src/core/rules/wiring/invariant-ref.ts"() {
12647
+ "use strict";
12648
+ init_models();
12649
+ }
12650
+ });
12651
+
12652
+ // src/core/rules/wiring/invariant-backing.ts
12272
12653
  function stepAsserts(impl, methodName, type, invariantId) {
12273
12654
  const method = impl.methods.find((m) => m.name === methodName);
12274
12655
  if (!method) return false;
@@ -12293,33 +12674,19 @@ var init_invariant_backing = __esm({
12293
12674
  "src/core/rules/wiring/invariant-backing.ts"() {
12294
12675
  "use strict";
12295
12676
  init_models();
12677
+ init_invariant_ref();
12296
12678
  invariantBackingRule = {
12297
12679
  name: "invariant-backing",
12298
- 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; dangling assertion references are errors.',
12680
+ 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.',
12299
12681
  codes: [
12300
- { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" },
12301
12682
  { 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" },
12302
- { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" },
12303
- { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12683
+ { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
12304
12684
  ],
12305
12685
  check(ctx) {
12306
12686
  for (const t of ctx.types) {
12307
12687
  const invariants = t.invariants ?? [];
12308
12688
  if (invariants.length === 0) continue;
12309
12689
  const entityDraft = isEntityDraft(t, ctx);
12310
- const seen = /* @__PURE__ */ new Set();
12311
- for (const inv of invariants) {
12312
- if (seen.has(inv.id)) {
12313
- ctx.addIssue(
12314
- "error",
12315
- "DUPLICATE_INVARIANT_ID",
12316
- `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
12317
- t.id,
12318
- entityDraft
12319
- );
12320
- }
12321
- seen.add(inv.id);
12322
- }
12323
12690
  const comp = resolveComponentClass(t, ctx);
12324
12691
  if (!comp) {
12325
12692
  ctx.addIssue(
@@ -12361,6 +12728,24 @@ var init_invariant_backing = __esm({
12361
12728
  }
12362
12729
  }
12363
12730
  }
12731
+ }
12732
+ };
12733
+ }
12734
+ });
12735
+
12736
+ // src/core/rules/wiring/invariant-references.ts
12737
+ var invariantReferencesRule;
12738
+ var init_invariant_references = __esm({
12739
+ "src/core/rules/wiring/invariant-references.ts"() {
12740
+ "use strict";
12741
+ init_invariant_ref();
12742
+ invariantReferencesRule = {
12743
+ name: "invariant-references",
12744
+ 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.",
12745
+ codes: [
12746
+ { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12747
+ ],
12748
+ check(ctx) {
12364
12749
  for (const impl of ctx.implementations) {
12365
12750
  const isDraftCtx = ctx.isImplementationDraft(impl);
12366
12751
  for (const method of impl.methods) {
@@ -12692,54 +13077,45 @@ var init_call_conformance = __esm({
12692
13077
  ],
12693
13078
  check(ctx) {
12694
13079
  const code = ctx.codeIndex();
12695
- for (const impl of ctx.implementations) {
12696
- const contract = ctx.interfaceMap.get(impl.contract);
12697
- if (!contract) continue;
12698
- const component = ctx.componentMap.get(contract.component);
12699
- if (!component) continue;
12700
- if (ctx.isInChainedSubproject(component.subsystem)) continue;
12701
- const specTier = impl.conformance ?? defaultConformanceTier(component);
12702
- const isDraftCtx = ctx.isImplementationDraft(impl);
12703
- for (const implMethod of impl.methods) {
12704
- const tier = implMethod.conformance ?? specTier;
12705
- if (tier === "off") continue;
12706
- if (!implMethod.narrative.length) continue;
12707
- const file = methodSourceFile(implMethod, impl.sourcePath);
12708
- if (!file) continue;
12709
- const facts = code.factsAt(file);
12710
- if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12711
- const fnSymbol = implMethod.symbol ?? implMethod.name;
12712
- const callees = closedCallees(facts, fnSymbol);
12713
- if (!callees) continue;
12714
- const missing = [];
12715
- for (const step of implMethod.narrative) {
12716
- if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
12717
- if (!ctx.componentMap.has(step.targetComponent)) continue;
12718
- const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
12719
- for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
12720
- for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
12721
- const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
12722
- if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12723
- }
13080
+ for (const entry of ctx.implementationMethods()) {
13081
+ const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
13082
+ const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
13083
+ if (tier === "off") continue;
13084
+ if (!implMethod.narrative.length) continue;
13085
+ if (!file) continue;
13086
+ const facts = code.factsAt(file);
13087
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
13088
+ const fnSymbol = implMethod.symbol ?? implMethod.name;
13089
+ const callees = closedCallees(facts, fnSymbol);
13090
+ if (!callees) continue;
13091
+ const missing = [];
13092
+ for (const step of implMethod.narrative) {
13093
+ if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
13094
+ if (!ctx.componentMap.has(step.targetComponent)) continue;
13095
+ const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
13096
+ for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
13097
+ for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
13098
+ const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
13099
+ if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12724
13100
  }
12725
- if (accepted.has(fnSymbol)) continue;
12726
- if ([...accepted].some((name) => callees.has(name))) continue;
12727
- missing.push({
12728
- step: step.stepNumber,
12729
- target: `${step.targetComponent}.${step.targetMethod}`,
12730
- accepted: [...accepted]
12731
- });
12732
13101
  }
12733
- if (missing.length === 0) continue;
12734
- const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
12735
- ctx.addIssue(
12736
- "warning",
12737
- "CALL_STEP_UNREALIZED",
12738
- `Method "${implMethod.name}" in implementation "${impl.id}": ${missing.length} narrative call step(s) are not realized as calls of the function "${fnSymbol}" in "${file}" \u2014 ${detail}. Callees are matched by name, closed over same-file helpers (exact grade, set membership \u2014 order and arguments are not checked). Realize the calls, fix the narrative, or map code names via per-method symbols on the targets.`,
12739
- impl.id,
12740
- isDraftCtx
12741
- );
13102
+ if (accepted.has(fnSymbol)) continue;
13103
+ if ([...accepted].some((name) => callees.has(name))) continue;
13104
+ missing.push({
13105
+ step: step.stepNumber,
13106
+ target: `${step.targetComponent}.${step.targetMethod}`,
13107
+ accepted: [...accepted]
13108
+ });
12742
13109
  }
13110
+ if (missing.length === 0) continue;
13111
+ const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
13112
+ ctx.addIssue(
13113
+ "warning",
13114
+ "CALL_STEP_UNREALIZED",
13115
+ `Method "${implMethod.name}" in implementation "${impl.id}": ${missing.length} narrative call step(s) are not realized as calls of the function "${fnSymbol}" in "${file}" \u2014 ${detail}. Callees are matched by name, closed over same-file helpers (exact grade, set membership \u2014 order and arguments are not checked). Realize the calls, fix the narrative, or map code names via per-method symbols on the targets.`,
13116
+ impl.id,
13117
+ entry.draftContext
13118
+ );
12743
13119
  }
12744
13120
  }
12745
13121
  };
@@ -12906,20 +13282,95 @@ var init_dependency_conformance = __esm({
12906
13282
  }
12907
13283
  });
12908
13284
 
12909
- // src/core/rules/conformance/integration-conformance.ts
12910
- var integrationConformanceRule;
12911
- var init_integration_conformance = __esm({
12912
- "src/core/rules/conformance/integration-conformance.ts"() {
13285
+ // src/core/rules/conformance/integration-sim-declaration.ts
13286
+ var integrationSimDeclarationRule;
13287
+ var init_integration_sim_declaration = __esm({
13288
+ "src/core/rules/conformance/integration-sim-declaration.ts"() {
13289
+ "use strict";
13290
+ integrationSimDeclarationRule = {
13291
+ name: "integration-sim-declaration",
13292
+ 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.",
13293
+ codes: [
13294
+ { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
13295
+ ],
13296
+ check(ctx) {
13297
+ const adopted = /* @__PURE__ */ new Set();
13298
+ for (const impl of ctx.implementations) {
13299
+ if (!impl.simPath) continue;
13300
+ const contract = ctx.interfaceMap.get(impl.contract);
13301
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13302
+ if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
13303
+ adopted.add(comp.subsystem);
13304
+ }
13305
+ for (const impl of ctx.implementations) {
13306
+ if (impl.simPath) continue;
13307
+ const contract = ctx.interfaceMap.get(impl.contract);
13308
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13309
+ if (!comp) continue;
13310
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13311
+ if (!adopted.has(comp.subsystem)) continue;
13312
+ if (ctx.isImplementationDraft(impl)) continue;
13313
+ const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
13314
+ if (deps.length === 0) continue;
13315
+ ctx.addIssue(
13316
+ "warning",
13317
+ "MISSING_INTEGRATION_SIM",
13318
+ `Implementation "${impl.id}" (component "${comp.id}") is complete with ${deps.length} declared dependenc${deps.length === 1 ? "y" : "ies"}, its subsystem "${comp.subsystem}" has adopted integration sims, but it declares no simPath \u2014 its unit suite proves contract shape against mocks, not that the wired components run together. Declare the committed harness that constructs it with its REAL dependencies (N:1 sharing allowed), or lint.allow with the reason.`,
13319
+ impl.id
13320
+ );
13321
+ }
13322
+ }
13323
+ };
13324
+ }
13325
+ });
13326
+
13327
+ // src/core/rules/conformance/integration-sim-file.ts
13328
+ var integrationSimFileRule;
13329
+ var init_integration_sim_file = __esm({
13330
+ "src/core/rules/conformance/integration-sim-file.ts"() {
12913
13331
  "use strict";
12914
13332
  init_models();
12915
- integrationConformanceRule = {
12916
- name: "integration-conformance",
12917
- description: "A component cannot honestly claim completeness until a committed integration harness wires it to its REAL dependencies (L4 simPath). Statically checked: the harness file exists inside the project root, and its import graph \u2014 closed transitively over the analyzed module set, exact grade only \u2014 reaches 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 fakes are sanctioned). Execution is CI's job \u2014 these findings prove wiring, never that the sim passes. MISSING_INTEGRATION_SIM activates per subsystem once its first simPath is declared.",
13333
+ integrationSimFileRule = {
13334
+ name: "integration-sim-file",
13335
+ 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.",
12918
13336
  codes: [
12919
- { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" },
12920
- { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" },
12921
- { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" },
12922
- { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13337
+ { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
13338
+ ],
13339
+ check(ctx) {
13340
+ const code = ctx.codeIndex();
13341
+ for (const impl of ctx.implementations) {
13342
+ if (!impl.simPath) continue;
13343
+ const contract = ctx.interfaceMap.get(impl.contract);
13344
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13345
+ if (!comp) continue;
13346
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13347
+ const facts = code.factsAt(pathKey(impl.simPath));
13348
+ if (facts && facts.status === "analyzed") continue;
13349
+ const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
13350
+ ctx.addIssue(
13351
+ "warning",
13352
+ "SIM_FILE_MISSING",
13353
+ `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
13354
+ impl.id,
13355
+ ctx.isImplementationDraft(impl)
13356
+ );
13357
+ }
13358
+ }
13359
+ };
13360
+ }
13361
+ });
13362
+
13363
+ // src/core/rules/conformance/integration-sim-wiring.ts
13364
+ var integrationSimWiringRule;
13365
+ var init_integration_sim_wiring = __esm({
13366
+ "src/core/rules/conformance/integration-sim-wiring.ts"() {
13367
+ "use strict";
13368
+ init_models();
13369
+ integrationSimWiringRule = {
13370
+ name: "integration-sim-wiring",
13371
+ 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.",
13372
+ codes: [
13373
+ { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
12923
13374
  ],
12924
13375
  check(ctx) {
12925
13376
  const code = ctx.codeIndex();
@@ -12931,48 +13382,18 @@ var init_integration_conformance = __esm({
12931
13382
  };
12932
13383
  const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
12933
13384
  const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
12934
- const adopted = /* @__PURE__ */ new Set();
12935
- for (const impl of ctx.implementations) {
12936
- if (!impl.simPath) continue;
12937
- const contract = ctx.interfaceMap.get(impl.contract);
12938
- const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12939
- if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
12940
- adopted.add(comp.subsystem);
12941
- }
12942
13385
  const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
12943
13386
  for (const impl of ctx.implementations) {
13387
+ if (!impl.simPath) continue;
12944
13388
  const contract = ctx.interfaceMap.get(impl.contract);
12945
13389
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12946
13390
  if (!comp) continue;
12947
13391
  if (ctx.isInChainedSubproject(comp.subsystem)) continue;
12948
- const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
12949
- if (!impl.simPath) {
12950
- if (!adopted.has(comp.subsystem)) continue;
12951
- if (ctx.isImplementationDraft(impl)) continue;
12952
- if (deps.length === 0) continue;
12953
- ctx.addIssue(
12954
- "warning",
12955
- "MISSING_INTEGRATION_SIM",
12956
- `Implementation "${impl.id}" (component "${comp.id}") is complete with ${deps.length} declared dependenc${deps.length === 1 ? "y" : "ies"}, its subsystem "${comp.subsystem}" has adopted integration sims, but it declares no simPath \u2014 its unit suite proves contract shape against mocks, not that the wired components run together. Declare the committed harness that constructs it with its REAL dependencies (N:1 sharing allowed), or lint.allow with the reason.`,
12957
- impl.id
12958
- );
12959
- continue;
12960
- }
12961
13392
  const simPath = pathKey(impl.simPath);
12962
13393
  const facts = code.factsAt(simPath);
12963
- const isDraftCtx = ctx.isImplementationDraft(impl);
12964
- if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
12965
- const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
12966
- ctx.addIssue(
12967
- "warning",
12968
- "SIM_FILE_MISSING",
12969
- `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
12970
- impl.id,
12971
- isDraftCtx
12972
- );
12973
- continue;
12974
- }
13394
+ if (!facts || facts.status !== "analyzed") continue;
12975
13395
  if (facts.analysisGrade !== "exact") continue;
13396
+ const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
12976
13397
  const reach = graph.reachFrom(simPath);
12977
13398
  const ownFiles = modulesOf(comp.id);
12978
13399
  const missing = [];
@@ -12989,15 +13410,43 @@ var init_integration_conformance = __esm({
12989
13410
  missing.push(label);
12990
13411
  }
12991
13412
  }
12992
- if (missing.length) {
12993
- ctx.addIssue(
12994
- "warning",
12995
- "UNWIRED_INTEGRATION_SIM",
12996
- `Sim "${impl.simPath}" of implementation "${impl.id}" does not reach ${missing.join(", nor ")} through its import graph (closed over the analyzed modules, exact grade) \u2014 the harness is not wiring the real implementations it claims to exercise. Import the real modules (technology-boundary adapters may stay contract-faithfully faked), or fix the simPath.`,
12997
- impl.id,
12998
- isDraftCtx
12999
- );
13000
- }
13413
+ if (missing.length === 0) continue;
13414
+ ctx.addIssue(
13415
+ "warning",
13416
+ "UNWIRED_INTEGRATION_SIM",
13417
+ `Sim "${impl.simPath}" of implementation "${impl.id}" does not reach ${missing.join(", nor ")} through its import graph (closed over the analyzed modules, exact grade) \u2014 the harness is not wiring the real implementations it claims to exercise. Import the real modules (technology-boundary adapters may stay contract-faithfully faked), or fix the simPath.`,
13418
+ impl.id,
13419
+ ctx.isImplementationDraft(impl)
13420
+ );
13421
+ }
13422
+ }
13423
+ };
13424
+ }
13425
+ });
13426
+
13427
+ // src/core/rules/conformance/integration-sim-coverage.ts
13428
+ var integrationSimCoverageRule;
13429
+ var init_integration_sim_coverage = __esm({
13430
+ "src/core/rules/conformance/integration-sim-coverage.ts"() {
13431
+ "use strict";
13432
+ init_models();
13433
+ integrationSimCoverageRule = {
13434
+ name: "integration-sim-coverage",
13435
+ 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.",
13436
+ codes: [
13437
+ { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13438
+ ],
13439
+ check(ctx) {
13440
+ const code = ctx.codeIndex();
13441
+ for (const impl of ctx.implementations) {
13442
+ if (!impl.simPath) continue;
13443
+ const contract = ctx.interfaceMap.get(impl.contract);
13444
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13445
+ if (!comp) continue;
13446
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13447
+ const facts = code.factsAt(pathKey(impl.simPath));
13448
+ if (!facts || facts.status !== "analyzed") continue;
13449
+ if (facts.analysisGrade !== "exact") continue;
13001
13450
  const simAnchors = new Set(facts.anchoredNames.filter((a) => a.startsWith("sim:")));
13002
13451
  if (simAnchors.size === 0) continue;
13003
13452
  const compPrefix = `sim:${comp.id}.`;
@@ -13018,15 +13467,14 @@ var init_integration_conformance = __esm({
13018
13467
  }
13019
13468
  }
13020
13469
  }
13021
- if (uncovered.length) {
13022
- ctx.addIssue(
13023
- "warning",
13024
- "SIM_PATH_UNCOVERED",
13025
- `Sim "${impl.simPath}" declares path coverage for component "${comp.id}" (it carries sim: anchors) but does not name ${uncovered.join(", nor ")}. Drive the path and anchor it with the exact string literal, or drop the component's sim: anchors to withdraw the coverage claim. Unlabeled throw steps are not expected \u2014 a step's label is the path's identity. Anchors prove the path is NAMED; assertion quality and execution stay CI's job.`,
13026
- impl.id,
13027
- isDraftCtx
13028
- );
13029
- }
13470
+ if (uncovered.length === 0) continue;
13471
+ ctx.addIssue(
13472
+ "warning",
13473
+ "SIM_PATH_UNCOVERED",
13474
+ `Sim "${impl.simPath}" declares path coverage for component "${comp.id}" (it carries sim: anchors) but does not name ${uncovered.join(", nor ")}. Drive the path and anchor it with the exact string literal, or drop the component's sim: anchors to withdraw the coverage claim. Unlabeled throw steps are not expected \u2014 a step's label is the path's identity. Anchors prove the path is NAMED; assertion quality and execution stay CI's job.`,
13475
+ impl.id,
13476
+ ctx.isImplementationDraft(impl)
13477
+ );
13030
13478
  }
13031
13479
  }
13032
13480
  };
@@ -13136,10 +13584,10 @@ var init_coupling_health = __esm({
13136
13584
  }
13137
13585
  });
13138
13586
 
13139
- // src/core/rules/heuristic/target-language.ts
13140
- var LANGUAGE_MARKERS, UNSUPPORTED_FLOW, languageRule;
13141
- var init_target_language = __esm({
13142
- "src/core/rules/heuristic/target-language.ts"() {
13587
+ // src/core/rules/heuristic/signature-language-builtins.ts
13588
+ var LANGUAGE_MARKERS, signatureLanguageBuiltinsRule;
13589
+ var init_signature_language_builtins = __esm({
13590
+ "src/core/rules/heuristic/signature-language-builtins.ts"() {
13143
13591
  "use strict";
13144
13592
  init_models();
13145
13593
  LANGUAGE_MARKERS = {
@@ -13174,6 +13622,68 @@ var init_target_language = __esm({
13174
13622
  csharp: /* @__PURE__ */ new Set(["task"]),
13175
13623
  go: /* @__PURE__ */ new Set(["chan", "rune"])
13176
13624
  };
13625
+ signatureLanguageBuiltinsRule = {
13626
+ name: "signature-language-builtins",
13627
+ 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).",
13628
+ codes: [
13629
+ { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
13630
+ ],
13631
+ check(ctx) {
13632
+ const markersFor = (family) => {
13633
+ const base = LANGUAGE_MARKERS[family];
13634
+ const extra = ctx.ext.languages[family]?.foreignBuiltins;
13635
+ if (!extra?.length) return base;
13636
+ return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
13637
+ };
13638
+ const families = [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)])];
13639
+ const scans = [];
13640
+ for (const intf of ctx.interfaces) {
13641
+ const comp = ctx.componentMap.get(intf.component);
13642
+ const lang = ctx.targetLanguageFor(comp?.subsystem);
13643
+ if (!lang) continue;
13644
+ const ownMarkers = markersFor(lang);
13645
+ if (!ownMarkers || ownMarkers.size === 0) continue;
13646
+ const draft = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
13647
+ for (const method of intf.methods) {
13648
+ for (const ref of methodTypeRefs(method)) scans.push({ intf, method, ref, lang, ownMarkers, draft });
13649
+ }
13650
+ }
13651
+ for (const scan of scans) {
13652
+ const refLower = scan.ref.toLowerCase();
13653
+ if (scan.ownMarkers.has(refLower)) continue;
13654
+ const foreign = families.find((family) => family !== scan.lang && markersFor(family)?.has(refLower));
13655
+ if (!foreign) continue;
13656
+ ctx.addIssue(
13657
+ "warning",
13658
+ "LANGUAGE_FOREIGN_BUILTIN",
13659
+ `Method "${scan.method.name}" on interface "${scan.intf.id}" uses "${scan.ref}", a ${foreign} builtin, but the target language here is ${scan.lang}. Use the ${scan.lang} equivalent so implementers generate idiomatic code.`,
13660
+ scan.intf.id,
13661
+ scan.draft
13662
+ );
13663
+ }
13664
+ }
13665
+ };
13666
+ }
13667
+ });
13668
+
13669
+ // src/core/rules/heuristic/narrative-language-constructs.ts
13670
+ function constructsOf(step) {
13671
+ const constructs = [
13672
+ step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13673
+ ];
13674
+ if (step.detach) constructs.push("detach");
13675
+ return constructs;
13676
+ }
13677
+ function constructLabel(construct) {
13678
+ if (construct === "doWhile") return "a do-while loop";
13679
+ if (construct === "forEach" || construct === "for" || construct === "while") return `a ${construct} loop`;
13680
+ if (construct === "detach") return "a detached (fire-and-forget) call";
13681
+ return `a ${construct} step`;
13682
+ }
13683
+ var UNSUPPORTED_FLOW, narrativeLanguageConstructsRule;
13684
+ var init_narrative_language_constructs = __esm({
13685
+ "src/core/rules/heuristic/narrative-language-constructs.ts"() {
13686
+ "use strict";
13177
13687
  UNSUPPORTED_FLOW = {
13178
13688
  rust: {
13179
13689
  try: "Rust models errors as values (Result + ?) \u2014 specify the guarded logic as explicit error branches, or note the Result mapping in the step description",
@@ -13193,53 +13703,18 @@ var init_target_language = __esm({
13193
13703
  doWhile: "Python has no do-while \u2014 prefer loopKind: while, or state the emulation (while True + break)"
13194
13704
  }
13195
13705
  };
13196
- languageRule = {
13197
- name: "target-language",
13198
- description: "Contracts must speak the declared target language: builtin types that unambiguously belong to another language family are flagged in method signatures, and narrative flow steps using constructs the language lacks (e.g. exceptions in Rust/Go, do-while in Python) are flagged too. Set targetLanguage on the system (L0) or override per subsystem (L1).",
13706
+ narrativeLanguageConstructsRule = {
13707
+ name: "narrative-language-constructs",
13708
+ 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).",
13199
13709
  codes: [
13200
- { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" },
13201
13710
  { code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
13202
13711
  ],
13203
13712
  check(ctx) {
13204
- const markersFor = (family) => {
13205
- const base = LANGUAGE_MARKERS[family];
13206
- const extra = ctx.ext.languages[family]?.foreignBuiltins;
13207
- if (!extra?.length) return base;
13208
- return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
13209
- };
13210
- const families = /* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)]);
13211
13713
  const gapsFor = (lang) => ({
13212
13714
  ...UNSUPPORTED_FLOW[lang] ?? {},
13213
13715
  ...ctx.ext.languages[lang]?.unsupportedFlow ?? {}
13214
13716
  });
13215
- for (const intf of ctx.interfaces) {
13216
- const comp = ctx.componentMap.get(intf.component);
13217
- const lang = ctx.targetLanguageFor(comp?.subsystem);
13218
- if (!lang) continue;
13219
- const ownMarkers = markersFor(lang);
13220
- if (!ownMarkers || ownMarkers.size === 0) continue;
13221
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
13222
- for (const m of intf.methods) {
13223
- const refs = methodTypeRefs(m);
13224
- for (const ref of refs) {
13225
- const refLower = ref.toLowerCase();
13226
- if (ownMarkers.has(refLower)) continue;
13227
- for (const family of families) {
13228
- if (family === lang) continue;
13229
- if (markersFor(family)?.has(refLower)) {
13230
- ctx.addIssue(
13231
- "warning",
13232
- "LANGUAGE_FOREIGN_BUILTIN",
13233
- `Method "${m.name}" on interface "${intf.id}" uses "${ref}", a ${family} builtin, but the target language here is ${lang}. Use the ${lang} equivalent so implementers generate idiomatic code.`,
13234
- intf.id,
13235
- isDraftCtx
13236
- );
13237
- break;
13238
- }
13239
- }
13240
- }
13241
- }
13242
- }
13717
+ const scans = [];
13243
13718
  for (const impl of ctx.implementations) {
13244
13719
  const contract = ctx.interfaceMap.get(impl.contract);
13245
13720
  if (!contract) continue;
@@ -13248,28 +13723,54 @@ var init_target_language = __esm({
13248
13723
  if (!lang) continue;
13249
13724
  const gaps = gapsFor(lang);
13250
13725
  if (Object.keys(gaps).length === 0) continue;
13251
- const isDraftCtx = ctx.isImplementationDraft(impl);
13252
- for (const implMethod of impl.methods) {
13253
- for (const step of implMethod.narrative) {
13254
- const constructs = [
13255
- step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13256
- ];
13257
- if (step.detach) constructs.push("detach");
13258
- for (const construct of constructs) {
13259
- const guidance = gaps[construct];
13260
- if (!guidance) continue;
13261
- const label = construct === "doWhile" ? "a do-while loop" : construct === "forEach" || construct === "for" || construct === "while" ? `a ${construct} loop` : construct === "detach" ? "a detached (fire-and-forget) call" : `a ${construct} step`;
13262
- ctx.addIssue(
13263
- "warning",
13264
- "LANGUAGE_FOREIGN_FLOW",
13265
- `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" uses ${label}, but the target language is ${lang}: ${guidance}.`,
13266
- impl.id,
13267
- isDraftCtx
13268
- );
13269
- }
13270
- }
13726
+ const draft = ctx.isImplementationDraft(impl);
13727
+ for (const method of impl.methods) {
13728
+ for (const step of method.narrative) scans.push({ implementation: impl, method, step, lang, gaps, draft });
13271
13729
  }
13272
13730
  }
13731
+ for (const scan of scans) {
13732
+ for (const construct of constructsOf(scan.step)) {
13733
+ const guidance = scan.gaps[construct];
13734
+ if (!guidance) continue;
13735
+ ctx.addIssue(
13736
+ "warning",
13737
+ "LANGUAGE_FOREIGN_FLOW",
13738
+ `Step ${scan.step.stepNumber} of "${scan.method.name}" in implementation "${scan.implementation.id}" uses ${constructLabel(construct)}, but the target language is ${scan.lang}: ${guidance}.`,
13739
+ scan.implementation.id,
13740
+ scan.draft
13741
+ );
13742
+ }
13743
+ }
13744
+ }
13745
+ };
13746
+ }
13747
+ });
13748
+
13749
+ // src/core/rules/heuristic/technology-binding.ts
13750
+ var DATA_LAYER, technologyBindingRule;
13751
+ var init_technology_binding = __esm({
13752
+ "src/core/rules/heuristic/technology-binding.ts"() {
13753
+ "use strict";
13754
+ DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13755
+ technologyBindingRule = {
13756
+ name: "technology-binding",
13757
+ 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.",
13758
+ codes: [
13759
+ { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13760
+ ],
13761
+ check(ctx) {
13762
+ for (const impl of ctx.implementations) {
13763
+ const intf = ctx.interfaceMap.get(impl.contract);
13764
+ const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13765
+ if (!impl.technologies?.length || !comp || DATA_LAYER.has(comp.componentType)) continue;
13766
+ ctx.addIssue(
13767
+ "warning",
13768
+ "TECH_ON_LOGIC_COMPONENT",
13769
+ `Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
13770
+ impl.id,
13771
+ ctx.isImplementationDraft(impl)
13772
+ );
13773
+ }
13273
13774
  }
13274
13775
  };
13275
13776
  }
@@ -13298,19 +13799,29 @@ function contractIdentifiers(m) {
13298
13799
  if (m.endpoint) parts.push(Object.values(m.endpoint).map(String).join(" "));
13299
13800
  return parts.join(" ");
13300
13801
  }
13301
- var DATA_LAYER, technologyRule;
13802
+ function implementationText(impl) {
13803
+ const parts = [impl.description, ...implementationSourceFiles(impl)];
13804
+ for (const m of impl.methods) {
13805
+ parts.push(m.intent);
13806
+ for (const s of m.narrative ?? []) {
13807
+ parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13808
+ for (const c of s.cases ?? []) parts.push(c.value);
13809
+ for (const c of s.catches ?? []) parts.push(c.error);
13810
+ }
13811
+ }
13812
+ return parts.filter(Boolean).join(" ");
13813
+ }
13814
+ var technologyRule;
13302
13815
  var init_technology_boundaries = __esm({
13303
13816
  "src/core/rules/heuristic/technology-boundaries.ts"() {
13304
13817
  "use strict";
13305
13818
  init_models();
13306
- DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13307
13819
  technologyRule = {
13308
13820
  name: "technology-boundaries",
13309
- 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, L3 contract identifiers must stay intent-language (the contract is the swap seam), and only data-layer stereotypes (Adapter/Store/Registry/Index) should bind a technology directly.",
13821
+ 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.",
13310
13822
  codes: [
13311
13823
  { code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
13312
- { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" },
13313
- { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13824
+ { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
13314
13825
  ],
13315
13826
  check(ctx) {
13316
13827
  const ownerOf = /* @__PURE__ */ new Map();
@@ -13338,21 +13849,18 @@ var init_technology_boundaries = __esm({
13338
13849
  }
13339
13850
  return out;
13340
13851
  };
13341
- const homes = /* @__PURE__ */ new Map();
13852
+ const declarations = [];
13342
13853
  for (const impl of ctx.implementations) {
13343
13854
  if (!impl.technologies?.length) continue;
13344
13855
  const intf = ctx.interfaceMap.get(impl.contract);
13345
13856
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13346
13857
  if (!comp) continue;
13347
- if (!DATA_LAYER.has(comp.componentType)) {
13348
- ctx.addIssue(
13349
- "warning",
13350
- "TECH_ON_LOGIC_COMPONENT",
13351
- `Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
13352
- impl.id,
13353
- ctx.isImplementationDraft(impl)
13354
- );
13355
- }
13858
+ for (const tech of impl.technologies) declarations.push({ comp, tech });
13859
+ }
13860
+ const scopes = /* @__PURE__ */ new Map();
13861
+ const scopeOf = (comp) => {
13862
+ const cached = scopes.get(comp.id);
13863
+ if (cached) return cached;
13356
13864
  const compSet = ownsClosure(ownershipRoot(comp.id));
13357
13865
  const scope = new Set(compSet);
13358
13866
  for (const i of ctx.interfaces) if (compSet.has(i.component)) scope.add(i.id);
@@ -13367,15 +13875,18 @@ var init_technology_boundaries = __esm({
13367
13875
  if (subId === s.id || subId.startsWith(`${s.id}::`)) scope.add(s.id);
13368
13876
  }
13369
13877
  }
13370
- for (const tech of impl.technologies) {
13371
- const match = makeMatcher(tech);
13372
- if (!match) continue;
13373
- const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13374
- const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13375
- home.ownerComponents.add(comp.id);
13376
- for (const id of scope) home.scope.add(id);
13377
- homes.set(key, home);
13378
- }
13878
+ scopes.set(comp.id, scope);
13879
+ return scope;
13880
+ };
13881
+ const homes = /* @__PURE__ */ new Map();
13882
+ for (const { comp, tech } of declarations) {
13883
+ const match = makeMatcher(tech);
13884
+ if (!match) continue;
13885
+ const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13886
+ const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13887
+ home.ownerComponents.add(comp.id);
13888
+ for (const id of scopeOf(comp)) home.scope.add(id);
13889
+ homes.set(key, home);
13379
13890
  }
13380
13891
  if (homes.size === 0) return;
13381
13892
  const ownersDesc = (h) => [...h.ownerComponents].map((c) => `"${c}"`).join(", ");
@@ -13439,16 +13950,7 @@ var init_technology_boundaries = __esm({
13439
13950
  }
13440
13951
  for (const impl of ctx.implementations) {
13441
13952
  if (home.scope.has(impl.id)) continue;
13442
- const parts = [impl.description, ...implementationSourceFiles(impl)];
13443
- for (const m of impl.methods) {
13444
- parts.push(m.intent);
13445
- for (const s of m.narrative ?? []) {
13446
- parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13447
- for (const c of s.cases ?? []) parts.push(c.value);
13448
- for (const c of s.catches ?? []) parts.push(c.error);
13449
- }
13450
- }
13451
- if (home.match(parts.filter(Boolean).join(" "))) {
13953
+ if (home.match(implementationText(impl))) {
13452
13954
  ctx.addIssue(
13453
13955
  "warning",
13454
13956
  "TECH_LEAKAGE",
@@ -14137,12 +14639,15 @@ var init_repository = __esm({
14137
14639
  "src/core/rules/repository.ts"() {
14138
14640
  "use strict";
14139
14641
  init_hierarchy_integrity();
14140
- init_namespace_hygiene();
14642
+ init_reserved_id_segments();
14643
+ init_namespace_shadowing();
14141
14644
  init_roundtrip_serialization();
14142
14645
  init_type_declarations();
14143
14646
  init_field_type_references();
14144
14647
  init_signature_type_references();
14145
- init_public_surface();
14648
+ init_public_surface_binding();
14649
+ init_public_surface_declared_type();
14650
+ init_public_surface_bound_contract();
14146
14651
  init_lint_allows();
14147
14652
  init_contract_symmetry();
14148
14653
  init_narrative_target_references();
@@ -14152,14 +14657,19 @@ var init_repository = __esm({
14152
14657
  init_narrative_step_config();
14153
14658
  init_narrative_reachability();
14154
14659
  init_narrative_jump_edges();
14155
- init_narrative_antipatterns();
14660
+ init_meaningless_branches();
14661
+ init_inescapable_cycles();
14662
+ init_unconditional_call_cycles();
14156
14663
  init_narrative_detail();
14664
+ init_detail_sufficiency();
14157
14665
  init_portal_fields();
14158
14666
  init_durability_declaration();
14159
14667
  init_logic_declaration();
14160
14668
  init_retired_stereotypes();
14161
14669
  init_portal_endpoints();
14670
+ init_non_portal_endpoints();
14162
14671
  init_portal_call_auth();
14672
+ init_auth_source_wiring();
14163
14673
  init_subsystem_boundary_dependencies();
14164
14674
  init_logic_dependency_class();
14165
14675
  init_data_block_dependencies();
@@ -14175,7 +14685,9 @@ var init_repository = __esm({
14175
14685
  init_pack_profile_stereotypes();
14176
14686
  init_pattern_references();
14177
14687
  init_component_variants();
14178
- init_declarative_assertions();
14688
+ init_assertion_forbidden_edges();
14689
+ init_assertion_required_fields();
14690
+ init_assertion_endpoint_shapes();
14179
14691
  init_pack_resolution();
14180
14692
  init_pack_reproducibility();
14181
14693
  init_dependency_cycles();
@@ -14183,10 +14695,14 @@ var init_repository = __esm({
14183
14695
  init_dispatch_step_routing();
14184
14696
  init_lifecycle_entrypoints();
14185
14697
  init_unused_detection();
14698
+ init_invoked_by_description();
14699
+ init_unused_types();
14186
14700
  init_durability_round_trip();
14187
14701
  init_untyped_seams();
14188
14702
  init_prose_claims();
14703
+ init_unique_invariant_ids();
14189
14704
  init_invariant_backing();
14705
+ init_invariant_references();
14190
14706
  init_event_topology();
14191
14707
  init_source_file_linkage();
14192
14708
  init_method_realization();
@@ -14194,9 +14710,14 @@ var init_repository = __esm({
14194
14710
  init_call_conformance();
14195
14711
  init_hidden_state();
14196
14712
  init_dependency_conformance();
14197
- init_integration_conformance();
14713
+ init_integration_sim_declaration();
14714
+ init_integration_sim_file();
14715
+ init_integration_sim_wiring();
14716
+ init_integration_sim_coverage();
14198
14717
  init_coupling_health();
14199
- init_target_language();
14718
+ init_signature_language_builtins();
14719
+ init_narrative_language_constructs();
14720
+ init_technology_binding();
14200
14721
  init_technology_boundaries();
14201
14722
  init_naming_conventions();
14202
14723
  init_complexity_and_metadata();
@@ -14206,8 +14727,11 @@ var init_repository = __esm({
14206
14727
  SDD_RULES = [
14207
14728
  hierarchyRule,
14208
14729
  // Namespace integrity right after hierarchy: unresolvable/unwritable ids
14209
- // explain many downstream findings, so surface them early in the list.
14210
- namespaceHygieneRule,
14730
+ // explain many downstream findings, so surface them early in the list. Two
14731
+ // questions of the same ids: the segment no id may spend, and the local name
14732
+ // that would anchor a bare reference to the root instead.
14733
+ reservedIdSegmentsRule,
14734
+ namespaceShadowingRule,
14211
14735
  roundtripRule,
14212
14736
  // The type vocabulary in three questions: what a type declares about
14213
14737
  // itself, then the identifiers its fields name, then the ones its
@@ -14233,18 +14757,30 @@ var init_repository = __esm({
14233
14757
  narrativeStepConfigRule,
14234
14758
  narrativeReachabilityRule,
14235
14759
  narrativeJumpEdgesRule,
14236
- // Antipatterns right after flow soundness: they analyze the same step
14237
- // graphs and only make sense once the graphs are structurally valid.
14238
- narrativeAntipatternsRule,
14760
+ // Antipatterns right after flow soundness: they read the same step graphs
14761
+ // and only make sense once the graphs are structurally valid. Three things
14762
+ // structure alone can prove, in widening scope: a decision that decides
14763
+ // nothing, a step cycle nothing leaves, and a call cycle with no guard.
14764
+ meaninglessBranchesRule,
14765
+ inescapableCyclesRule,
14766
+ unconditionalCallCyclesRule,
14767
+ // The detail dial in two questions: does a method carry the detail its level
14768
+ // promises, and is that level low enough to be hiding something.
14239
14769
  narrativeDetailRule,
14770
+ detailSufficiencyRule,
14240
14771
  // Field shape before endpoint bindings: a Portal-only field on the wrong
14241
14772
  // stereotype explains the endpoint findings around it, and this half is
14242
14773
  // spec-scoped so the write boundary refuses it first.
14243
14774
  portalFieldsRule,
14775
+ // Endpoints from both sides: a Portal binds every method to its transport,
14776
+ // and nothing that is not a Portal may bind one at all.
14244
14777
  portalsRule,
14245
- // Cross-call auth: a narrative call into an authed Portal must name its
14246
- // credential source (rides with the portal family).
14778
+ nonPortalEndpointsRule,
14779
+ // Cross-call auth rides with the portal family, in two questions: does a
14780
+ // call into an authed Portal present a credential properly, and does the
14781
+ // source it names resolve to a provider it is wired to.
14247
14782
  portalCallAuthRule,
14783
+ authSourceWiringRule,
14248
14784
  // Dependencies in five questions, one owner each: where an edge is
14249
14785
  // allowed to LAND (the boundary, and what an unresolved id means), then
14250
14786
  // the intra-subsystem matrix by the layer that answers for it — an
@@ -14274,9 +14810,18 @@ var init_repository = __esm({
14274
14810
  patternReferencesRule,
14275
14811
  variantReferencesRule,
14276
14812
  // Pack-instantiated declarative doctrine rides with the pack-reference
14277
- // family: same data source, same provenance-bearing findings.
14278
- declarativeAssertionsRule,
14279
- publicSurfaceRule,
14813
+ // family: same data source, same provenance-bearing findings. One rule per
14814
+ // assertion kind — the kind is what a pack author writes, and each kind
14815
+ // asks its own question of its own collection.
14816
+ assertionForbiddenEdgesRule,
14817
+ assertionRequiredFieldsRule,
14818
+ assertionEndpointShapesRule,
14819
+ // The published surface in three questions: what backs the entry, whether
14820
+ // that component's stereotype can realize the type it declares, and whether
14821
+ // the contract it binds is that component's own.
14822
+ publicSurfaceBindingRule,
14823
+ publicSurfaceDeclaredTypeRule,
14824
+ publicSurfaceBoundContractRule,
14280
14825
  cyclesRule,
14281
14826
  // Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
14282
14827
  // reader sees the broken edge finding next to the unused-detection fallout
@@ -14284,7 +14829,11 @@ var init_repository = __esm({
14284
14829
  dispatchTableBindingsRule,
14285
14830
  dispatchStepRoutingRule,
14286
14831
  lifecycleRule,
14832
+ // The declared entrypoint's own prose before the walk that its declaration
14833
+ // silences, then the walk, then the types no walk can reach.
14834
+ invokedByDescriptionRule,
14287
14835
  reachabilityRule,
14836
+ unusedTypesRule,
14288
14837
  // The declaration (spec-scoped, refused at the write boundary) before the
14289
14838
  // round-trip consequences it enables.
14290
14839
  durabilityDeclarationRule,
@@ -14296,8 +14845,13 @@ var init_repository = __esm({
14296
14845
  untypedSeamRule,
14297
14846
  proseClaimRule,
14298
14847
  // Invariant registry rides with the semantic-edge family: declared entity
14299
- // invariants must be asserted on every write path (declarations, not proofs).
14848
+ // invariants must be asserted on every write path (declarations, not
14849
+ // proofs). Three questions of one registry: are the entity's ids unique,
14850
+ // does each invariant reach every write path of its owner, and does every
14851
+ // asserted reference name something declared.
14852
+ uniqueInvariantIdsRule,
14300
14853
  invariantBackingRule,
14854
+ invariantReferencesRule,
14301
14855
  // Pub/sub completeness: emitted topics need subscribers and vice versa.
14302
14856
  eventTopologyRule,
14303
14857
  // Code↔spec: structural conformance consumes the injected CodeModel (built
@@ -14316,10 +14870,22 @@ var init_repository = __esm({
14316
14870
  hiddenStateRule,
14317
14871
  dependencyConformanceRule,
14318
14872
  // Integration wiring proof rides after the code↔spec family: it consumes
14319
- // the same code model and speaks about the same sourcePath modules.
14320
- integrationConformanceRule,
14873
+ // the same code model and speaks about the same sourcePath modules. Four
14874
+ // questions about one harness, in the order a reader meets them: is one
14875
+ // expected here, does the declared one exist, does it wire the real
14876
+ // modules, does it name every narrated path.
14877
+ integrationSimDeclarationRule,
14878
+ integrationSimFileRule,
14879
+ integrationSimWiringRule,
14880
+ integrationSimCoverageRule,
14321
14881
  couplingRule,
14322
- languageRule,
14882
+ // Target-language fit in two questions: what a CONTRACT may name, and what
14883
+ // a NARRATIVE may describe.
14884
+ signatureLanguageBuiltinsRule,
14885
+ narrativeLanguageConstructsRule,
14886
+ // The declaration before the consequences: which stereotype may bind a
14887
+ // technology at all, then where its name may appear.
14888
+ technologyBindingRule,
14323
14889
  technologyRule,
14324
14890
  namingRule,
14325
14891
  complexityRule,
@@ -15156,6 +15722,10 @@ function buildRuleContext(opts) {
15156
15722
  const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
15157
15723
  let dependencyEdgesMemo;
15158
15724
  const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
15725
+ let specIdsMemo;
15726
+ const specIds = () => specIdsMemo ??= buildSpecIds(ctx);
15727
+ let implementationMethodsMemo;
15728
+ const implementationMethods = () => implementationMethodsMemo ??= buildImplementationMethods(ctx);
15159
15729
  const ctx = {
15160
15730
  system,
15161
15731
  subsystems,
@@ -15190,6 +15760,8 @@ function buildRuleContext(opts) {
15190
15760
  importGraph,
15191
15761
  ownershipIndex,
15192
15762
  dependencyEdges,
15763
+ specIds,
15764
+ implementationMethods,
15193
15765
  complexityConfigFor,
15194
15766
  documentationConfigFor,
15195
15767
  namingConfigFor,
@@ -17054,8 +17626,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
17054
17626
  const componentById = new Map(graph.components.map((c) => [c.id, c]));
17055
17627
  const resolveComponent = (id) => {
17056
17628
  if (componentById.has(id)) return componentById.get(id);
17057
- const matches2 = graph.components.filter((c) => c.id.endsWith(`::${id}`));
17058
- return matches2.length === 1 ? matches2[0] : void 0;
17629
+ const matches = graph.components.filter((c) => c.id.endsWith(`::${id}`));
17630
+ return matches.length === 1 ? matches[0] : void 0;
17059
17631
  };
17060
17632
  const entry = resolveComponent(componentId);
17061
17633
  if (!entry) {
@@ -18316,9 +18888,9 @@ var init_specs2 = __esm({
18316
18888
  }
18317
18889
  }
18318
18890
  const suffix = `::${id}`;
18319
- const matches2 = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
18320
- if (matches2.length === 1) {
18321
- return index.paths.subsystem[matches2[0]];
18891
+ const matches = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
18892
+ if (matches.length === 1) {
18893
+ return index.paths.subsystem[matches[0]];
18322
18894
  }
18323
18895
  if (pathExists(this.paths.specsSubsystemsDir()) && listFiles(this.paths.specsSubsystemsDir(), ".yaml").length > 0) {
18324
18896
  return path16.join(this.paths.specsSubsystemsDir(), `${id}.yaml`);