@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/cli/index.js CHANGED
@@ -65,7 +65,7 @@ var init_defaults = __esm({
65
65
  copilot: ".github/prompts",
66
66
  codex: ".codex/agents"
67
67
  };
68
- WAIRON_VERSION = "5.1.1-dev.26";
68
+ WAIRON_VERSION = "5.1.1-dev.27";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -1966,8 +1966,8 @@ function extractTypeIdentifiers(typeStr) {
1966
1966
  cleaned = cleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
1967
1967
  cleaned = cleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
1968
1968
  cleaned = cleaned.replace(/(["'`])(?:\\.|[^\\])*?\1/g, " ");
1969
- const matches2 = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
1970
- return matches2.filter((t) => {
1969
+ const matches = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
1970
+ return matches.filter((t) => {
1971
1971
  if (!/[a-zA-Z0-9]/.test(t)) return false;
1972
1972
  if (/^\d+$/.test(t)) return false;
1973
1973
  return true;
@@ -8931,6 +8931,37 @@ function buildDependencyEdges(ctx) {
8931
8931
  }
8932
8932
  return { all, matrix };
8933
8933
  }
8934
+ function buildSpecIds(ctx) {
8935
+ const out = [];
8936
+ for (const s of ctx.subsystems) out.push({ id: s.id, kind: "Subsystem" });
8937
+ for (const c of ctx.components) out.push({ id: c.id, kind: "Component" });
8938
+ for (const i of ctx.interfaces) out.push({ id: i.id, kind: "Interface" });
8939
+ for (const im of ctx.implementations) out.push({ id: im.id, kind: "Implementation" });
8940
+ for (const t of ctx.types) out.push({ id: t.id, kind: "Type" });
8941
+ return out;
8942
+ }
8943
+ function buildImplementationMethods(ctx) {
8944
+ const out = [];
8945
+ for (const impl of ctx.implementations) {
8946
+ const contract = ctx.interfaceMap.get(impl.contract);
8947
+ if (!contract) continue;
8948
+ const component = ctx.componentMap.get(contract.component);
8949
+ if (!component) continue;
8950
+ if (ctx.isInChainedSubproject(component.subsystem)) continue;
8951
+ const draftContext = ctx.isImplementationDraft(impl);
8952
+ for (const method2 of impl.methods) {
8953
+ const sourceFile = methodSourceFile(method2, impl.sourcePath);
8954
+ out.push({
8955
+ implementation: impl,
8956
+ method: method2,
8957
+ component,
8958
+ ...sourceFile !== void 0 ? { sourceFile } : {},
8959
+ draftContext
8960
+ });
8961
+ }
8962
+ }
8963
+ return out;
8964
+ }
8934
8965
  function offTreeEdge(ctx, from, ref, fromDraft) {
8935
8966
  const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
8936
8967
  const external = ctx.isExternalNamespaceRef(ref);
@@ -9031,48 +9062,58 @@ var init_hierarchy_integrity = __esm({
9031
9062
  }
9032
9063
  });
9033
9064
 
9034
- // src/core/rules/integrity/namespace-hygiene.ts
9035
- var namespaceHygieneRule;
9036
- var init_namespace_hygiene = __esm({
9037
- "src/core/rules/integrity/namespace-hygiene.ts"() {
9065
+ // src/core/rules/integrity/reserved-id-segments.ts
9066
+ var reservedIdSegmentsRule;
9067
+ var init_reserved_id_segments = __esm({
9068
+ "src/core/rules/integrity/reserved-id-segments.ts"() {
9038
9069
  "use strict";
9039
- namespaceHygieneRule = {
9040
- name: "namespace-hygiene",
9041
- 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.',
9070
+ reservedIdSegmentsRule = {
9071
+ name: "reserved-id-segments",
9072
+ 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.',
9073
+ codes: [
9074
+ { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
9075
+ ],
9076
+ check(ctx) {
9077
+ for (const spec of ctx.specIds()) {
9078
+ if (!ctx.isSpecInScope(spec.id)) continue;
9079
+ if (!spec.id.split("::").includes("super")) continue;
9080
+ ctx.addIssue(
9081
+ "error",
9082
+ "RESERVED_ID_SEGMENT",
9083
+ `${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.`,
9084
+ spec.id
9085
+ );
9086
+ }
9087
+ }
9088
+ };
9089
+ }
9090
+ });
9091
+
9092
+ // src/core/rules/integrity/namespace-shadowing.ts
9093
+ var namespaceShadowingRule;
9094
+ var init_namespace_shadowing = __esm({
9095
+ "src/core/rules/integrity/namespace-shadowing.ts"() {
9096
+ "use strict";
9097
+ namespaceShadowingRule = {
9098
+ name: "namespace-shadowing",
9099
+ 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.",
9042
9100
  codes: [
9043
- { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' },
9044
9101
  { code: "NAMESPACE_SHADOWING", defaultSeverity: "error", summary: "Subproject-local id shadows a root subsystem id (bare references anchor to the root)" }
9045
9102
  ],
9046
9103
  check(ctx) {
9047
9104
  const rootSubs = new Set(ctx.subsystems.filter((s) => !s.id.includes("::")).map((s) => s.id));
9048
- const checkId = (id, kindLabel) => {
9049
- if (!ctx.isSpecInScope(id)) return;
9050
- const segments = id.split("::");
9051
- if (segments.includes("super")) {
9052
- ctx.addIssue(
9053
- "error",
9054
- "RESERVED_ID_SEGMENT",
9055
- `${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.`,
9056
- id
9057
- );
9058
- }
9059
- if (segments.length > 1) {
9060
- const local = segments[segments.length - 1];
9061
- if (rootSubs.has(local)) {
9062
- ctx.addIssue(
9063
- "error",
9064
- "NAMESPACE_SHADOWING",
9065
- `${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.`,
9066
- id
9067
- );
9068
- }
9069
- }
9070
- };
9071
- for (const s of ctx.subsystems) checkId(s.id, "Subsystem");
9072
- for (const c of ctx.components) checkId(c.id, "Component");
9073
- for (const i of ctx.interfaces) checkId(i.id, "Interface");
9074
- for (const im of ctx.implementations) checkId(im.id, "Implementation");
9075
- for (const t of ctx.types) checkId(t.id, "Type");
9105
+ for (const spec of ctx.specIds()) {
9106
+ if (!ctx.isSpecInScope(spec.id)) continue;
9107
+ const segments = spec.id.split("::");
9108
+ const local = segments[segments.length - 1];
9109
+ if (segments.length <= 1 || !rootSubs.has(local)) continue;
9110
+ ctx.addIssue(
9111
+ "error",
9112
+ "NAMESPACE_SHADOWING",
9113
+ `${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.`,
9114
+ spec.id
9115
+ );
9116
+ }
9076
9117
  }
9077
9118
  };
9078
9119
  }
@@ -9217,10 +9258,48 @@ var init_signature_type_references = __esm({
9217
9258
  }
9218
9259
  });
9219
9260
 
9220
- // src/core/rules/integrity/public-surface.ts
9221
- var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceRule;
9222
- var init_public_surface = __esm({
9223
- "src/core/rules/integrity/public-surface.ts"() {
9261
+ // src/core/rules/integrity/public-surface-binding.ts
9262
+ var publicSurfaceBindingRule;
9263
+ var init_public_surface_binding = __esm({
9264
+ "src/core/rules/integrity/public-surface-binding.ts"() {
9265
+ "use strict";
9266
+ init_models();
9267
+ publicSurfaceBindingRule = {
9268
+ name: "public-surface-binding",
9269
+ 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.",
9270
+ codes: [
9271
+ { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9272
+ { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9273
+ { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" }
9274
+ ],
9275
+ check(ctx) {
9276
+ for (const sub of ctx.subsystems) {
9277
+ const isDraftCtx = isDraftSubsystem(sub);
9278
+ for (const pi of sub.publicInterfaces) {
9279
+ if (!pi.component) {
9280
+ 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);
9281
+ continue;
9282
+ }
9283
+ const backing = ctx.componentMap.get(pi.component);
9284
+ if (!backing) {
9285
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9286
+ continue;
9287
+ }
9288
+ const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9289
+ if (!isSubsystemOwner) {
9290
+ 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);
9291
+ }
9292
+ }
9293
+ }
9294
+ }
9295
+ };
9296
+ }
9297
+ });
9298
+
9299
+ // src/core/rules/integrity/public-surface-declared-type.ts
9300
+ var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceDeclaredTypeRule;
9301
+ var init_public_surface_declared_type = __esm({
9302
+ "src/core/rules/integrity/public-surface-declared-type.ts"() {
9224
9303
  "use strict";
9225
9304
  init_models();
9226
9305
  pubTypeMatches = (piType, ct, portalType) => {
@@ -9254,45 +9333,22 @@ var init_public_surface = __esm({
9254
9333
  return "a compatible component";
9255
9334
  }
9256
9335
  };
9257
- publicSurfaceRule = {
9258
- name: "public-surface",
9259
- 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.",
9336
+ publicSurfaceDeclaredTypeRule = {
9337
+ name: "public-surface-declared-type",
9338
+ 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.",
9260
9339
  codes: [
9261
- { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9262
- { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9263
- { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" },
9264
9340
  { code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
9265
- { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" },
9266
9341
  { code: "PUBLIC_INTERFACE_EVENT_MISTYPED", defaultSeverity: "warning", summary: "Custom interface describing eventing backed by a non-event component" }
9267
9342
  ],
9268
9343
  check(ctx) {
9269
9344
  for (const sub of ctx.subsystems) {
9270
9345
  const isDraftCtx = isDraftSubsystem(sub);
9271
9346
  for (const pi of sub.publicInterfaces) {
9272
- if (!pi.component) {
9273
- 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);
9274
- continue;
9275
- }
9276
- const backing = ctx.componentMap.get(pi.component);
9277
- if (!backing) {
9278
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9279
- continue;
9280
- }
9281
- const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9282
- if (!isSubsystemOwner) {
9283
- 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);
9284
- }
9347
+ const backing = pi.component ? ctx.componentMap.get(pi.component) : void 0;
9348
+ if (!backing) continue;
9285
9349
  if (!pubTypeMatches(pi.type, backing.componentType, backing.portalType)) {
9286
9350
  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);
9287
9351
  }
9288
- if (pi.interface) {
9289
- const intf = ctx.interfaceMap.get(pi.interface);
9290
- if (!intf) {
9291
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9292
- } else if (intf.component !== pi.component) {
9293
- 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);
9294
- }
9295
- }
9296
9352
  if (pi.type === "Custom" && EVENT_VOCAB.test(pi.details)) {
9297
9353
  const eventCapable = backing.componentType === "Observer" || backing.componentType === "Portal" && backing.portalType === "MessageBus";
9298
9354
  if (!eventCapable) {
@@ -9306,6 +9362,37 @@ var init_public_surface = __esm({
9306
9362
  }
9307
9363
  });
9308
9364
 
9365
+ // src/core/rules/integrity/public-surface-bound-contract.ts
9366
+ var publicSurfaceBoundContractRule;
9367
+ var init_public_surface_bound_contract = __esm({
9368
+ "src/core/rules/integrity/public-surface-bound-contract.ts"() {
9369
+ "use strict";
9370
+ init_models();
9371
+ publicSurfaceBoundContractRule = {
9372
+ name: "public-surface-bound-contract",
9373
+ description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
9374
+ codes: [
9375
+ { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
9376
+ ],
9377
+ check(ctx) {
9378
+ for (const sub of ctx.subsystems) {
9379
+ const isDraftCtx = isDraftSubsystem(sub);
9380
+ for (const pi of sub.publicInterfaces) {
9381
+ if (!pi.interface) continue;
9382
+ if (!pi.component || !ctx.componentMap.has(pi.component)) continue;
9383
+ const intf = ctx.interfaceMap.get(pi.interface);
9384
+ if (!intf) {
9385
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9386
+ } else if (intf.component !== pi.component) {
9387
+ 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);
9388
+ }
9389
+ }
9390
+ }
9391
+ }
9392
+ };
9393
+ }
9394
+ });
9395
+
9309
9396
  // src/core/rules/integrity/lint-allows.ts
9310
9397
  var lintAllowsRule;
9311
9398
  var init_lint_allows = __esm({
@@ -9911,7 +9998,7 @@ var init_narrative_jump_edges = __esm({
9911
9998
  }
9912
9999
  });
9913
10000
 
9914
- // src/core/rules/narrative/narrative-antipatterns.ts
10001
+ // src/core/rules/narrative/completed-step-graph.ts
9915
10002
  function completedStepGraph(steps) {
9916
10003
  const numbers = steps.flatMap((s) => [
9917
10004
  s.stepNumber,
@@ -9941,6 +10028,14 @@ function isUnavoidable(steps, target) {
9941
10028
  if (s.type === "return" || s.type === "throw") return true;
9942
10029
  const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
9943
10030
  if (arms.length) return arms.every((e) => completes.has(e));
10031
+ const guaranteed = s.endStep !== void 0 && (s.type === "try" || s.type === "loop" && s.loopKind === "doWhile");
10032
+ if (guaranteed) {
10033
+ const bodyEntry = graph.nextOf(n);
10034
+ if (bodyEntry !== void 0 && bodyEntry <= s.endStep) {
10035
+ if (s.type === "loop") return completes.has(bodyEntry);
10036
+ if (bodyEntry === target) return false;
10037
+ }
10038
+ }
9944
10039
  return graph.successorsOf(n).some((t) => completes.has(t));
9945
10040
  };
9946
10041
  const order = [...graph.stepNumbers].reverse();
@@ -10004,25 +10099,27 @@ function stronglyConnected(nums, successorsOf) {
10004
10099
  }
10005
10100
  return sccs;
10006
10101
  }
10007
- var byCodeUnit, narrativeAntipatternsRule;
10008
- var init_narrative_antipatterns = __esm({
10009
- "src/core/rules/narrative/narrative-antipatterns.ts"() {
10102
+ var init_completed_step_graph = __esm({
10103
+ "src/core/rules/narrative/completed-step-graph.ts"() {
10010
10104
  "use strict";
10011
10105
  init_models();
10012
- byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
10013
- narrativeAntipatternsRule = {
10014
- name: "narrative-antipatterns",
10015
- 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.",
10106
+ }
10107
+ });
10108
+
10109
+ // src/core/rules/narrative/meaningless-branches.ts
10110
+ var meaninglessBranchesRule;
10111
+ var init_meaningless_branches = __esm({
10112
+ "src/core/rules/narrative/meaningless-branches.ts"() {
10113
+ "use strict";
10114
+ init_completed_step_graph();
10115
+ meaninglessBranchesRule = {
10116
+ name: "meaningless-branches",
10117
+ 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.",
10016
10118
  codes: [
10017
- { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" },
10018
- { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" },
10019
- { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10119
+ { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
10020
10120
  ],
10021
10121
  check(ctx) {
10022
- const callEdges = [];
10023
10122
  for (const impl of ctx.implementations) {
10024
- const contract = ctx.interfaceMap.get(impl.contract);
10025
- const component = contract ? ctx.componentMap.get(contract.component) : void 0;
10026
10123
  const isDraftCtx = ctx.isImplementationDraft(impl);
10027
10124
  for (const implMethod of impl.methods) {
10028
10125
  const steps = implMethod.narrative;
@@ -10061,6 +10158,33 @@ var init_narrative_antipatterns = __esm({
10061
10158
  }
10062
10159
  }
10063
10160
  }
10161
+ }
10162
+ }
10163
+ }
10164
+ };
10165
+ }
10166
+ });
10167
+
10168
+ // src/core/rules/narrative/inescapable-cycles.ts
10169
+ var inescapableCyclesRule;
10170
+ var init_inescapable_cycles = __esm({
10171
+ "src/core/rules/narrative/inescapable-cycles.ts"() {
10172
+ "use strict";
10173
+ init_completed_step_graph();
10174
+ inescapableCyclesRule = {
10175
+ name: "inescapable-cycles",
10176
+ 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.",
10177
+ codes: [
10178
+ { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
10179
+ ],
10180
+ check(ctx) {
10181
+ for (const impl of ctx.implementations) {
10182
+ const isDraftCtx = ctx.isImplementationDraft(impl);
10183
+ for (const implMethod of impl.methods) {
10184
+ const steps = implMethod.narrative;
10185
+ if (!steps.length) continue;
10186
+ const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
10187
+ const graph = completedStepGraph(steps);
10064
10188
  for (const scc of stronglyConnected(graph.stepNumbers, graph.successorsOf)) {
10065
10189
  const inScc = new Set(scc);
10066
10190
  const isCycle = scc.length > 1 || graph.successorsOf(scc[0]).includes(scc[0]);
@@ -10081,7 +10205,35 @@ var init_narrative_antipatterns = __esm({
10081
10205
  );
10082
10206
  }
10083
10207
  }
10084
- if (!component) continue;
10208
+ }
10209
+ }
10210
+ }
10211
+ };
10212
+ }
10213
+ });
10214
+
10215
+ // src/core/rules/narrative/unconditional-call-cycles.ts
10216
+ var byCodeUnit, unconditionalCallCyclesRule;
10217
+ var init_unconditional_call_cycles = __esm({
10218
+ "src/core/rules/narrative/unconditional-call-cycles.ts"() {
10219
+ "use strict";
10220
+ init_completed_step_graph();
10221
+ byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
10222
+ unconditionalCallCyclesRule = {
10223
+ name: "unconditional-call-cycles",
10224
+ 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.",
10225
+ codes: [
10226
+ { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10227
+ ],
10228
+ check(ctx) {
10229
+ const callEdges = [];
10230
+ for (const impl of ctx.implementations) {
10231
+ const contract = ctx.interfaceMap.get(impl.contract);
10232
+ const component = contract ? ctx.componentMap.get(contract.component) : void 0;
10233
+ if (!component) continue;
10234
+ for (const implMethod of impl.methods) {
10235
+ const steps = implMethod.narrative;
10236
+ if (!steps.length) continue;
10085
10237
  const fromKey = `${component.id}::${implMethod.name}`;
10086
10238
  for (const s of steps) {
10087
10239
  let toComponent;
@@ -10143,34 +10295,29 @@ var init_narrative_antipatterns = __esm({
10143
10295
  });
10144
10296
 
10145
10297
  // src/core/rules/narrative/narrative-detail.ts
10146
- var DEFAULT_MAX_UNNARRATED_COMPLEXITY, narrativeDetailRule;
10298
+ var narrativeDetailRule;
10147
10299
  var init_narrative_detail = __esm({
10148
10300
  "src/core/rules/narrative/narrative-detail.ts"() {
10149
10301
  "use strict";
10150
10302
  init_models();
10151
- DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
10152
10303
  narrativeDetailRule = {
10153
10304
  name: "narrative-detail",
10154
- 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.",
10305
+ 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.",
10155
10306
  codes: [
10156
10307
  { code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
10157
- { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" },
10158
- { 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)" },
10159
- { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10308
+ { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
10160
10309
  ],
10161
10310
  check(ctx) {
10162
- const code = ctx.codeIndex();
10163
10311
  for (const impl of ctx.implementations) {
10164
10312
  const contract = ctx.interfaceMap.get(impl.contract);
10165
10313
  if (!contract) continue;
10166
10314
  const component = ctx.componentMap.get(contract.component);
10167
- const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract.status === "draft" || contract.status === "design" || ctx.isComponentDraft(contract.component);
10315
+ const isDraftCtx = ctx.isImplementationDraft(impl);
10168
10316
  for (const implMethod of impl.methods) {
10169
10317
  const contractMethod = contract.methods.find((m) => m.name === implMethod.name);
10170
10318
  if (!contractMethod) continue;
10319
+ if (implMethod.narrative.length > 0) continue;
10171
10320
  const eff = effectiveDetail(implMethod, impl, component);
10172
- const hasNarrative = implMethod.narrative.length > 0;
10173
- if (hasNarrative) continue;
10174
10321
  if (eff.level === "full") {
10175
10322
  ctx.addIssue(
10176
10323
  eff.explicit ? "error" : "warning",
@@ -10181,34 +10328,6 @@ var init_narrative_detail = __esm({
10181
10328
  );
10182
10329
  continue;
10183
10330
  }
10184
- let complexityFired = false;
10185
- const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
10186
- const file = methodSourceFile(implMethod, impl.sourcePath);
10187
- if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
10188
- const facts = code.factsAt(file);
10189
- const symbol = implMethod.symbol ?? implMethod.name;
10190
- const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
10191
- const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
10192
- if (complexity !== void 0 && complexity > limit) {
10193
- complexityFired = true;
10194
- ctx.addIssue(
10195
- "warning",
10196
- "UNNARRATED_COMPLEXITY",
10197
- `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.`,
10198
- impl.id,
10199
- isDraftCtx
10200
- );
10201
- }
10202
- }
10203
- if (!complexityFired && eff.explicit && component && isLogic(component)) {
10204
- ctx.addIssue(
10205
- "warning",
10206
- "DETAIL_BELOW_STEREOTYPE",
10207
- `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.`,
10208
- impl.id,
10209
- isDraftCtx
10210
- );
10211
- }
10212
10331
  const prose = implMethod.intent ?? contractMethod.description;
10213
10332
  if (!passesIntentFloor(prose, implMethod.name)) {
10214
10333
  ctx.addIssue(
@@ -10226,6 +10345,71 @@ var init_narrative_detail = __esm({
10226
10345
  }
10227
10346
  });
10228
10347
 
10348
+ // src/core/rules/narrative/detail-sufficiency.ts
10349
+ var DEFAULT_MAX_UNNARRATED_COMPLEXITY, detailSufficiencyRule;
10350
+ var init_detail_sufficiency = __esm({
10351
+ "src/core/rules/narrative/detail-sufficiency.ts"() {
10352
+ "use strict";
10353
+ init_models();
10354
+ DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
10355
+ detailSufficiencyRule = {
10356
+ name: "detail-sufficiency",
10357
+ 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.",
10358
+ codes: [
10359
+ { 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)" },
10360
+ { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10361
+ ],
10362
+ check(ctx) {
10363
+ const code = ctx.codeIndex();
10364
+ const dialedDown = [];
10365
+ for (const impl of ctx.implementations) {
10366
+ const contract = ctx.interfaceMap.get(impl.contract);
10367
+ if (!contract) continue;
10368
+ const component = ctx.componentMap.get(contract.component);
10369
+ const draftContext = ctx.isImplementationDraft(impl);
10370
+ for (const method2 of impl.methods) {
10371
+ const detail = effectiveDetail(method2, impl, component);
10372
+ if (contract.methods.some((m) => m.name === method2.name) && method2.narrative.length === 0 && detail.level !== "full") {
10373
+ dialedDown.push({ implementation: impl, method: method2, component, detail, draftContext });
10374
+ }
10375
+ }
10376
+ }
10377
+ for (const entry of dialedDown) {
10378
+ const { implementation: impl, method: method2, component, detail } = entry;
10379
+ const tier = method2.conformance ?? impl.conformance ?? defaultConformanceTier(component);
10380
+ const file = methodSourceFile(method2, impl.sourcePath);
10381
+ const symbol = method2.symbol ?? method2.name;
10382
+ const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
10383
+ let complexity;
10384
+ if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
10385
+ const facts = code.factsAt(file);
10386
+ complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
10387
+ }
10388
+ if (complexity !== void 0 && complexity > limit) {
10389
+ ctx.addIssue(
10390
+ "warning",
10391
+ "UNNARRATED_COMPLEXITY",
10392
+ `Method "${method2.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.`,
10393
+ impl.id,
10394
+ entry.draftContext
10395
+ );
10396
+ continue;
10397
+ }
10398
+ if (detail.explicit && component && isLogic(component)) {
10399
+ ctx.addIssue(
10400
+ "warning",
10401
+ "DETAIL_BELOW_STEREOTYPE",
10402
+ `Method "${method2.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.`,
10403
+ impl.id,
10404
+ entry.draftContext
10405
+ );
10406
+ }
10407
+ }
10408
+ }
10409
+ };
10410
+ }
10411
+ });
10412
+
10229
10413
  // src/core/rules/intrinsic/portal-fields.ts
10230
10414
  var portalFieldsRule;
10231
10415
  var init_portal_fields = __esm({
@@ -10410,45 +10594,61 @@ var init_portal_endpoints = __esm({
10410
10594
  };
10411
10595
  portalsRule = {
10412
10596
  name: "portal-endpoints",
10413
- description: "A Portal binds every interface method to a concrete endpoint of the matching transport, and only a Portal may carry endpoints at all.",
10597
+ description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
10414
10598
  codes: [
10415
10599
  { code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
10416
- { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" },
10417
- { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10600
+ { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
10418
10601
  ],
10419
10602
  check(ctx) {
10420
10603
  for (const comp of ctx.components) {
10604
+ if (comp.componentType !== "Portal") continue;
10605
+ const expected = comp.portalType ? PORTAL_TRANSPORT[comp.portalType] : void 0;
10606
+ if (!expected) continue;
10421
10607
  const isDraftCtx = ctx.isComponentDraft(comp.id);
10422
- const compInterfaces = ctx.interfaces.filter((i) => i.component === comp.id);
10423
- if (comp.componentType === "Portal") {
10424
- if (!comp.portalType) continue;
10425
- const expected = PORTAL_TRANSPORT[comp.portalType];
10426
- if (!expected) continue;
10427
- for (const intf of compInterfaces) {
10428
- const isIntfDraft = intf.status === "draft" || intf.status === "design";
10429
- for (const m of intf.methods) {
10430
- if (!m.endpoint) {
10431
- ctx.addIssue(
10432
- "error",
10433
- "MISSING_ENDPOINT",
10434
- `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10435
- intf.id,
10436
- isDraftCtx || isIntfDraft
10437
- );
10438
- } else if (m.endpoint.transport !== expected) {
10439
- ctx.addIssue(
10440
- "error",
10441
- "ENDPOINT_TRANSPORT_MISMATCH",
10442
- `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}").`,
10443
- intf.id,
10444
- isDraftCtx || isIntfDraft
10445
- );
10446
- }
10608
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10609
+ const isIntfDraft = intf.status === "draft" || intf.status === "design";
10610
+ for (const m of intf.methods) {
10611
+ if (!m.endpoint) {
10612
+ ctx.addIssue(
10613
+ "error",
10614
+ "MISSING_ENDPOINT",
10615
+ `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10616
+ intf.id,
10617
+ isDraftCtx || isIntfDraft
10618
+ );
10619
+ } else if (m.endpoint.transport !== expected) {
10620
+ ctx.addIssue(
10621
+ "error",
10622
+ "ENDPOINT_TRANSPORT_MISMATCH",
10623
+ `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}").`,
10624
+ intf.id,
10625
+ isDraftCtx || isIntfDraft
10626
+ );
10447
10627
  }
10448
10628
  }
10449
- continue;
10450
10629
  }
10451
- for (const intf of compInterfaces) {
10630
+ }
10631
+ }
10632
+ };
10633
+ }
10634
+ });
10635
+
10636
+ // src/core/rules/doctrine/non-portal-endpoints.ts
10637
+ var nonPortalEndpointsRule;
10638
+ var init_non_portal_endpoints = __esm({
10639
+ "src/core/rules/doctrine/non-portal-endpoints.ts"() {
10640
+ "use strict";
10641
+ nonPortalEndpointsRule = {
10642
+ name: "non-portal-endpoints",
10643
+ 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.",
10644
+ codes: [
10645
+ { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10646
+ ],
10647
+ check(ctx) {
10648
+ for (const comp of ctx.components) {
10649
+ if (comp.componentType === "Portal") continue;
10650
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10651
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10452
10652
  const isIntfDraft = intf.status === "draft" || intf.status === "design";
10453
10653
  for (const m of intf.methods) {
10454
10654
  if (m.endpoint) {
@@ -10469,20 +10669,16 @@ var init_portal_endpoints = __esm({
10469
10669
  });
10470
10670
 
10471
10671
  // src/core/rules/doctrine/portal-call-auth.ts
10472
- var COMPONENT_REF_PREFIX, portalCallAuthRule;
10672
+ var portalCallAuthRule;
10473
10673
  var init_portal_call_auth = __esm({
10474
10674
  "src/core/rules/doctrine/portal-call-auth.ts"() {
10475
10675
  "use strict";
10476
- COMPONENT_REF_PREFIX = "component:";
10477
10676
  portalCallAuthRule = {
10478
10677
  name: "portal-call-auth",
10479
- 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.",
10678
+ 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.",
10480
10679
  codes: [
10481
10680
  { code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
10482
- { 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)" },
10483
- { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10484
- { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10485
- { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10681
+ { 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)" }
10486
10682
  ],
10487
10683
  check(ctx) {
10488
10684
  for (const impl of ctx.implementations) {
@@ -10505,8 +10701,7 @@ var init_portal_call_auth = __esm({
10505
10701
  draft
10506
10702
  );
10507
10703
  }
10508
- const from = step.auth?.from;
10509
- if (!from) {
10704
+ if (!step.auth?.from) {
10510
10705
  ctx.addIssue(
10511
10706
  "warning",
10512
10707
  "PORTAL_AUTH_UNMET",
@@ -10514,40 +10709,73 @@ var init_portal_call_auth = __esm({
10514
10709
  impl.id,
10515
10710
  draft
10516
10711
  );
10517
- continue;
10518
10712
  }
10519
- if (from.startsWith(COMPONENT_REF_PREFIX)) {
10520
- const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10521
- const src = ctx.componentMap.get(srcId);
10522
- if (!src) {
10523
- ctx.addIssue(
10524
- "warning",
10525
- "UNKNOWN_AUTH_SOURCE",
10526
- `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10527
- impl.id,
10528
- draft
10529
- );
10530
- } else {
10531
- if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10532
- ctx.addIssue(
10533
- "warning",
10534
- "AUTH_SOURCE_NOT_PROVIDER",
10535
- `Narrative step ${step.stepNumber} of "${method2.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).`,
10536
- impl.id,
10537
- draft
10538
- );
10539
- }
10540
- const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10541
- if (presenter && !wired) {
10542
- ctx.addIssue(
10543
- "warning",
10544
- "AUTH_SOURCE_UNWIRED",
10545
- `Narrative step ${step.stepNumber} of "${method2.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.`,
10546
- impl.id,
10547
- draft
10548
- );
10549
- }
10550
- }
10713
+ }
10714
+ }
10715
+ }
10716
+ }
10717
+ };
10718
+ }
10719
+ });
10720
+
10721
+ // src/core/rules/doctrine/auth-source-wiring.ts
10722
+ var COMPONENT_REF_PREFIX, authSourceWiringRule;
10723
+ var init_auth_source_wiring = __esm({
10724
+ "src/core/rules/doctrine/auth-source-wiring.ts"() {
10725
+ "use strict";
10726
+ COMPONENT_REF_PREFIX = "component:";
10727
+ authSourceWiringRule = {
10728
+ name: "auth-source-wiring",
10729
+ 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.",
10730
+ codes: [
10731
+ { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10732
+ { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10733
+ { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10734
+ ],
10735
+ check(ctx) {
10736
+ for (const impl of ctx.implementations) {
10737
+ const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
10738
+ const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
10739
+ const draft = ctx.isImplementationDraft(impl);
10740
+ for (const method2 of impl.methods ?? []) {
10741
+ for (const step of method2.narrative ?? []) {
10742
+ if (step.type !== "call") continue;
10743
+ if (!step.targetComponent || step.targetComponent === ownComponent) continue;
10744
+ const target = ctx.componentMap.get(step.targetComponent);
10745
+ if (!target || target.componentType !== "Portal") continue;
10746
+ if (!target.auth || target.auth.scheme === "none") continue;
10747
+ const from = step.auth?.from;
10748
+ if (!from || !from.startsWith(COMPONENT_REF_PREFIX)) continue;
10749
+ const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10750
+ const src = ctx.componentMap.get(srcId);
10751
+ if (!src) {
10752
+ ctx.addIssue(
10753
+ "warning",
10754
+ "UNKNOWN_AUTH_SOURCE",
10755
+ `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10756
+ impl.id,
10757
+ draft
10758
+ );
10759
+ continue;
10760
+ }
10761
+ if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10762
+ ctx.addIssue(
10763
+ "warning",
10764
+ "AUTH_SOURCE_NOT_PROVIDER",
10765
+ `Narrative step ${step.stepNumber} of "${method2.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).`,
10766
+ impl.id,
10767
+ draft
10768
+ );
10769
+ }
10770
+ const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10771
+ if (presenter && !wired) {
10772
+ ctx.addIssue(
10773
+ "warning",
10774
+ "AUTH_SOURCE_UNWIRED",
10775
+ `Narrative step ${step.stepNumber} of "${method2.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.`,
10776
+ impl.id,
10777
+ draft
10778
+ );
10551
10779
  }
10552
10780
  }
10553
10781
  }
@@ -11423,17 +11651,135 @@ var init_component_variants = __esm({
11423
11651
  }
11424
11652
  });
11425
11653
 
11426
- // src/core/rules/extension/declarative-assertions.ts
11654
+ // src/core/rules/extension/declared-assertion.ts
11655
+ function assertionsOfKind(ctx, kind) {
11656
+ return ctx.ext.assertions.filter(
11657
+ (a) => a.kind === kind
11658
+ );
11659
+ }
11427
11660
  function globToRegExp(glob) {
11428
11661
  const escaped = glob.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? ".*" : `\\${ch}`);
11429
11662
  return new RegExp(`^${escaped}$`);
11430
11663
  }
11431
- function matches(sel, comp, ctx) {
11664
+ function matchesSelector(sel, comp, ctx) {
11432
11665
  if (sel.componentType?.length && !sel.componentType.includes(comp.componentType)) return false;
11433
11666
  if (sel.profile?.length && !sel.profile.includes(ctx.getComponentProfile(comp.id))) return false;
11434
11667
  if (sel.id && !globToRegExp(sel.id).test(comp.id)) return false;
11435
11668
  return true;
11436
11669
  }
11670
+ function reportAssertion(ctx, a, message, specId, isDraftCtx) {
11671
+ const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11672
+ ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11673
+ }
11674
+ var init_declared_assertion = __esm({
11675
+ "src/core/rules/extension/declared-assertion.ts"() {
11676
+ "use strict";
11677
+ }
11678
+ });
11679
+
11680
+ // src/core/rules/extension/assertion-forbidden-edges.ts
11681
+ var assertionForbiddenEdgesRule;
11682
+ var init_assertion_forbidden_edges = __esm({
11683
+ "src/core/rules/extension/assertion-forbidden-edges.ts"() {
11684
+ "use strict";
11685
+ init_declared_assertion();
11686
+ assertionForbiddenEdgesRule = {
11687
+ name: "assertion-forbidden-edges",
11688
+ 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.",
11689
+ // Static codes are unknown here — packs bring their own. The validator
11690
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11691
+ codes: [],
11692
+ check(ctx) {
11693
+ for (const a of assertionsOfKind(ctx, "forbid-edge")) {
11694
+ for (const comp of ctx.components) {
11695
+ if (!matchesSelector(a.from, comp, ctx)) continue;
11696
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11697
+ const edges = a.relation.flatMap(
11698
+ (relation) => comp[relation].map((targetId) => ({ relation, targetId }))
11699
+ );
11700
+ for (const { relation, targetId } of edges) {
11701
+ const target = ctx.componentMap.get(targetId);
11702
+ if (!target || !matchesSelector(a.to, target, ctx)) continue;
11703
+ reportAssertion(
11704
+ ctx,
11705
+ a,
11706
+ `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`,
11707
+ comp.id,
11708
+ isDraftCtx
11709
+ );
11710
+ }
11711
+ }
11712
+ }
11713
+ }
11714
+ };
11715
+ }
11716
+ });
11717
+
11718
+ // src/core/rules/extension/assertion-required-fields.ts
11719
+ function fieldValue(spec, field) {
11720
+ const segments = field.split(".");
11721
+ if (segments[0] !== "ext") {
11722
+ return segments.length === 1 ? spec[field] : void 0;
11723
+ }
11724
+ let cur = spec.ext;
11725
+ for (const seg of segments.slice(1)) {
11726
+ if (cur === null || typeof cur !== "object") return void 0;
11727
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11728
+ cur = cur[seg];
11729
+ }
11730
+ return cur;
11731
+ }
11732
+ function holdersAt(ctx, level) {
11733
+ const holders = [];
11734
+ if (level === "component") {
11735
+ for (const c of ctx.components) {
11736
+ holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11737
+ }
11738
+ } else if (level === "interface") {
11739
+ for (const i of ctx.interfaces) {
11740
+ const comp = ctx.componentMap.get(i.component);
11741
+ if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11742
+ }
11743
+ } else {
11744
+ for (const impl of ctx.implementations) {
11745
+ const contract = ctx.interfaceMap.get(impl.contract);
11746
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11747
+ if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11748
+ }
11749
+ }
11750
+ return holders;
11751
+ }
11752
+ var assertionRequiredFieldsRule;
11753
+ var init_assertion_required_fields = __esm({
11754
+ "src/core/rules/extension/assertion-required-fields.ts"() {
11755
+ "use strict";
11756
+ init_declared_assertion();
11757
+ assertionRequiredFieldsRule = {
11758
+ name: "assertion-required-fields",
11759
+ 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.",
11760
+ // Static codes are unknown here — packs bring their own. The validator
11761
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11762
+ codes: [],
11763
+ check(ctx) {
11764
+ for (const a of assertionsOfKind(ctx, "require-field")) {
11765
+ for (const h of holdersAt(ctx, a.level)) {
11766
+ if (!matchesSelector(a.on, h.comp, ctx)) continue;
11767
+ const v = fieldValue(h.spec, a.field);
11768
+ if (v === void 0 || v === null) {
11769
+ reportAssertion(ctx, a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11770
+ continue;
11771
+ }
11772
+ if (a.values && !a.values.includes(String(v))) {
11773
+ 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);
11774
+ }
11775
+ }
11776
+ }
11777
+ }
11778
+ };
11779
+ }
11780
+ });
11781
+
11782
+ // src/core/rules/extension/assertion-endpoint-shapes.ts
11437
11783
  function endpointAddress(ep) {
11438
11784
  switch (ep.transport) {
11439
11785
  case "HTTP":
@@ -11454,92 +11800,42 @@ function endpointAddress(ep) {
11454
11800
  return ep.address;
11455
11801
  }
11456
11802
  }
11457
- function fieldValue(spec, field) {
11458
- const segments = field.split(".");
11459
- if (segments[0] !== "ext") {
11460
- return segments.length === 1 ? spec[field] : void 0;
11461
- }
11462
- let cur = spec.ext;
11463
- for (const seg of segments.slice(1)) {
11464
- if (cur === null || typeof cur !== "object") return void 0;
11465
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11466
- cur = cur[seg];
11803
+ function boundEndpoints(ctx, on) {
11804
+ const out = [];
11805
+ for (const intf of ctx.interfaces) {
11806
+ const comp = ctx.componentMap.get(intf.component);
11807
+ if (!comp || !matchesSelector(on, comp, ctx)) continue;
11808
+ const draft = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11809
+ for (const method2 of intf.methods) {
11810
+ if (!method2.endpoint) continue;
11811
+ out.push({ intf, method: method2, endpoint: method2.endpoint, draft });
11812
+ }
11467
11813
  }
11468
- return cur;
11814
+ return out;
11469
11815
  }
11470
- var declarativeAssertionsRule;
11471
- var init_declarative_assertions = __esm({
11472
- "src/core/rules/extension/declarative-assertions.ts"() {
11816
+ var assertionEndpointShapesRule;
11817
+ var init_assertion_endpoint_shapes = __esm({
11818
+ "src/core/rules/extension/assertion-endpoint-shapes.ts"() {
11473
11819
  "use strict";
11474
- declarativeAssertionsRule = {
11475
- name: "declarative-assertions",
11476
- 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.",
11820
+ init_declared_assertion();
11821
+ assertionEndpointShapesRule = {
11822
+ name: "assertion-endpoint-shapes",
11823
+ 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.",
11477
11824
  // Static codes are unknown here — packs bring their own. The validator
11478
11825
  // gathers every loaded assertion's fullCode into knownIssueCodes.
11479
11826
  codes: [],
11480
11827
  check(ctx) {
11481
- const assertions = ctx.ext.assertions;
11482
- if (!assertions.length) return;
11483
- const emit = (a, message, specId, isDraftCtx) => {
11484
- const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11485
- ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11486
- };
11487
- for (const a of assertions) {
11488
- if (a.kind === "forbid-edge") {
11489
- for (const comp of ctx.components) {
11490
- if (!matches(a.from, comp, ctx)) continue;
11491
- const isDraftCtx = ctx.isComponentDraft(comp.id);
11492
- for (const relation of a.relation) {
11493
- for (const targetId of comp[relation]) {
11494
- const target = ctx.componentMap.get(targetId);
11495
- if (!target || !matches(a.to, target, ctx)) continue;
11496
- emit(a, `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`, comp.id, isDraftCtx);
11497
- }
11498
- }
11499
- }
11500
- } else if (a.kind === "require-field") {
11501
- const holders = [];
11502
- if (a.level === "component") {
11503
- for (const c of ctx.components) holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11504
- } else if (a.level === "interface") {
11505
- for (const i of ctx.interfaces) {
11506
- const comp = ctx.componentMap.get(i.component);
11507
- if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11508
- }
11509
- } else {
11510
- for (const impl of ctx.implementations) {
11511
- const contract = ctx.interfaceMap.get(impl.contract);
11512
- const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11513
- if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11514
- }
11515
- }
11516
- for (const h of holders) {
11517
- if (!matches(a.on, h.comp, ctx)) continue;
11518
- const v = fieldValue(h.spec, a.field);
11519
- if (v === void 0 || v === null) {
11520
- emit(a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11521
- } else if (a.values && !a.values.includes(String(v))) {
11522
- 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);
11523
- }
11828
+ for (const a of assertionsOfKind(ctx, "endpoint-shape")) {
11829
+ const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11830
+ for (const bound2 of boundEndpoints(ctx, a.on)) {
11831
+ const { intf, method: method2, endpoint } = bound2;
11832
+ if (a.transport?.length && !a.transport.includes(endpoint.transport)) {
11833
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method2.name}" uses transport ${endpoint.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, bound2.draft);
11834
+ continue;
11524
11835
  }
11525
- } else if (a.kind === "endpoint-shape") {
11526
- const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11527
- for (const intf of ctx.interfaces) {
11528
- const comp = ctx.componentMap.get(intf.component);
11529
- if (!comp || !matches(a.on, comp, ctx)) continue;
11530
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11531
- for (const method2 of intf.methods) {
11532
- if (!method2.endpoint) continue;
11533
- const ep = method2.endpoint;
11534
- if (a.transport?.length && !a.transport.includes(ep.transport)) {
11535
- emit(a, `Endpoint of "${intf.id}.${method2.name}" uses transport ${ep.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, isDraftCtx);
11536
- continue;
11537
- }
11538
- const address = endpointAddress(ep);
11539
- if (pattern && !pattern.test(address)) {
11540
- emit(a, `Endpoint of "${intf.id}.${method2.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, isDraftCtx);
11541
- }
11542
- }
11836
+ const address = endpointAddress(endpoint);
11837
+ if (pattern && !pattern.test(address)) {
11838
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method2.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, bound2.draft);
11543
11839
  }
11544
11840
  }
11545
11841
  }
@@ -11979,16 +12275,13 @@ var reachabilityRule;
11979
12275
  var init_unused_detection = __esm({
11980
12276
  "src/core/rules/wiring/unused-detection.ts"() {
11981
12277
  "use strict";
11982
- init_models();
11983
12278
  init_narrative_graph_projector();
11984
12279
  reachabilityRule = {
11985
12280
  name: "unused-detection",
11986
- 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).",
12281
+ 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.",
11987
12282
  codes: [
11988
12283
  { code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
11989
12284
  { code: "UNUSED_METHOD", defaultSeverity: "warning", summary: "Method never called by any narrative step" },
11990
- { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" },
11991
- { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" },
11992
12285
  { code: "INVOKED_BY_REDUNDANT", defaultSeverity: "warning", summary: "invokedBy declaration on a method the internal narrative walk already reaches \u2014 stale, remove it" }
11993
12286
  ],
11994
12287
  check(ctx) {
@@ -12016,17 +12309,8 @@ var init_unused_detection = __esm({
12016
12309
  if (!m.invokedBy) continue;
12017
12310
  invokedBySeeds.push({ compId: intf.component, methodName: m.name });
12018
12311
  if (!ctx.isSpecInScope(intf.id)) continue;
12019
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
12020
- if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
12021
- ctx.addIssue(
12022
- "warning",
12023
- "INVOKED_BY_UNDESCRIBED",
12024
- `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.`,
12025
- intf.id,
12026
- isDraftCtx
12027
- );
12028
- }
12029
12312
  if (baseWalk.reachesMethod(intf.component, m.name)) {
12313
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
12030
12314
  ctx.addIssue(
12031
12315
  "warning",
12032
12316
  "INVOKED_BY_REDUNDANT",
@@ -12049,23 +12333,75 @@ var init_unused_detection = __esm({
12049
12333
  comp.id,
12050
12334
  isDraftCtx
12051
12335
  );
12052
- } else {
12053
- for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
12054
- for (const m of intf.methods) {
12055
- if (!reach.reachesMethod(comp.id, m.name)) {
12056
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
12057
- ctx.addIssue(
12058
- "warning",
12059
- "UNUSED_METHOD",
12060
- `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
12061
- intf.id,
12062
- isDraftCtx
12063
- );
12064
- }
12336
+ continue;
12337
+ }
12338
+ for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
12339
+ for (const m of intf.methods) {
12340
+ if (!reach.reachesMethod(comp.id, m.name)) {
12341
+ const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
12342
+ ctx.addIssue(
12343
+ "warning",
12344
+ "UNUSED_METHOD",
12345
+ `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
12346
+ intf.id,
12347
+ isDraftCtx
12348
+ );
12065
12349
  }
12066
12350
  }
12067
12351
  }
12068
12352
  }
12353
+ }
12354
+ };
12355
+ }
12356
+ });
12357
+
12358
+ // src/core/rules/wiring/invoked-by-description.ts
12359
+ var invokedByDescriptionRule;
12360
+ var init_invoked_by_description = __esm({
12361
+ "src/core/rules/wiring/invoked-by-description.ts"() {
12362
+ "use strict";
12363
+ init_models();
12364
+ invokedByDescriptionRule = {
12365
+ name: "invoked-by-description",
12366
+ 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.',
12367
+ codes: [
12368
+ { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
12369
+ ],
12370
+ check(ctx) {
12371
+ for (const intf of ctx.interfaces) {
12372
+ if (!ctx.componentMap.has(intf.component)) continue;
12373
+ if (!ctx.isSpecInScope(intf.id)) continue;
12374
+ for (const m of intf.methods) {
12375
+ if (!m.invokedBy) continue;
12376
+ if (passesIntentFloor(m.invokedBy.caller, m.name)) continue;
12377
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
12378
+ ctx.addIssue(
12379
+ "warning",
12380
+ "INVOKED_BY_UNDESCRIBED",
12381
+ `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.`,
12382
+ intf.id,
12383
+ isDraftCtx
12384
+ );
12385
+ }
12386
+ }
12387
+ }
12388
+ };
12389
+ }
12390
+ });
12391
+
12392
+ // src/core/rules/wiring/unused-types.ts
12393
+ var unusedTypesRule;
12394
+ var init_unused_types = __esm({
12395
+ "src/core/rules/wiring/unused-types.ts"() {
12396
+ "use strict";
12397
+ init_models();
12398
+ unusedTypesRule = {
12399
+ name: "unused-types",
12400
+ 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.",
12401
+ codes: [
12402
+ { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" }
12403
+ ],
12404
+ check(ctx) {
12069
12405
  const referencedTypes = /* @__PURE__ */ new Set();
12070
12406
  const markTypeReferenced = (ref) => {
12071
12407
  if (ctx.isBuiltinType(ref)) return;
@@ -12299,7 +12635,44 @@ var init_prose_claims = __esm({
12299
12635
  }
12300
12636
  });
12301
12637
 
12302
- // src/core/rules/wiring/invariant-backing.ts
12638
+ // src/core/rules/wiring/unique-invariant-ids.ts
12639
+ var uniqueInvariantIdsRule;
12640
+ var init_unique_invariant_ids = __esm({
12641
+ "src/core/rules/wiring/unique-invariant-ids.ts"() {
12642
+ "use strict";
12643
+ init_models();
12644
+ uniqueInvariantIdsRule = {
12645
+ name: "unique-invariant-ids",
12646
+ 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.`,
12647
+ codes: [
12648
+ { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
12649
+ ],
12650
+ check(ctx) {
12651
+ for (const t of ctx.types) {
12652
+ const invariants = t.invariants ?? [];
12653
+ if (invariants.length === 0) continue;
12654
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
12655
+ const entityDraft = sub !== void 0 && isDraftSubsystem(sub);
12656
+ const seen = /* @__PURE__ */ new Set();
12657
+ for (const inv of invariants) {
12658
+ if (seen.has(inv.id)) {
12659
+ ctx.addIssue(
12660
+ "error",
12661
+ "DUPLICATE_INVARIANT_ID",
12662
+ `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
12663
+ t.id,
12664
+ entityDraft
12665
+ );
12666
+ }
12667
+ seen.add(inv.id);
12668
+ }
12669
+ }
12670
+ }
12671
+ };
12672
+ }
12673
+ });
12674
+
12675
+ // src/core/rules/wiring/invariant-ref.ts
12303
12676
  function splitInvariantRef(ref) {
12304
12677
  const at = ref.lastIndexOf(".");
12305
12678
  if (at <= 0 || at === ref.length - 1) return null;
@@ -12323,6 +12696,14 @@ function refMatchesInvariant(ref, type, invariantId) {
12323
12696
  if (!(type.invariants ?? []).some((inv) => inv.id === invariantId)) return false;
12324
12697
  return typeMatchesRef(type, parts.typeRef);
12325
12698
  }
12699
+ var init_invariant_ref = __esm({
12700
+ "src/core/rules/wiring/invariant-ref.ts"() {
12701
+ "use strict";
12702
+ init_models();
12703
+ }
12704
+ });
12705
+
12706
+ // src/core/rules/wiring/invariant-backing.ts
12326
12707
  function stepAsserts(impl, methodName, type, invariantId) {
12327
12708
  const method2 = impl.methods.find((m) => m.name === methodName);
12328
12709
  if (!method2) return false;
@@ -12347,33 +12728,19 @@ var init_invariant_backing = __esm({
12347
12728
  "src/core/rules/wiring/invariant-backing.ts"() {
12348
12729
  "use strict";
12349
12730
  init_models();
12731
+ init_invariant_ref();
12350
12732
  invariantBackingRule = {
12351
12733
  name: "invariant-backing",
12352
- 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.',
12734
+ 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.',
12353
12735
  codes: [
12354
- { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" },
12355
12736
  { 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" },
12356
- { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" },
12357
- { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12737
+ { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
12358
12738
  ],
12359
12739
  check(ctx) {
12360
12740
  for (const t of ctx.types) {
12361
12741
  const invariants = t.invariants ?? [];
12362
12742
  if (invariants.length === 0) continue;
12363
12743
  const entityDraft = isEntityDraft(t, ctx);
12364
- const seen = /* @__PURE__ */ new Set();
12365
- for (const inv of invariants) {
12366
- if (seen.has(inv.id)) {
12367
- ctx.addIssue(
12368
- "error",
12369
- "DUPLICATE_INVARIANT_ID",
12370
- `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
12371
- t.id,
12372
- entityDraft
12373
- );
12374
- }
12375
- seen.add(inv.id);
12376
- }
12377
12744
  const comp = resolveComponentClass(t, ctx);
12378
12745
  if (!comp) {
12379
12746
  ctx.addIssue(
@@ -12415,6 +12782,24 @@ var init_invariant_backing = __esm({
12415
12782
  }
12416
12783
  }
12417
12784
  }
12785
+ }
12786
+ };
12787
+ }
12788
+ });
12789
+
12790
+ // src/core/rules/wiring/invariant-references.ts
12791
+ var invariantReferencesRule;
12792
+ var init_invariant_references = __esm({
12793
+ "src/core/rules/wiring/invariant-references.ts"() {
12794
+ "use strict";
12795
+ init_invariant_ref();
12796
+ invariantReferencesRule = {
12797
+ name: "invariant-references",
12798
+ 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.",
12799
+ codes: [
12800
+ { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12801
+ ],
12802
+ check(ctx) {
12418
12803
  for (const impl of ctx.implementations) {
12419
12804
  const isDraftCtx = ctx.isImplementationDraft(impl);
12420
12805
  for (const method2 of impl.methods) {
@@ -12746,54 +13131,45 @@ var init_call_conformance = __esm({
12746
13131
  ],
12747
13132
  check(ctx) {
12748
13133
  const code = ctx.codeIndex();
12749
- for (const impl of ctx.implementations) {
12750
- const contract = ctx.interfaceMap.get(impl.contract);
12751
- if (!contract) continue;
12752
- const component = ctx.componentMap.get(contract.component);
12753
- if (!component) continue;
12754
- if (ctx.isInChainedSubproject(component.subsystem)) continue;
12755
- const specTier = impl.conformance ?? defaultConformanceTier(component);
12756
- const isDraftCtx = ctx.isImplementationDraft(impl);
12757
- for (const implMethod of impl.methods) {
12758
- const tier = implMethod.conformance ?? specTier;
12759
- if (tier === "off") continue;
12760
- if (!implMethod.narrative.length) continue;
12761
- const file = methodSourceFile(implMethod, impl.sourcePath);
12762
- if (!file) continue;
12763
- const facts = code.factsAt(file);
12764
- if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12765
- const fnSymbol = implMethod.symbol ?? implMethod.name;
12766
- const callees = closedCallees(facts, fnSymbol);
12767
- if (!callees) continue;
12768
- const missing = [];
12769
- for (const step of implMethod.narrative) {
12770
- if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
12771
- if (!ctx.componentMap.has(step.targetComponent)) continue;
12772
- const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
12773
- for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
12774
- for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
12775
- const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
12776
- if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12777
- }
13134
+ for (const entry of ctx.implementationMethods()) {
13135
+ const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
13136
+ const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
13137
+ if (tier === "off") continue;
13138
+ if (!implMethod.narrative.length) continue;
13139
+ if (!file) continue;
13140
+ const facts = code.factsAt(file);
13141
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
13142
+ const fnSymbol = implMethod.symbol ?? implMethod.name;
13143
+ const callees = closedCallees(facts, fnSymbol);
13144
+ if (!callees) continue;
13145
+ const missing = [];
13146
+ for (const step of implMethod.narrative) {
13147
+ if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
13148
+ if (!ctx.componentMap.has(step.targetComponent)) continue;
13149
+ const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
13150
+ for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
13151
+ for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
13152
+ const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
13153
+ if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12778
13154
  }
12779
- if (accepted.has(fnSymbol)) continue;
12780
- if ([...accepted].some((name) => callees.has(name))) continue;
12781
- missing.push({
12782
- step: step.stepNumber,
12783
- target: `${step.targetComponent}.${step.targetMethod}`,
12784
- accepted: [...accepted]
12785
- });
12786
13155
  }
12787
- if (missing.length === 0) continue;
12788
- const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
12789
- ctx.addIssue(
12790
- "warning",
12791
- "CALL_STEP_UNREALIZED",
12792
- `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.`,
12793
- impl.id,
12794
- isDraftCtx
12795
- );
13156
+ if (accepted.has(fnSymbol)) continue;
13157
+ if ([...accepted].some((name) => callees.has(name))) continue;
13158
+ missing.push({
13159
+ step: step.stepNumber,
13160
+ target: `${step.targetComponent}.${step.targetMethod}`,
13161
+ accepted: [...accepted]
13162
+ });
12796
13163
  }
13164
+ if (missing.length === 0) continue;
13165
+ const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
13166
+ ctx.addIssue(
13167
+ "warning",
13168
+ "CALL_STEP_UNREALIZED",
13169
+ `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.`,
13170
+ impl.id,
13171
+ entry.draftContext
13172
+ );
12797
13173
  }
12798
13174
  }
12799
13175
  };
@@ -12960,20 +13336,95 @@ var init_dependency_conformance = __esm({
12960
13336
  }
12961
13337
  });
12962
13338
 
12963
- // src/core/rules/conformance/integration-conformance.ts
12964
- var integrationConformanceRule;
12965
- var init_integration_conformance = __esm({
12966
- "src/core/rules/conformance/integration-conformance.ts"() {
13339
+ // src/core/rules/conformance/integration-sim-declaration.ts
13340
+ var integrationSimDeclarationRule;
13341
+ var init_integration_sim_declaration = __esm({
13342
+ "src/core/rules/conformance/integration-sim-declaration.ts"() {
13343
+ "use strict";
13344
+ integrationSimDeclarationRule = {
13345
+ name: "integration-sim-declaration",
13346
+ 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.",
13347
+ codes: [
13348
+ { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
13349
+ ],
13350
+ check(ctx) {
13351
+ const adopted = /* @__PURE__ */ new Set();
13352
+ for (const impl of ctx.implementations) {
13353
+ if (!impl.simPath) continue;
13354
+ const contract = ctx.interfaceMap.get(impl.contract);
13355
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13356
+ if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
13357
+ adopted.add(comp.subsystem);
13358
+ }
13359
+ for (const impl of ctx.implementations) {
13360
+ if (impl.simPath) continue;
13361
+ const contract = ctx.interfaceMap.get(impl.contract);
13362
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13363
+ if (!comp) continue;
13364
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13365
+ if (!adopted.has(comp.subsystem)) continue;
13366
+ if (ctx.isImplementationDraft(impl)) continue;
13367
+ const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
13368
+ if (deps.length === 0) continue;
13369
+ ctx.addIssue(
13370
+ "warning",
13371
+ "MISSING_INTEGRATION_SIM",
13372
+ `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.`,
13373
+ impl.id
13374
+ );
13375
+ }
13376
+ }
13377
+ };
13378
+ }
13379
+ });
13380
+
13381
+ // src/core/rules/conformance/integration-sim-file.ts
13382
+ var integrationSimFileRule;
13383
+ var init_integration_sim_file = __esm({
13384
+ "src/core/rules/conformance/integration-sim-file.ts"() {
12967
13385
  "use strict";
12968
13386
  init_models();
12969
- integrationConformanceRule = {
12970
- name: "integration-conformance",
12971
- 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.",
13387
+ integrationSimFileRule = {
13388
+ name: "integration-sim-file",
13389
+ 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.",
12972
13390
  codes: [
12973
- { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" },
12974
- { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" },
12975
- { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" },
12976
- { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13391
+ { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
13392
+ ],
13393
+ check(ctx) {
13394
+ const code = ctx.codeIndex();
13395
+ for (const impl of ctx.implementations) {
13396
+ if (!impl.simPath) continue;
13397
+ const contract = ctx.interfaceMap.get(impl.contract);
13398
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13399
+ if (!comp) continue;
13400
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13401
+ const facts = code.factsAt(pathKey(impl.simPath));
13402
+ if (facts && facts.status === "analyzed") continue;
13403
+ 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";
13404
+ ctx.addIssue(
13405
+ "warning",
13406
+ "SIM_FILE_MISSING",
13407
+ `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
13408
+ impl.id,
13409
+ ctx.isImplementationDraft(impl)
13410
+ );
13411
+ }
13412
+ }
13413
+ };
13414
+ }
13415
+ });
13416
+
13417
+ // src/core/rules/conformance/integration-sim-wiring.ts
13418
+ var integrationSimWiringRule;
13419
+ var init_integration_sim_wiring = __esm({
13420
+ "src/core/rules/conformance/integration-sim-wiring.ts"() {
13421
+ "use strict";
13422
+ init_models();
13423
+ integrationSimWiringRule = {
13424
+ name: "integration-sim-wiring",
13425
+ 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.",
13426
+ codes: [
13427
+ { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
12977
13428
  ],
12978
13429
  check(ctx) {
12979
13430
  const code = ctx.codeIndex();
@@ -12985,48 +13436,18 @@ var init_integration_conformance = __esm({
12985
13436
  };
12986
13437
  const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
12987
13438
  const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
12988
- const adopted = /* @__PURE__ */ new Set();
12989
- for (const impl of ctx.implementations) {
12990
- if (!impl.simPath) continue;
12991
- const contract = ctx.interfaceMap.get(impl.contract);
12992
- const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12993
- if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
12994
- adopted.add(comp.subsystem);
12995
- }
12996
13439
  const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
12997
13440
  for (const impl of ctx.implementations) {
13441
+ if (!impl.simPath) continue;
12998
13442
  const contract = ctx.interfaceMap.get(impl.contract);
12999
13443
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13000
13444
  if (!comp) continue;
13001
13445
  if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13002
- const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
13003
- if (!impl.simPath) {
13004
- if (!adopted.has(comp.subsystem)) continue;
13005
- if (ctx.isImplementationDraft(impl)) continue;
13006
- if (deps.length === 0) continue;
13007
- ctx.addIssue(
13008
- "warning",
13009
- "MISSING_INTEGRATION_SIM",
13010
- `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.`,
13011
- impl.id
13012
- );
13013
- continue;
13014
- }
13015
13446
  const simPath = pathKey(impl.simPath);
13016
13447
  const facts = code.factsAt(simPath);
13017
- const isDraftCtx = ctx.isImplementationDraft(impl);
13018
- if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
13019
- 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";
13020
- ctx.addIssue(
13021
- "warning",
13022
- "SIM_FILE_MISSING",
13023
- `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
13024
- impl.id,
13025
- isDraftCtx
13026
- );
13027
- continue;
13028
- }
13448
+ if (!facts || facts.status !== "analyzed") continue;
13029
13449
  if (facts.analysisGrade !== "exact") continue;
13450
+ const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
13030
13451
  const reach = graph.reachFrom(simPath);
13031
13452
  const ownFiles = modulesOf(comp.id);
13032
13453
  const missing = [];
@@ -13043,15 +13464,43 @@ var init_integration_conformance = __esm({
13043
13464
  missing.push(label);
13044
13465
  }
13045
13466
  }
13046
- if (missing.length) {
13047
- ctx.addIssue(
13048
- "warning",
13049
- "UNWIRED_INTEGRATION_SIM",
13050
- `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.`,
13051
- impl.id,
13052
- isDraftCtx
13053
- );
13054
- }
13467
+ if (missing.length === 0) continue;
13468
+ ctx.addIssue(
13469
+ "warning",
13470
+ "UNWIRED_INTEGRATION_SIM",
13471
+ `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.`,
13472
+ impl.id,
13473
+ ctx.isImplementationDraft(impl)
13474
+ );
13475
+ }
13476
+ }
13477
+ };
13478
+ }
13479
+ });
13480
+
13481
+ // src/core/rules/conformance/integration-sim-coverage.ts
13482
+ var integrationSimCoverageRule;
13483
+ var init_integration_sim_coverage = __esm({
13484
+ "src/core/rules/conformance/integration-sim-coverage.ts"() {
13485
+ "use strict";
13486
+ init_models();
13487
+ integrationSimCoverageRule = {
13488
+ name: "integration-sim-coverage",
13489
+ 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.",
13490
+ codes: [
13491
+ { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13492
+ ],
13493
+ check(ctx) {
13494
+ const code = ctx.codeIndex();
13495
+ for (const impl of ctx.implementations) {
13496
+ if (!impl.simPath) continue;
13497
+ const contract = ctx.interfaceMap.get(impl.contract);
13498
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13499
+ if (!comp) continue;
13500
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13501
+ const facts = code.factsAt(pathKey(impl.simPath));
13502
+ if (!facts || facts.status !== "analyzed") continue;
13503
+ if (facts.analysisGrade !== "exact") continue;
13055
13504
  const simAnchors = new Set(facts.anchoredNames.filter((a) => a.startsWith("sim:")));
13056
13505
  if (simAnchors.size === 0) continue;
13057
13506
  const compPrefix = `sim:${comp.id}.`;
@@ -13072,15 +13521,14 @@ var init_integration_conformance = __esm({
13072
13521
  }
13073
13522
  }
13074
13523
  }
13075
- if (uncovered.length) {
13076
- ctx.addIssue(
13077
- "warning",
13078
- "SIM_PATH_UNCOVERED",
13079
- `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.`,
13080
- impl.id,
13081
- isDraftCtx
13082
- );
13083
- }
13524
+ if (uncovered.length === 0) continue;
13525
+ ctx.addIssue(
13526
+ "warning",
13527
+ "SIM_PATH_UNCOVERED",
13528
+ `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.`,
13529
+ impl.id,
13530
+ ctx.isImplementationDraft(impl)
13531
+ );
13084
13532
  }
13085
13533
  }
13086
13534
  };
@@ -13190,10 +13638,10 @@ var init_coupling_health = __esm({
13190
13638
  }
13191
13639
  });
13192
13640
 
13193
- // src/core/rules/heuristic/target-language.ts
13194
- var LANGUAGE_MARKERS, UNSUPPORTED_FLOW, languageRule;
13195
- var init_target_language = __esm({
13196
- "src/core/rules/heuristic/target-language.ts"() {
13641
+ // src/core/rules/heuristic/signature-language-builtins.ts
13642
+ var LANGUAGE_MARKERS, signatureLanguageBuiltinsRule;
13643
+ var init_signature_language_builtins = __esm({
13644
+ "src/core/rules/heuristic/signature-language-builtins.ts"() {
13197
13645
  "use strict";
13198
13646
  init_models();
13199
13647
  LANGUAGE_MARKERS = {
@@ -13228,6 +13676,68 @@ var init_target_language = __esm({
13228
13676
  csharp: /* @__PURE__ */ new Set(["task"]),
13229
13677
  go: /* @__PURE__ */ new Set(["chan", "rune"])
13230
13678
  };
13679
+ signatureLanguageBuiltinsRule = {
13680
+ name: "signature-language-builtins",
13681
+ 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).",
13682
+ codes: [
13683
+ { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
13684
+ ],
13685
+ check(ctx) {
13686
+ const markersFor = (family) => {
13687
+ const base = LANGUAGE_MARKERS[family];
13688
+ const extra = ctx.ext.languages[family]?.foreignBuiltins;
13689
+ if (!extra?.length) return base;
13690
+ return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
13691
+ };
13692
+ const families = [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)])];
13693
+ const scans = [];
13694
+ for (const intf of ctx.interfaces) {
13695
+ const comp = ctx.componentMap.get(intf.component);
13696
+ const lang = ctx.targetLanguageFor(comp?.subsystem);
13697
+ if (!lang) continue;
13698
+ const ownMarkers = markersFor(lang);
13699
+ if (!ownMarkers || ownMarkers.size === 0) continue;
13700
+ const draft = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
13701
+ for (const method2 of intf.methods) {
13702
+ for (const ref of methodTypeRefs(method2)) scans.push({ intf, method: method2, ref, lang, ownMarkers, draft });
13703
+ }
13704
+ }
13705
+ for (const scan of scans) {
13706
+ const refLower = scan.ref.toLowerCase();
13707
+ if (scan.ownMarkers.has(refLower)) continue;
13708
+ const foreign = families.find((family) => family !== scan.lang && markersFor(family)?.has(refLower));
13709
+ if (!foreign) continue;
13710
+ ctx.addIssue(
13711
+ "warning",
13712
+ "LANGUAGE_FOREIGN_BUILTIN",
13713
+ `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.`,
13714
+ scan.intf.id,
13715
+ scan.draft
13716
+ );
13717
+ }
13718
+ }
13719
+ };
13720
+ }
13721
+ });
13722
+
13723
+ // src/core/rules/heuristic/narrative-language-constructs.ts
13724
+ function constructsOf(step) {
13725
+ const constructs = [
13726
+ step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13727
+ ];
13728
+ if (step.detach) constructs.push("detach");
13729
+ return constructs;
13730
+ }
13731
+ function constructLabel(construct) {
13732
+ if (construct === "doWhile") return "a do-while loop";
13733
+ if (construct === "forEach" || construct === "for" || construct === "while") return `a ${construct} loop`;
13734
+ if (construct === "detach") return "a detached (fire-and-forget) call";
13735
+ return `a ${construct} step`;
13736
+ }
13737
+ var UNSUPPORTED_FLOW, narrativeLanguageConstructsRule;
13738
+ var init_narrative_language_constructs = __esm({
13739
+ "src/core/rules/heuristic/narrative-language-constructs.ts"() {
13740
+ "use strict";
13231
13741
  UNSUPPORTED_FLOW = {
13232
13742
  rust: {
13233
13743
  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",
@@ -13247,53 +13757,18 @@ var init_target_language = __esm({
13247
13757
  doWhile: "Python has no do-while \u2014 prefer loopKind: while, or state the emulation (while True + break)"
13248
13758
  }
13249
13759
  };
13250
- languageRule = {
13251
- name: "target-language",
13252
- 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).",
13760
+ narrativeLanguageConstructsRule = {
13761
+ name: "narrative-language-constructs",
13762
+ 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).",
13253
13763
  codes: [
13254
- { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" },
13255
13764
  { code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
13256
13765
  ],
13257
13766
  check(ctx) {
13258
- const markersFor = (family) => {
13259
- const base = LANGUAGE_MARKERS[family];
13260
- const extra = ctx.ext.languages[family]?.foreignBuiltins;
13261
- if (!extra?.length) return base;
13262
- return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
13263
- };
13264
- const families = /* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)]);
13265
13767
  const gapsFor = (lang) => ({
13266
13768
  ...UNSUPPORTED_FLOW[lang] ?? {},
13267
13769
  ...ctx.ext.languages[lang]?.unsupportedFlow ?? {}
13268
13770
  });
13269
- for (const intf of ctx.interfaces) {
13270
- const comp = ctx.componentMap.get(intf.component);
13271
- const lang = ctx.targetLanguageFor(comp?.subsystem);
13272
- if (!lang) continue;
13273
- const ownMarkers = markersFor(lang);
13274
- if (!ownMarkers || ownMarkers.size === 0) continue;
13275
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
13276
- for (const m of intf.methods) {
13277
- const refs = methodTypeRefs(m);
13278
- for (const ref of refs) {
13279
- const refLower = ref.toLowerCase();
13280
- if (ownMarkers.has(refLower)) continue;
13281
- for (const family of families) {
13282
- if (family === lang) continue;
13283
- if (markersFor(family)?.has(refLower)) {
13284
- ctx.addIssue(
13285
- "warning",
13286
- "LANGUAGE_FOREIGN_BUILTIN",
13287
- `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.`,
13288
- intf.id,
13289
- isDraftCtx
13290
- );
13291
- break;
13292
- }
13293
- }
13294
- }
13295
- }
13296
- }
13771
+ const scans = [];
13297
13772
  for (const impl of ctx.implementations) {
13298
13773
  const contract = ctx.interfaceMap.get(impl.contract);
13299
13774
  if (!contract) continue;
@@ -13302,28 +13777,54 @@ var init_target_language = __esm({
13302
13777
  if (!lang) continue;
13303
13778
  const gaps = gapsFor(lang);
13304
13779
  if (Object.keys(gaps).length === 0) continue;
13305
- const isDraftCtx = ctx.isImplementationDraft(impl);
13306
- for (const implMethod of impl.methods) {
13307
- for (const step of implMethod.narrative) {
13308
- const constructs = [
13309
- step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13310
- ];
13311
- if (step.detach) constructs.push("detach");
13312
- for (const construct of constructs) {
13313
- const guidance = gaps[construct];
13314
- if (!guidance) continue;
13315
- 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`;
13316
- ctx.addIssue(
13317
- "warning",
13318
- "LANGUAGE_FOREIGN_FLOW",
13319
- `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" uses ${label}, but the target language is ${lang}: ${guidance}.`,
13320
- impl.id,
13321
- isDraftCtx
13322
- );
13323
- }
13324
- }
13780
+ const draft = ctx.isImplementationDraft(impl);
13781
+ for (const method2 of impl.methods) {
13782
+ for (const step of method2.narrative) scans.push({ implementation: impl, method: method2, step, lang, gaps, draft });
13325
13783
  }
13326
13784
  }
13785
+ for (const scan of scans) {
13786
+ for (const construct of constructsOf(scan.step)) {
13787
+ const guidance = scan.gaps[construct];
13788
+ if (!guidance) continue;
13789
+ ctx.addIssue(
13790
+ "warning",
13791
+ "LANGUAGE_FOREIGN_FLOW",
13792
+ `Step ${scan.step.stepNumber} of "${scan.method.name}" in implementation "${scan.implementation.id}" uses ${constructLabel(construct)}, but the target language is ${scan.lang}: ${guidance}.`,
13793
+ scan.implementation.id,
13794
+ scan.draft
13795
+ );
13796
+ }
13797
+ }
13798
+ }
13799
+ };
13800
+ }
13801
+ });
13802
+
13803
+ // src/core/rules/heuristic/technology-binding.ts
13804
+ var DATA_LAYER, technologyBindingRule;
13805
+ var init_technology_binding = __esm({
13806
+ "src/core/rules/heuristic/technology-binding.ts"() {
13807
+ "use strict";
13808
+ DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13809
+ technologyBindingRule = {
13810
+ name: "technology-binding",
13811
+ 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.",
13812
+ codes: [
13813
+ { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13814
+ ],
13815
+ check(ctx) {
13816
+ for (const impl of ctx.implementations) {
13817
+ const intf = ctx.interfaceMap.get(impl.contract);
13818
+ const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13819
+ if (!impl.technologies?.length || !comp || DATA_LAYER.has(comp.componentType)) continue;
13820
+ ctx.addIssue(
13821
+ "warning",
13822
+ "TECH_ON_LOGIC_COMPONENT",
13823
+ `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.`,
13824
+ impl.id,
13825
+ ctx.isImplementationDraft(impl)
13826
+ );
13827
+ }
13327
13828
  }
13328
13829
  };
13329
13830
  }
@@ -13352,19 +13853,29 @@ function contractIdentifiers(m) {
13352
13853
  if (m.endpoint) parts.push(Object.values(m.endpoint).map(String).join(" "));
13353
13854
  return parts.join(" ");
13354
13855
  }
13355
- var DATA_LAYER, technologyRule;
13856
+ function implementationText(impl) {
13857
+ const parts = [impl.description, ...implementationSourceFiles(impl)];
13858
+ for (const m of impl.methods) {
13859
+ parts.push(m.intent);
13860
+ for (const s of m.narrative ?? []) {
13861
+ parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13862
+ for (const c of s.cases ?? []) parts.push(c.value);
13863
+ for (const c of s.catches ?? []) parts.push(c.error);
13864
+ }
13865
+ }
13866
+ return parts.filter(Boolean).join(" ");
13867
+ }
13868
+ var technologyRule;
13356
13869
  var init_technology_boundaries = __esm({
13357
13870
  "src/core/rules/heuristic/technology-boundaries.ts"() {
13358
13871
  "use strict";
13359
13872
  init_models();
13360
- DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13361
13873
  technologyRule = {
13362
13874
  name: "technology-boundaries",
13363
- 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.",
13875
+ 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.",
13364
13876
  codes: [
13365
13877
  { code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
13366
- { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" },
13367
- { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13878
+ { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
13368
13879
  ],
13369
13880
  check(ctx) {
13370
13881
  const ownerOf = /* @__PURE__ */ new Map();
@@ -13392,21 +13903,18 @@ var init_technology_boundaries = __esm({
13392
13903
  }
13393
13904
  return out;
13394
13905
  };
13395
- const homes = /* @__PURE__ */ new Map();
13906
+ const declarations = [];
13396
13907
  for (const impl of ctx.implementations) {
13397
13908
  if (!impl.technologies?.length) continue;
13398
13909
  const intf = ctx.interfaceMap.get(impl.contract);
13399
13910
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13400
13911
  if (!comp) continue;
13401
- if (!DATA_LAYER.has(comp.componentType)) {
13402
- ctx.addIssue(
13403
- "warning",
13404
- "TECH_ON_LOGIC_COMPONENT",
13405
- `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.`,
13406
- impl.id,
13407
- ctx.isImplementationDraft(impl)
13408
- );
13409
- }
13912
+ for (const tech of impl.technologies) declarations.push({ comp, tech });
13913
+ }
13914
+ const scopes = /* @__PURE__ */ new Map();
13915
+ const scopeOf2 = (comp) => {
13916
+ const cached = scopes.get(comp.id);
13917
+ if (cached) return cached;
13410
13918
  const compSet = ownsClosure(ownershipRoot(comp.id));
13411
13919
  const scope = new Set(compSet);
13412
13920
  for (const i of ctx.interfaces) if (compSet.has(i.component)) scope.add(i.id);
@@ -13421,15 +13929,18 @@ var init_technology_boundaries = __esm({
13421
13929
  if (subId === s.id || subId.startsWith(`${s.id}::`)) scope.add(s.id);
13422
13930
  }
13423
13931
  }
13424
- for (const tech of impl.technologies) {
13425
- const match = makeMatcher(tech);
13426
- if (!match) continue;
13427
- const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13428
- const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13429
- home.ownerComponents.add(comp.id);
13430
- for (const id of scope) home.scope.add(id);
13431
- homes.set(key, home);
13432
- }
13932
+ scopes.set(comp.id, scope);
13933
+ return scope;
13934
+ };
13935
+ const homes = /* @__PURE__ */ new Map();
13936
+ for (const { comp, tech } of declarations) {
13937
+ const match = makeMatcher(tech);
13938
+ if (!match) continue;
13939
+ const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13940
+ const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13941
+ home.ownerComponents.add(comp.id);
13942
+ for (const id of scopeOf2(comp)) home.scope.add(id);
13943
+ homes.set(key, home);
13433
13944
  }
13434
13945
  if (homes.size === 0) return;
13435
13946
  const ownersDesc = (h) => [...h.ownerComponents].map((c) => `"${c}"`).join(", ");
@@ -13493,16 +14004,7 @@ var init_technology_boundaries = __esm({
13493
14004
  }
13494
14005
  for (const impl of ctx.implementations) {
13495
14006
  if (home.scope.has(impl.id)) continue;
13496
- const parts = [impl.description, ...implementationSourceFiles(impl)];
13497
- for (const m of impl.methods) {
13498
- parts.push(m.intent);
13499
- for (const s of m.narrative ?? []) {
13500
- parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13501
- for (const c of s.cases ?? []) parts.push(c.value);
13502
- for (const c of s.catches ?? []) parts.push(c.error);
13503
- }
13504
- }
13505
- if (home.match(parts.filter(Boolean).join(" "))) {
14007
+ if (home.match(implementationText(impl))) {
13506
14008
  ctx.addIssue(
13507
14009
  "warning",
13508
14010
  "TECH_LEAKAGE",
@@ -14191,12 +14693,15 @@ var init_repository = __esm({
14191
14693
  "src/core/rules/repository.ts"() {
14192
14694
  "use strict";
14193
14695
  init_hierarchy_integrity();
14194
- init_namespace_hygiene();
14696
+ init_reserved_id_segments();
14697
+ init_namespace_shadowing();
14195
14698
  init_roundtrip_serialization();
14196
14699
  init_type_declarations();
14197
14700
  init_field_type_references();
14198
14701
  init_signature_type_references();
14199
- init_public_surface();
14702
+ init_public_surface_binding();
14703
+ init_public_surface_declared_type();
14704
+ init_public_surface_bound_contract();
14200
14705
  init_lint_allows();
14201
14706
  init_contract_symmetry();
14202
14707
  init_narrative_target_references();
@@ -14206,14 +14711,19 @@ var init_repository = __esm({
14206
14711
  init_narrative_step_config();
14207
14712
  init_narrative_reachability();
14208
14713
  init_narrative_jump_edges();
14209
- init_narrative_antipatterns();
14714
+ init_meaningless_branches();
14715
+ init_inescapable_cycles();
14716
+ init_unconditional_call_cycles();
14210
14717
  init_narrative_detail();
14718
+ init_detail_sufficiency();
14211
14719
  init_portal_fields();
14212
14720
  init_durability_declaration();
14213
14721
  init_logic_declaration();
14214
14722
  init_retired_stereotypes();
14215
14723
  init_portal_endpoints();
14724
+ init_non_portal_endpoints();
14216
14725
  init_portal_call_auth();
14726
+ init_auth_source_wiring();
14217
14727
  init_subsystem_boundary_dependencies();
14218
14728
  init_logic_dependency_class();
14219
14729
  init_data_block_dependencies();
@@ -14229,7 +14739,9 @@ var init_repository = __esm({
14229
14739
  init_pack_profile_stereotypes();
14230
14740
  init_pattern_references();
14231
14741
  init_component_variants();
14232
- init_declarative_assertions();
14742
+ init_assertion_forbidden_edges();
14743
+ init_assertion_required_fields();
14744
+ init_assertion_endpoint_shapes();
14233
14745
  init_pack_resolution();
14234
14746
  init_pack_reproducibility();
14235
14747
  init_dependency_cycles();
@@ -14237,10 +14749,14 @@ var init_repository = __esm({
14237
14749
  init_dispatch_step_routing();
14238
14750
  init_lifecycle_entrypoints();
14239
14751
  init_unused_detection();
14752
+ init_invoked_by_description();
14753
+ init_unused_types();
14240
14754
  init_durability_round_trip();
14241
14755
  init_untyped_seams();
14242
14756
  init_prose_claims();
14757
+ init_unique_invariant_ids();
14243
14758
  init_invariant_backing();
14759
+ init_invariant_references();
14244
14760
  init_event_topology();
14245
14761
  init_source_file_linkage();
14246
14762
  init_method_realization();
@@ -14248,9 +14764,14 @@ var init_repository = __esm({
14248
14764
  init_call_conformance();
14249
14765
  init_hidden_state();
14250
14766
  init_dependency_conformance();
14251
- init_integration_conformance();
14767
+ init_integration_sim_declaration();
14768
+ init_integration_sim_file();
14769
+ init_integration_sim_wiring();
14770
+ init_integration_sim_coverage();
14252
14771
  init_coupling_health();
14253
- init_target_language();
14772
+ init_signature_language_builtins();
14773
+ init_narrative_language_constructs();
14774
+ init_technology_binding();
14254
14775
  init_technology_boundaries();
14255
14776
  init_naming_conventions();
14256
14777
  init_complexity_and_metadata();
@@ -14260,8 +14781,11 @@ var init_repository = __esm({
14260
14781
  SDD_RULES = [
14261
14782
  hierarchyRule,
14262
14783
  // Namespace integrity right after hierarchy: unresolvable/unwritable ids
14263
- // explain many downstream findings, so surface them early in the list.
14264
- namespaceHygieneRule,
14784
+ // explain many downstream findings, so surface them early in the list. Two
14785
+ // questions of the same ids: the segment no id may spend, and the local name
14786
+ // that would anchor a bare reference to the root instead.
14787
+ reservedIdSegmentsRule,
14788
+ namespaceShadowingRule,
14265
14789
  roundtripRule,
14266
14790
  // The type vocabulary in three questions: what a type declares about
14267
14791
  // itself, then the identifiers its fields name, then the ones its
@@ -14287,18 +14811,30 @@ var init_repository = __esm({
14287
14811
  narrativeStepConfigRule,
14288
14812
  narrativeReachabilityRule,
14289
14813
  narrativeJumpEdgesRule,
14290
- // Antipatterns right after flow soundness: they analyze the same step
14291
- // graphs and only make sense once the graphs are structurally valid.
14292
- narrativeAntipatternsRule,
14814
+ // Antipatterns right after flow soundness: they read the same step graphs
14815
+ // and only make sense once the graphs are structurally valid. Three things
14816
+ // structure alone can prove, in widening scope: a decision that decides
14817
+ // nothing, a step cycle nothing leaves, and a call cycle with no guard.
14818
+ meaninglessBranchesRule,
14819
+ inescapableCyclesRule,
14820
+ unconditionalCallCyclesRule,
14821
+ // The detail dial in two questions: does a method carry the detail its level
14822
+ // promises, and is that level low enough to be hiding something.
14293
14823
  narrativeDetailRule,
14824
+ detailSufficiencyRule,
14294
14825
  // Field shape before endpoint bindings: a Portal-only field on the wrong
14295
14826
  // stereotype explains the endpoint findings around it, and this half is
14296
14827
  // spec-scoped so the write boundary refuses it first.
14297
14828
  portalFieldsRule,
14829
+ // Endpoints from both sides: a Portal binds every method to its transport,
14830
+ // and nothing that is not a Portal may bind one at all.
14298
14831
  portalsRule,
14299
- // Cross-call auth: a narrative call into an authed Portal must name its
14300
- // credential source (rides with the portal family).
14832
+ nonPortalEndpointsRule,
14833
+ // Cross-call auth rides with the portal family, in two questions: does a
14834
+ // call into an authed Portal present a credential properly, and does the
14835
+ // source it names resolve to a provider it is wired to.
14301
14836
  portalCallAuthRule,
14837
+ authSourceWiringRule,
14302
14838
  // Dependencies in five questions, one owner each: where an edge is
14303
14839
  // allowed to LAND (the boundary, and what an unresolved id means), then
14304
14840
  // the intra-subsystem matrix by the layer that answers for it — an
@@ -14328,9 +14864,18 @@ var init_repository = __esm({
14328
14864
  patternReferencesRule,
14329
14865
  variantReferencesRule,
14330
14866
  // Pack-instantiated declarative doctrine rides with the pack-reference
14331
- // family: same data source, same provenance-bearing findings.
14332
- declarativeAssertionsRule,
14333
- publicSurfaceRule,
14867
+ // family: same data source, same provenance-bearing findings. One rule per
14868
+ // assertion kind — the kind is what a pack author writes, and each kind
14869
+ // asks its own question of its own collection.
14870
+ assertionForbiddenEdgesRule,
14871
+ assertionRequiredFieldsRule,
14872
+ assertionEndpointShapesRule,
14873
+ // The published surface in three questions: what backs the entry, whether
14874
+ // that component's stereotype can realize the type it declares, and whether
14875
+ // the contract it binds is that component's own.
14876
+ publicSurfaceBindingRule,
14877
+ publicSurfaceDeclaredTypeRule,
14878
+ publicSurfaceBoundContractRule,
14334
14879
  cyclesRule,
14335
14880
  // Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
14336
14881
  // reader sees the broken edge finding next to the unused-detection fallout
@@ -14338,7 +14883,11 @@ var init_repository = __esm({
14338
14883
  dispatchTableBindingsRule,
14339
14884
  dispatchStepRoutingRule,
14340
14885
  lifecycleRule,
14886
+ // The declared entrypoint's own prose before the walk that its declaration
14887
+ // silences, then the walk, then the types no walk can reach.
14888
+ invokedByDescriptionRule,
14341
14889
  reachabilityRule,
14890
+ unusedTypesRule,
14342
14891
  // The declaration (spec-scoped, refused at the write boundary) before the
14343
14892
  // round-trip consequences it enables.
14344
14893
  durabilityDeclarationRule,
@@ -14350,8 +14899,13 @@ var init_repository = __esm({
14350
14899
  untypedSeamRule,
14351
14900
  proseClaimRule,
14352
14901
  // Invariant registry rides with the semantic-edge family: declared entity
14353
- // invariants must be asserted on every write path (declarations, not proofs).
14902
+ // invariants must be asserted on every write path (declarations, not
14903
+ // proofs). Three questions of one registry: are the entity's ids unique,
14904
+ // does each invariant reach every write path of its owner, and does every
14905
+ // asserted reference name something declared.
14906
+ uniqueInvariantIdsRule,
14354
14907
  invariantBackingRule,
14908
+ invariantReferencesRule,
14355
14909
  // Pub/sub completeness: emitted topics need subscribers and vice versa.
14356
14910
  eventTopologyRule,
14357
14911
  // Code↔spec: structural conformance consumes the injected CodeModel (built
@@ -14370,10 +14924,22 @@ var init_repository = __esm({
14370
14924
  hiddenStateRule,
14371
14925
  dependencyConformanceRule,
14372
14926
  // Integration wiring proof rides after the code↔spec family: it consumes
14373
- // the same code model and speaks about the same sourcePath modules.
14374
- integrationConformanceRule,
14927
+ // the same code model and speaks about the same sourcePath modules. Four
14928
+ // questions about one harness, in the order a reader meets them: is one
14929
+ // expected here, does the declared one exist, does it wire the real
14930
+ // modules, does it name every narrated path.
14931
+ integrationSimDeclarationRule,
14932
+ integrationSimFileRule,
14933
+ integrationSimWiringRule,
14934
+ integrationSimCoverageRule,
14375
14935
  couplingRule,
14376
- languageRule,
14936
+ // Target-language fit in two questions: what a CONTRACT may name, and what
14937
+ // a NARRATIVE may describe.
14938
+ signatureLanguageBuiltinsRule,
14939
+ narrativeLanguageConstructsRule,
14940
+ // The declaration before the consequences: which stereotype may bind a
14941
+ // technology at all, then where its name may appear.
14942
+ technologyBindingRule,
14377
14943
  technologyRule,
14378
14944
  namingRule,
14379
14945
  complexityRule,
@@ -15206,6 +15772,10 @@ function buildRuleContext(opts) {
15206
15772
  const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
15207
15773
  let dependencyEdgesMemo;
15208
15774
  const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
15775
+ let specIdsMemo;
15776
+ const specIds = () => specIdsMemo ??= buildSpecIds(ctx);
15777
+ let implementationMethodsMemo;
15778
+ const implementationMethods = () => implementationMethodsMemo ??= buildImplementationMethods(ctx);
15209
15779
  const ctx = {
15210
15780
  system,
15211
15781
  subsystems,
@@ -15240,6 +15810,8 @@ function buildRuleContext(opts) {
15240
15810
  importGraph,
15241
15811
  ownershipIndex,
15242
15812
  dependencyEdges,
15813
+ specIds,
15814
+ implementationMethods,
15243
15815
  complexityConfigFor,
15244
15816
  documentationConfigFor,
15245
15817
  namingConfigFor,
@@ -22194,8 +22766,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
22194
22766
  const componentById = new Map(graph.components.map((c) => [c.id, c]));
22195
22767
  const resolveComponent = (id) => {
22196
22768
  if (componentById.has(id)) return componentById.get(id);
22197
- const matches2 = graph.components.filter((c) => c.id.endsWith(`::${id}`));
22198
- return matches2.length === 1 ? matches2[0] : void 0;
22769
+ const matches = graph.components.filter((c) => c.id.endsWith(`::${id}`));
22770
+ return matches.length === 1 ? matches[0] : void 0;
22199
22771
  };
22200
22772
  const entry = resolveComponent(componentId);
22201
22773
  if (!entry) {
@@ -23527,9 +24099,9 @@ var init_specs2 = __esm({
23527
24099
  }
23528
24100
  }
23529
24101
  const suffix = `::${id}`;
23530
- const matches2 = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
23531
- if (matches2.length === 1) {
23532
- return index.paths.subsystem[matches2[0]];
24102
+ const matches = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
24103
+ if (matches.length === 1) {
24104
+ return index.paths.subsystem[matches[0]];
23533
24105
  }
23534
24106
  if (pathExists(this.paths.specsSubsystemsDir()) && listFiles(this.paths.specsSubsystemsDir(), ".yaml").length > 0) {
23535
24107
  return path23.join(this.paths.specsSubsystemsDir(), `${id}.yaml`);
@@ -28816,8 +29388,8 @@ function materializeAgentLayer(agents, projectConfig, options) {
28816
29388
  } else if (options.domains) {
28817
29389
  filterDomainIds = options.domains.split(",").map((s) => s.trim()).filter(Boolean);
28818
29390
  } else if (options.domain) {
28819
- const matches2 = agents.map((a) => a.domainRoot).filter((d) => !!d && (d === options.domain || d.startsWith(`${options.domain}::`)));
28820
- filterDomainIds = Array.from(/* @__PURE__ */ new Set([options.domain, ...matches2]));
29391
+ const matches = agents.map((a) => a.domainRoot).filter((d) => !!d && (d === options.domain || d.startsWith(`${options.domain}::`)));
29392
+ filterDomainIds = Array.from(/* @__PURE__ */ new Set([options.domain, ...matches]));
28821
29393
  }
28822
29394
  const agentPool = filterDomainIds ? agents.filter((a) => filterDomainIds.includes(a.domainRoot ?? "root")) : agents;
28823
29395
  if (options.dryRun) {