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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1113,7 +1113,7 @@ var init_defaults = __esm({
1113
1113
  copilot: ".github/prompts",
1114
1114
  codex: ".codex/agents"
1115
1115
  };
1116
- WAIRON_VERSION = "5.1.1-dev.25";
1116
+ WAIRON_VERSION = "5.1.1-dev.27";
1117
1117
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1118
1118
  ARCHITECT_AGENT_ID = "agent-architect";
1119
1119
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -2253,8 +2253,8 @@ function extractTypeIdentifiers(typeStr) {
2253
2253
  cleaned = cleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
2254
2254
  cleaned = cleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
2255
2255
  cleaned = cleaned.replace(/(["'`])(?:\\.|[^\\])*?\1/g, " ");
2256
- const matches2 = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
2257
- return matches2.filter((t) => {
2256
+ const matches = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
2257
+ return matches.filter((t) => {
2258
2258
  if (!/[a-zA-Z0-9]/.test(t)) return false;
2259
2259
  if (/^\d+$/.test(t)) return false;
2260
2260
  return true;
@@ -8694,6 +8694,240 @@ var init_types = __esm({
8694
8694
  }
8695
8695
  });
8696
8696
 
8697
+ // src/core/rules/read-model.ts
8698
+ function appendUnique(map, key, value, same) {
8699
+ const list = map.get(key);
8700
+ if (!list) {
8701
+ map.set(key, [value]);
8702
+ return;
8703
+ }
8704
+ if (same ? list.some((v) => same(v, value)) : list.includes(value)) return;
8705
+ list.push(value);
8706
+ }
8707
+ function buildCodeIndex(model) {
8708
+ const facts = /* @__PURE__ */ new Map();
8709
+ const paths = /* @__PURE__ */ new Set();
8710
+ const exactPaths = /* @__PURE__ */ new Set();
8711
+ for (const f of model.files) {
8712
+ const key = pathKey(f.path);
8713
+ facts.set(key, f);
8714
+ paths.add(key);
8715
+ if (f.status === "analyzed" && f.analysisGrade === "exact") exactPaths.add(key);
8716
+ }
8717
+ const declarations = /* @__PURE__ */ new Map();
8718
+ const anchors = /* @__PURE__ */ new Map();
8719
+ const findingAnchors = /* @__PURE__ */ new Map();
8720
+ const namesAt = (cache, path27, make) => {
8721
+ const cached = cache.get(path27);
8722
+ if (cached) return cached;
8723
+ const f = facts.get(path27);
8724
+ if (!f) return NO_NAMES;
8725
+ const built = make(f);
8726
+ cache.set(path27, built);
8727
+ return built;
8728
+ };
8729
+ return {
8730
+ paths,
8731
+ exactPaths,
8732
+ factsAt: (path27) => facts.get(pathKey(path27)),
8733
+ declarationsAt: (path27) => namesAt(declarations, pathKey(path27), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames])),
8734
+ anchorsAt: (path27) => namesAt(anchors, pathKey(path27), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
8735
+ findingAnchorsAt: (path27) => namesAt(findingAnchors, pathKey(path27), (f) => new Set(f.anchoredNames))
8736
+ };
8737
+ }
8738
+ function buildRealizationIndex(ctx) {
8739
+ const implsByComponent = /* @__PURE__ */ new Map();
8740
+ const implsByPath = /* @__PURE__ */ new Map();
8741
+ const filesByComponent = /* @__PURE__ */ new Map();
8742
+ const filesBySubsystem = /* @__PURE__ */ new Map();
8743
+ const componentsByPath = /* @__PURE__ */ new Map();
8744
+ const paths = [];
8745
+ const seenPaths = /* @__PURE__ */ new Set();
8746
+ for (const impl of ctx.implementations) {
8747
+ const contract = ctx.interfaceMap.get(impl.contract);
8748
+ if (!contract) continue;
8749
+ const component = ctx.componentMap.get(contract.component);
8750
+ if (!component) continue;
8751
+ if (ctx.isInChainedSubproject(component.subsystem)) continue;
8752
+ appendUnique(implsByComponent, component.id, impl);
8753
+ for (const file of implementationSourceFiles(impl)) {
8754
+ const p = pathKey(file);
8755
+ if (!seenPaths.has(p)) {
8756
+ seenPaths.add(p);
8757
+ paths.push(p);
8758
+ }
8759
+ appendUnique(implsByPath, p, impl);
8760
+ appendUnique(filesByComponent, component.id, p);
8761
+ appendUnique(filesBySubsystem, component.subsystem, p);
8762
+ appendUnique(componentsByPath, p, component, (a, b) => a.id === b.id);
8763
+ }
8764
+ }
8765
+ const NONE_IMPL = [];
8766
+ const NONE_COMP = [];
8767
+ const NONE_PATH = [];
8768
+ return {
8769
+ paths,
8770
+ implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
8771
+ implementationsAt: (path27) => implsByPath.get(pathKey(path27)) ?? NONE_IMPL,
8772
+ filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
8773
+ filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
8774
+ componentsAt: (path27) => componentsByPath.get(pathKey(path27)) ?? NONE_COMP
8775
+ };
8776
+ }
8777
+ function buildImportGraph(index, universe) {
8778
+ const imports = /* @__PURE__ */ new Map();
8779
+ const traces = /* @__PURE__ */ new Map();
8780
+ const resolveAll = (from, specifiers, into) => {
8781
+ for (const specifier of specifiers) {
8782
+ const to = resolveImport(from, specifier, universe);
8783
+ if (to && to !== from) into.add(to);
8784
+ }
8785
+ };
8786
+ const importsOf = (path27) => {
8787
+ const cached = imports.get(path27);
8788
+ if (cached) return cached;
8789
+ const facts = index.factsAt(path27);
8790
+ const out = /* @__PURE__ */ new Set();
8791
+ if (facts?.status === "analyzed") resolveAll(path27, facts.imports, out);
8792
+ imports.set(path27, out);
8793
+ return out;
8794
+ };
8795
+ const tracesOf = (path27) => {
8796
+ const cached = traces.get(path27);
8797
+ if (cached) return cached;
8798
+ const facts = index.factsAt(path27);
8799
+ const out = new Set(importsOf(path27));
8800
+ if (facts?.status === "analyzed") resolveAll(path27, facts.reexports, out);
8801
+ traces.set(path27, out);
8802
+ return out;
8803
+ };
8804
+ return {
8805
+ paths: [...universe],
8806
+ importsOf,
8807
+ tracesOf,
8808
+ connects: (from, to, allowReverse) => {
8809
+ for (const f of from) {
8810
+ const forward = tracesOf(f);
8811
+ for (const t of to) if (forward.has(t)) return true;
8812
+ }
8813
+ if (allowReverse) {
8814
+ for (const t of to) {
8815
+ const reverse = tracesOf(t);
8816
+ for (const f of from) if (reverse.has(f)) return true;
8817
+ }
8818
+ }
8819
+ return false;
8820
+ },
8821
+ reachFrom: (start) => {
8822
+ const seen = /* @__PURE__ */ new Set([start]);
8823
+ const stack = [start];
8824
+ while (stack.length) {
8825
+ const from = stack.pop();
8826
+ for (const to of tracesOf(from)) {
8827
+ if (seen.has(to)) continue;
8828
+ seen.add(to);
8829
+ stack.push(to);
8830
+ }
8831
+ }
8832
+ return seen;
8833
+ }
8834
+ };
8835
+ }
8836
+ function buildOwnershipIndex(ctx) {
8837
+ const ownedBy = /* @__PURE__ */ new Map();
8838
+ for (const comp of ctx.components) {
8839
+ if (isRetired(comp) || !isPattern(comp)) continue;
8840
+ for (const memberId of comp.owns) {
8841
+ const member = ctx.componentMap.get(memberId);
8842
+ if (!member || isPattern(member)) continue;
8843
+ if (!ownedBy.has(memberId)) ownedBy.set(memberId, comp.id);
8844
+ }
8845
+ }
8846
+ return {
8847
+ ownedMembers: new Set(ownedBy.keys()),
8848
+ ownerOf: (memberId) => ownedBy.get(memberId)
8849
+ };
8850
+ }
8851
+ function buildDependencyEdges(ctx) {
8852
+ const all = [];
8853
+ const matrix = [];
8854
+ for (const from of ctx.components) {
8855
+ const fromDraft = ctx.isComponentDraft(from.id);
8856
+ const profile = ctx.ext.profiles[ctx.getComponentProfile(from.id)];
8857
+ for (const ref of from.dependsOn) {
8858
+ const to = ctx.componentMap.get(ref);
8859
+ if (!to) {
8860
+ all.push(offTreeEdge(ctx, from, ref, fromDraft));
8861
+ continue;
8862
+ }
8863
+ const edge = {
8864
+ from,
8865
+ ref,
8866
+ to,
8867
+ reach: to.subsystem === from.subsystem ? "internal" : "cross-subsystem",
8868
+ retired: isRetired(from) || isRetired(to),
8869
+ draftContext: fromDraft || ctx.isComponentDraft(to.id),
8870
+ licensed: profile?.allowedEdges?.some(
8871
+ (e) => e.from.includes(from.componentType) && e.to.includes(to.componentType)
8872
+ ) ?? false
8873
+ };
8874
+ all.push(edge);
8875
+ if (edge.reach === "internal" && !edge.retired && !edge.licensed) matrix.push(edge);
8876
+ }
8877
+ }
8878
+ return { all, matrix };
8879
+ }
8880
+ function buildSpecIds(ctx) {
8881
+ const out = [];
8882
+ for (const s of ctx.subsystems) out.push({ id: s.id, kind: "Subsystem" });
8883
+ for (const c of ctx.components) out.push({ id: c.id, kind: "Component" });
8884
+ for (const i of ctx.interfaces) out.push({ id: i.id, kind: "Interface" });
8885
+ for (const im of ctx.implementations) out.push({ id: im.id, kind: "Implementation" });
8886
+ for (const t of ctx.types) out.push({ id: t.id, kind: "Type" });
8887
+ return out;
8888
+ }
8889
+ function buildImplementationMethods(ctx) {
8890
+ const out = [];
8891
+ for (const impl of ctx.implementations) {
8892
+ const contract = ctx.interfaceMap.get(impl.contract);
8893
+ if (!contract) continue;
8894
+ const component = ctx.componentMap.get(contract.component);
8895
+ if (!component) continue;
8896
+ if (ctx.isInChainedSubproject(component.subsystem)) continue;
8897
+ const draftContext = ctx.isImplementationDraft(impl);
8898
+ for (const method of impl.methods) {
8899
+ const sourceFile = methodSourceFile(method, impl.sourcePath);
8900
+ out.push({
8901
+ implementation: impl,
8902
+ method,
8903
+ component,
8904
+ ...sourceFile !== void 0 ? { sourceFile } : {},
8905
+ draftContext
8906
+ });
8907
+ }
8908
+ }
8909
+ return out;
8910
+ }
8911
+ function offTreeEdge(ctx, from, ref, fromDraft) {
8912
+ const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
8913
+ const external = ctx.isExternalNamespaceRef(ref);
8914
+ if (external || ctx.isCollapsedCrossTreeRef(ref, from.subsystem)) {
8915
+ const surface = ctx.resolveSurfaceRef(ref, from.subsystem);
8916
+ if (surface.kind === "ambiguous") return { ...base, reach: "ambiguous", surface };
8917
+ if (surface.kind === "resolved") return { ...base, reach: "surface", surface };
8918
+ if (external) return { ...base, reach: "unpinned" };
8919
+ }
8920
+ return { ...base, reach: "missing" };
8921
+ }
8922
+ var NO_NAMES;
8923
+ var init_read_model = __esm({
8924
+ "src/core/rules/read-model.ts"() {
8925
+ "use strict";
8926
+ init_models();
8927
+ NO_NAMES = /* @__PURE__ */ new Set();
8928
+ }
8929
+ });
8930
+
8697
8931
  // src/core/rules/integrity/hierarchy-integrity.ts
8698
8932
  var hierarchyRule;
8699
8933
  var init_hierarchy_integrity = __esm({
@@ -8774,48 +9008,58 @@ var init_hierarchy_integrity = __esm({
8774
9008
  }
8775
9009
  });
8776
9010
 
8777
- // src/core/rules/integrity/namespace-hygiene.ts
8778
- var namespaceHygieneRule;
8779
- var init_namespace_hygiene = __esm({
8780
- "src/core/rules/integrity/namespace-hygiene.ts"() {
9011
+ // src/core/rules/integrity/reserved-id-segments.ts
9012
+ var reservedIdSegmentsRule;
9013
+ var init_reserved_id_segments = __esm({
9014
+ "src/core/rules/integrity/reserved-id-segments.ts"() {
9015
+ "use strict";
9016
+ reservedIdSegmentsRule = {
9017
+ name: "reserved-id-segments",
9018
+ description: 'No segment of a spec id may be the reserved namespace keyword "super" \u2014 the :: grammar consumes it as a namespace hop, so a stored reference to such an id resolves to a different spec.',
9019
+ codes: [
9020
+ { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
9021
+ ],
9022
+ check(ctx) {
9023
+ for (const spec of ctx.specIds()) {
9024
+ if (!ctx.isSpecInScope(spec.id)) continue;
9025
+ if (!spec.id.split("::").includes("super")) continue;
9026
+ ctx.addIssue(
9027
+ "error",
9028
+ "RESERVED_ID_SEGMENT",
9029
+ `${spec.kind} id "${spec.id}" uses the reserved namespace keyword "super" \u2014 stored references to it would be consumed as a namespace hop and resolve to a different spec.`,
9030
+ spec.id
9031
+ );
9032
+ }
9033
+ }
9034
+ };
9035
+ }
9036
+ });
9037
+
9038
+ // src/core/rules/integrity/namespace-shadowing.ts
9039
+ var namespaceShadowingRule;
9040
+ var init_namespace_shadowing = __esm({
9041
+ "src/core/rules/integrity/namespace-shadowing.ts"() {
8781
9042
  "use strict";
8782
- namespaceHygieneRule = {
8783
- name: "namespace-hygiene",
8784
- 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.',
9043
+ namespaceShadowingRule = {
9044
+ name: "namespace-shadowing",
9045
+ description: "A subproject-local name must not shadow a root-level subsystem id \u2014 inside its subproject a bare reference to the shadowed name anchors to the ROOT subsystem, so the local spec cannot be addressed reliably.",
8785
9046
  codes: [
8786
- { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' },
8787
9047
  { code: "NAMESPACE_SHADOWING", defaultSeverity: "error", summary: "Subproject-local id shadows a root subsystem id (bare references anchor to the root)" }
8788
9048
  ],
8789
9049
  check(ctx) {
8790
9050
  const rootSubs = new Set(ctx.subsystems.filter((s) => !s.id.includes("::")).map((s) => s.id));
8791
- const checkId = (id, kindLabel) => {
8792
- if (!ctx.isSpecInScope(id)) return;
8793
- const segments = id.split("::");
8794
- if (segments.includes("super")) {
8795
- ctx.addIssue(
8796
- "error",
8797
- "RESERVED_ID_SEGMENT",
8798
- `${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.`,
8799
- id
8800
- );
8801
- }
8802
- if (segments.length > 1) {
8803
- const local = segments[segments.length - 1];
8804
- if (rootSubs.has(local)) {
8805
- ctx.addIssue(
8806
- "error",
8807
- "NAMESPACE_SHADOWING",
8808
- `${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.`,
8809
- id
8810
- );
8811
- }
8812
- }
8813
- };
8814
- for (const s of ctx.subsystems) checkId(s.id, "Subsystem");
8815
- for (const c of ctx.components) checkId(c.id, "Component");
8816
- for (const i of ctx.interfaces) checkId(i.id, "Interface");
8817
- for (const im of ctx.implementations) checkId(im.id, "Implementation");
8818
- for (const t of ctx.types) checkId(t.id, "Type");
9051
+ for (const spec of ctx.specIds()) {
9052
+ if (!ctx.isSpecInScope(spec.id)) continue;
9053
+ const segments = spec.id.split("::");
9054
+ const local = segments[segments.length - 1];
9055
+ if (segments.length <= 1 || !rootSubs.has(local)) continue;
9056
+ ctx.addIssue(
9057
+ "error",
9058
+ "NAMESPACE_SHADOWING",
9059
+ `${spec.kind} "${spec.id}" shadows root subsystem "${local}" \u2014 inside its subproject a bare reference to "${local}" anchors to the ROOT subsystem, so this spec cannot be addressed reliably. Rename one of them.`,
9060
+ spec.id
9061
+ );
9062
+ }
8819
9063
  }
8820
9064
  };
8821
9065
  }
@@ -8960,10 +9204,48 @@ var init_signature_type_references = __esm({
8960
9204
  }
8961
9205
  });
8962
9206
 
8963
- // src/core/rules/integrity/public-surface.ts
8964
- var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceRule;
8965
- var init_public_surface = __esm({
8966
- "src/core/rules/integrity/public-surface.ts"() {
9207
+ // src/core/rules/integrity/public-surface-binding.ts
9208
+ var publicSurfaceBindingRule;
9209
+ var init_public_surface_binding = __esm({
9210
+ "src/core/rules/integrity/public-surface-binding.ts"() {
9211
+ "use strict";
9212
+ init_models();
9213
+ publicSurfaceBindingRule = {
9214
+ name: "public-surface-binding",
9215
+ description: "Every declared publicInterface names a backing component, that component exists, and the subsystem owns it \u2014 a subsystem may only publish its own components.",
9216
+ codes: [
9217
+ { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9218
+ { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9219
+ { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" }
9220
+ ],
9221
+ check(ctx) {
9222
+ for (const sub of ctx.subsystems) {
9223
+ const isDraftCtx = isDraftSubsystem(sub);
9224
+ for (const pi of sub.publicInterfaces) {
9225
+ if (!pi.component) {
9226
+ ctx.addIssue("error", "PUBLIC_INTERFACE_UNBOUND", `Subsystem "${sub.id}" declares a ${pi.type} public interface with no backing component. Bind it to the component that realizes it (publicInterfaces[].component).`, sub.id, isDraftCtx);
9227
+ continue;
9228
+ }
9229
+ const backing = ctx.componentMap.get(pi.component);
9230
+ if (!backing) {
9231
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9232
+ continue;
9233
+ }
9234
+ const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9235
+ if (!isSubsystemOwner) {
9236
+ ctx.addIssue("error", "PUBLIC_INTERFACE_FOREIGN_COMPONENT", `Subsystem "${sub.id}" publishes component "${pi.component}", but it belongs to subsystem "${backing.subsystem}". A subsystem may only publish its own components.`, sub.id, isDraftCtx);
9237
+ }
9238
+ }
9239
+ }
9240
+ }
9241
+ };
9242
+ }
9243
+ });
9244
+
9245
+ // src/core/rules/integrity/public-surface-declared-type.ts
9246
+ var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceDeclaredTypeRule;
9247
+ var init_public_surface_declared_type = __esm({
9248
+ "src/core/rules/integrity/public-surface-declared-type.ts"() {
8967
9249
  "use strict";
8968
9250
  init_models();
8969
9251
  pubTypeMatches = (piType, ct, portalType) => {
@@ -8997,45 +9279,22 @@ var init_public_surface = __esm({
8997
9279
  return "a compatible component";
8998
9280
  }
8999
9281
  };
9000
- publicSurfaceRule = {
9001
- name: "public-surface",
9002
- description: "Every declared publicInterface is bound to an existing component of this subsystem whose stereotype can realize the declared interface type; Custom entries whose prose implies eventing must be backed by an event-capable component.",
9282
+ publicSurfaceDeclaredTypeRule = {
9283
+ name: "public-surface-declared-type",
9284
+ description: "A publicInterface entry's declared type must be realizable by the stereotype of its backing component; Custom entries whose prose implies eventing must be backed by an event-capable component.",
9003
9285
  codes: [
9004
- { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9005
- { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9006
- { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" },
9007
9286
  { code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
9008
- { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" },
9009
9287
  { code: "PUBLIC_INTERFACE_EVENT_MISTYPED", defaultSeverity: "warning", summary: "Custom interface describing eventing backed by a non-event component" }
9010
9288
  ],
9011
9289
  check(ctx) {
9012
9290
  for (const sub of ctx.subsystems) {
9013
9291
  const isDraftCtx = isDraftSubsystem(sub);
9014
9292
  for (const pi of sub.publicInterfaces) {
9015
- if (!pi.component) {
9016
- 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);
9017
- continue;
9018
- }
9019
- const backing = ctx.componentMap.get(pi.component);
9020
- if (!backing) {
9021
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9022
- continue;
9023
- }
9024
- const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9025
- if (!isSubsystemOwner) {
9026
- 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);
9027
- }
9293
+ const backing = pi.component ? ctx.componentMap.get(pi.component) : void 0;
9294
+ if (!backing) continue;
9028
9295
  if (!pubTypeMatches(pi.type, backing.componentType, backing.portalType)) {
9029
9296
  ctx.addIssue("error", "PUBLIC_INTERFACE_TYPE_MISMATCH", `Subsystem "${sub.id}" declares a ${pi.type} public interface backed by "${pi.component}" (${backing.componentType}${backing.portalType ? `/${backing.portalType}` : ""}), which cannot realize ${pi.type}. Expected ${expectedFor(pi.type)}.`, sub.id, isDraftCtx);
9030
9297
  }
9031
- if (pi.interface) {
9032
- const intf = ctx.interfaceMap.get(pi.interface);
9033
- if (!intf) {
9034
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9035
- } else if (intf.component !== pi.component) {
9036
- 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);
9037
- }
9038
- }
9039
9298
  if (pi.type === "Custom" && EVENT_VOCAB.test(pi.details)) {
9040
9299
  const eventCapable = backing.componentType === "Observer" || backing.componentType === "Portal" && backing.portalType === "MessageBus";
9041
9300
  if (!eventCapable) {
@@ -9049,6 +9308,37 @@ var init_public_surface = __esm({
9049
9308
  }
9050
9309
  });
9051
9310
 
9311
+ // src/core/rules/integrity/public-surface-bound-contract.ts
9312
+ var publicSurfaceBoundContractRule;
9313
+ var init_public_surface_bound_contract = __esm({
9314
+ "src/core/rules/integrity/public-surface-bound-contract.ts"() {
9315
+ "use strict";
9316
+ init_models();
9317
+ publicSurfaceBoundContractRule = {
9318
+ name: "public-surface-bound-contract",
9319
+ description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
9320
+ codes: [
9321
+ { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
9322
+ ],
9323
+ check(ctx) {
9324
+ for (const sub of ctx.subsystems) {
9325
+ const isDraftCtx = isDraftSubsystem(sub);
9326
+ for (const pi of sub.publicInterfaces) {
9327
+ if (!pi.interface) continue;
9328
+ if (!pi.component || !ctx.componentMap.has(pi.component)) continue;
9329
+ const intf = ctx.interfaceMap.get(pi.interface);
9330
+ if (!intf) {
9331
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9332
+ } else if (intf.component !== pi.component) {
9333
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" binds interface "${pi.interface}" to component "${pi.component}", but that interface belongs to component "${intf.component}".`, sub.id, isDraftCtx);
9334
+ }
9335
+ }
9336
+ }
9337
+ }
9338
+ };
9339
+ }
9340
+ });
9341
+
9052
9342
  // src/core/rules/integrity/lint-allows.ts
9053
9343
  var lintAllowsRule;
9054
9344
  var init_lint_allows = __esm({
@@ -9654,7 +9944,7 @@ var init_narrative_jump_edges = __esm({
9654
9944
  }
9655
9945
  });
9656
9946
 
9657
- // src/core/rules/narrative/narrative-antipatterns.ts
9947
+ // src/core/rules/narrative/completed-step-graph.ts
9658
9948
  function completedStepGraph(steps) {
9659
9949
  const numbers = steps.flatMap((s) => [
9660
9950
  s.stepNumber,
@@ -9684,6 +9974,14 @@ function isUnavoidable(steps, target) {
9684
9974
  if (s.type === "return" || s.type === "throw") return true;
9685
9975
  const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
9686
9976
  if (arms.length) return arms.every((e) => completes.has(e));
9977
+ const guaranteed = s.endStep !== void 0 && (s.type === "try" || s.type === "loop" && s.loopKind === "doWhile");
9978
+ if (guaranteed) {
9979
+ const bodyEntry = graph.nextOf(n);
9980
+ if (bodyEntry !== void 0 && bodyEntry <= s.endStep) {
9981
+ if (s.type === "loop") return completes.has(bodyEntry);
9982
+ if (bodyEntry === target) return false;
9983
+ }
9984
+ }
9687
9985
  return graph.successorsOf(n).some((t) => completes.has(t));
9688
9986
  };
9689
9987
  const order = [...graph.stepNumbers].reverse();
@@ -9747,25 +10045,27 @@ function stronglyConnected(nums, successorsOf) {
9747
10045
  }
9748
10046
  return sccs;
9749
10047
  }
9750
- var byCodeUnit, narrativeAntipatternsRule;
9751
- var init_narrative_antipatterns = __esm({
9752
- "src/core/rules/narrative/narrative-antipatterns.ts"() {
10048
+ var init_completed_step_graph = __esm({
10049
+ "src/core/rules/narrative/completed-step-graph.ts"() {
9753
10050
  "use strict";
9754
10051
  init_models();
9755
- byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
9756
- narrativeAntipatternsRule = {
9757
- name: "narrative-antipatterns",
9758
- description: "Provable narrative bugs, caught at spec level before implementation: a step-graph cycle with no exit edge and no return/throw member never terminates by construction (INESCAPABLE_CYCLE); a branch or switch whose arms all target the same step decides nothing (MEANINGLESS_BRANCH); a method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path is unbounded recursion (UNCONDITIONAL_CALL_CYCLE \u2014 dispatch steps resolved through their portal tables; a cycle with even one guarded edge is NOT flagged, since prose conditions are never judged). Structure-only claims \u2014 warnings, lint.allow-suppressible.",
10052
+ }
10053
+ });
10054
+
10055
+ // src/core/rules/narrative/meaningless-branches.ts
10056
+ var meaninglessBranchesRule;
10057
+ var init_meaningless_branches = __esm({
10058
+ "src/core/rules/narrative/meaningless-branches.ts"() {
10059
+ "use strict";
10060
+ init_completed_step_graph();
10061
+ meaninglessBranchesRule = {
10062
+ name: "meaningless-branches",
10063
+ description: "A branch or switch whose arms all target the same step decides nothing: the condition is evaluated and flow continues at one place whatever it answers. A decision's targets are its explicit ones plus, where it leaves its fall-through field unset, the steps the step graph continues it at \u2014 so the classic authoring slip of pointing both arms at the next step is caught. Structure-only \u2014 a warning, lint.allow-suppressible.",
9759
10064
  codes: [
9760
- { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" },
9761
- { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" },
9762
- { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10065
+ { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
9763
10066
  ],
9764
10067
  check(ctx) {
9765
- const callEdges = [];
9766
10068
  for (const impl of ctx.implementations) {
9767
- const contract = ctx.interfaceMap.get(impl.contract);
9768
- const component = contract ? ctx.componentMap.get(contract.component) : void 0;
9769
10069
  const isDraftCtx = ctx.isImplementationDraft(impl);
9770
10070
  for (const implMethod of impl.methods) {
9771
10071
  const steps = implMethod.narrative;
@@ -9804,6 +10104,33 @@ var init_narrative_antipatterns = __esm({
9804
10104
  }
9805
10105
  }
9806
10106
  }
10107
+ }
10108
+ }
10109
+ }
10110
+ };
10111
+ }
10112
+ });
10113
+
10114
+ // src/core/rules/narrative/inescapable-cycles.ts
10115
+ var inescapableCyclesRule;
10116
+ var init_inescapable_cycles = __esm({
10117
+ "src/core/rules/narrative/inescapable-cycles.ts"() {
10118
+ "use strict";
10119
+ init_completed_step_graph();
10120
+ inescapableCyclesRule = {
10121
+ name: "inescapable-cycles",
10122
+ description: "A cycle in a narrative's step graph with no exit edge and no return or throw member never terminates, by construction: every path that enters it stays inside it forever. Read over the step graph closed by a completion step, so falling off the end of the narrative counts as leaving. Structure-only \u2014 prose conditions are never judged \u2014 and reported as a warning, lint.allow-suppressible.",
10123
+ codes: [
10124
+ { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
10125
+ ],
10126
+ check(ctx) {
10127
+ for (const impl of ctx.implementations) {
10128
+ const isDraftCtx = ctx.isImplementationDraft(impl);
10129
+ for (const implMethod of impl.methods) {
10130
+ const steps = implMethod.narrative;
10131
+ if (!steps.length) continue;
10132
+ const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
10133
+ const graph = completedStepGraph(steps);
9807
10134
  for (const scc of stronglyConnected(graph.stepNumbers, graph.successorsOf)) {
9808
10135
  const inScc = new Set(scc);
9809
10136
  const isCycle = scc.length > 1 || graph.successorsOf(scc[0]).includes(scc[0]);
@@ -9824,7 +10151,35 @@ var init_narrative_antipatterns = __esm({
9824
10151
  );
9825
10152
  }
9826
10153
  }
9827
- if (!component) continue;
10154
+ }
10155
+ }
10156
+ }
10157
+ };
10158
+ }
10159
+ });
10160
+
10161
+ // src/core/rules/narrative/unconditional-call-cycles.ts
10162
+ var byCodeUnit, unconditionalCallCyclesRule;
10163
+ var init_unconditional_call_cycles = __esm({
10164
+ "src/core/rules/narrative/unconditional-call-cycles.ts"() {
10165
+ "use strict";
10166
+ init_completed_step_graph();
10167
+ byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
10168
+ unconditionalCallCyclesRule = {
10169
+ name: "unconditional-call-cycles",
10170
+ description: "A method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path of its narrative is unbounded recursion, by construction. Dispatch steps are resolved through their portal's table; register steps are never edges (a handoff defers the invocation to the runtime, so it cannot recurse). A cycle with even one guarded edge is NOT flagged, since prose conditions are never judged. Structure-only \u2014 a warning, lint.allow-suppressible, anchored on the member edge that sorts first so one allow covers the cycle.",
10171
+ codes: [
10172
+ { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10173
+ ],
10174
+ check(ctx) {
10175
+ const callEdges = [];
10176
+ for (const impl of ctx.implementations) {
10177
+ const contract = ctx.interfaceMap.get(impl.contract);
10178
+ const component = contract ? ctx.componentMap.get(contract.component) : void 0;
10179
+ if (!component) continue;
10180
+ for (const implMethod of impl.methods) {
10181
+ const steps = implMethod.narrative;
10182
+ if (!steps.length) continue;
9828
10183
  const fromKey = `${component.id}::${implMethod.name}`;
9829
10184
  for (const s of steps) {
9830
10185
  let toComponent;
@@ -9886,35 +10241,29 @@ var init_narrative_antipatterns = __esm({
9886
10241
  });
9887
10242
 
9888
10243
  // src/core/rules/narrative/narrative-detail.ts
9889
- var DEFAULT_MAX_UNNARRATED_COMPLEXITY, narrativeDetailRule;
10244
+ var narrativeDetailRule;
9890
10245
  var init_narrative_detail = __esm({
9891
10246
  "src/core/rules/narrative/narrative-detail.ts"() {
9892
10247
  "use strict";
9893
10248
  init_models();
9894
- DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
9895
10249
  narrativeDetailRule = {
9896
10250
  name: "narrative-detail",
9897
- description: "The narrative detail dial: each method resolves to full | calls-only | intent (method override \u2192 spec default \u2192 stereotype default). full requires a narrative; intent-level methods without a narrative must specify behavior as non-trivial prose (L4 intent or L3 description) \u2014 dialing detail down never means leaving behavior unspecified. Explicit declarations are held to their promise as errors; stereotype-defaulted gaps surface as warnings. Detail sufficiency rides along: a method whose realized function \u2014 in the method's own source file, else the implementation's \u2014 measures real branching (cyclomatic complexity above rules.complexity.maxUnnarratedComplexity, exact AST grade only) may not hide below detail: full without a narrative (UNNARRATED_COMPLEXITY), and an explicit dial below a full-floor logic stereotype without a narrative is a visible, lint.allow-justifiable choice (DETAIL_BELOW_STEREOTYPE). Both are honest lints over declarations \u2014 they never claim the narrative or prose is CORRECT.",
10251
+ description: "The narrative detail dial: each method resolves to full | calls-only | intent (method override \u2192 spec default \u2192 stereotype default). full requires a narrative; intent-level methods without a narrative must specify behavior as non-trivial prose (L4 intent or L3 description) \u2014 dialing detail down never means leaving behavior unspecified. Explicit declarations are held to their promise as errors; stereotype-defaulted gaps surface as warnings. An honest lint over declarations \u2014 it never claims the narrative or the prose is CORRECT.",
9898
10252
  codes: [
9899
10253
  { code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
9900
- { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" },
9901
- { 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)" },
9902
- { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10254
+ { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
9903
10255
  ],
9904
10256
  check(ctx) {
9905
- const factsByPath = /* @__PURE__ */ new Map();
9906
- for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
9907
10257
  for (const impl of ctx.implementations) {
9908
10258
  const contract = ctx.interfaceMap.get(impl.contract);
9909
10259
  if (!contract) continue;
9910
10260
  const component = ctx.componentMap.get(contract.component);
9911
- const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract.status === "draft" || contract.status === "design" || ctx.isComponentDraft(contract.component);
10261
+ const isDraftCtx = ctx.isImplementationDraft(impl);
9912
10262
  for (const implMethod of impl.methods) {
9913
10263
  const contractMethod = contract.methods.find((m) => m.name === implMethod.name);
9914
10264
  if (!contractMethod) continue;
10265
+ if (implMethod.narrative.length > 0) continue;
9915
10266
  const eff = effectiveDetail(implMethod, impl, component);
9916
- const hasNarrative = implMethod.narrative.length > 0;
9917
- if (hasNarrative) continue;
9918
10267
  if (eff.level === "full") {
9919
10268
  ctx.addIssue(
9920
10269
  eff.explicit ? "error" : "warning",
@@ -9925,34 +10274,6 @@ var init_narrative_detail = __esm({
9925
10274
  );
9926
10275
  continue;
9927
10276
  }
9928
- let complexityFired = false;
9929
- const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
9930
- const file = methodSourceFile(implMethod, impl.sourcePath);
9931
- if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
9932
- const facts = factsByPath.get(pathKey(file));
9933
- const symbol = implMethod.symbol ?? implMethod.name;
9934
- const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
9935
- const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
9936
- if (complexity !== void 0 && complexity > limit) {
9937
- complexityFired = true;
9938
- ctx.addIssue(
9939
- "warning",
9940
- "UNNARRATED_COMPLEXITY",
9941
- `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.`,
9942
- impl.id,
9943
- isDraftCtx
9944
- );
9945
- }
9946
- }
9947
- if (!complexityFired && eff.explicit && component && isLogic(component)) {
9948
- ctx.addIssue(
9949
- "warning",
9950
- "DETAIL_BELOW_STEREOTYPE",
9951
- `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.`,
9952
- impl.id,
9953
- isDraftCtx
9954
- );
9955
- }
9956
10277
  const prose = implMethod.intent ?? contractMethod.description;
9957
10278
  if (!passesIntentFloor(prose, implMethod.name)) {
9958
10279
  ctx.addIssue(
@@ -9970,6 +10291,71 @@ var init_narrative_detail = __esm({
9970
10291
  }
9971
10292
  });
9972
10293
 
10294
+ // src/core/rules/narrative/detail-sufficiency.ts
10295
+ var DEFAULT_MAX_UNNARRATED_COMPLEXITY, detailSufficiencyRule;
10296
+ var init_detail_sufficiency = __esm({
10297
+ "src/core/rules/narrative/detail-sufficiency.ts"() {
10298
+ "use strict";
10299
+ init_models();
10300
+ DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
10301
+ detailSufficiencyRule = {
10302
+ name: "detail-sufficiency",
10303
+ description: "A method dialed below detail: full with no narrative may not be hiding behavior it owes a reader. A method whose realized function \u2014 in the method's own source file, else the implementation's \u2014 measures real branching (cyclomatic complexity above rules.complexity.maxUnnarratedComplexity, exact AST grade only) is reported (UNNARRATED_COMPLEXITY), and an explicit dial below a full-floor logic stereotype without a narrative is a visible, lint.allow-justifiable choice (DETAIL_BELOW_STEREOTYPE) \u2014 reported only where the measured finding did not already fire, since evidence outranks expectation. Both are honest lints over declarations: they never claim a narrative would be correct, only that the dial hides something.",
10304
+ codes: [
10305
+ { code: "UNNARRATED_COMPLEXITY", defaultSeverity: "warning", summary: "Method below detail: full with no narrative whose realized function, in the method's source file, has real branching (cyclomatic complexity over the configured threshold, exact-grade analysis only)" },
10306
+ { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10307
+ ],
10308
+ check(ctx) {
10309
+ const code = ctx.codeIndex();
10310
+ const dialedDown = [];
10311
+ for (const impl of ctx.implementations) {
10312
+ const contract = ctx.interfaceMap.get(impl.contract);
10313
+ if (!contract) continue;
10314
+ const component = ctx.componentMap.get(contract.component);
10315
+ const draftContext = ctx.isImplementationDraft(impl);
10316
+ for (const method of impl.methods) {
10317
+ const detail = effectiveDetail(method, impl, component);
10318
+ if (contract.methods.some((m) => m.name === method.name) && method.narrative.length === 0 && detail.level !== "full") {
10319
+ dialedDown.push({ implementation: impl, method, component, detail, draftContext });
10320
+ }
10321
+ }
10322
+ }
10323
+ for (const entry of dialedDown) {
10324
+ const { implementation: impl, method, component, detail } = entry;
10325
+ const tier = method.conformance ?? impl.conformance ?? defaultConformanceTier(component);
10326
+ const file = methodSourceFile(method, impl.sourcePath);
10327
+ const symbol = method.symbol ?? method.name;
10328
+ const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
10329
+ let complexity;
10330
+ if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
10331
+ const facts = code.factsAt(file);
10332
+ complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
10333
+ }
10334
+ if (complexity !== void 0 && complexity > limit) {
10335
+ ctx.addIssue(
10336
+ "warning",
10337
+ "UNNARRATED_COMPLEXITY",
10338
+ `Method "${method.name}" in implementation "${impl.id}" sits at detail: ${detail.level} with no narrative, but its realized function "${symbol}" in "${file}" measures cyclomatic complexity ${complexity} (limit ${limit}) \u2014 real branching is hiding behind ${detail.level}. Write the narrative, or keep the dial with a lint.allow stating why the branching needs no choreography.`,
10339
+ impl.id,
10340
+ entry.draftContext
10341
+ );
10342
+ continue;
10343
+ }
10344
+ if (detail.explicit && component && isLogic(component)) {
10345
+ ctx.addIssue(
10346
+ "warning",
10347
+ "DETAIL_BELOW_STEREOTYPE",
10348
+ `Method "${method.name}" in implementation "${impl.id}" is explicitly dialed to detail: ${detail.level}, below the full narrative floor of its ${component.componentType} stereotype, and has no narrative. Logic behavior belongs in a narrative \u2014 write one, or keep the dial with a lint.allow stating why.`,
10349
+ impl.id,
10350
+ entry.draftContext
10351
+ );
10352
+ }
10353
+ }
10354
+ }
10355
+ };
10356
+ }
10357
+ });
10358
+
9973
10359
  // src/core/rules/intrinsic/portal-fields.ts
9974
10360
  var portalFieldsRule;
9975
10361
  var init_portal_fields = __esm({
@@ -10075,7 +10461,7 @@ var init_logic_declaration = __esm({
10075
10461
  logicDeclarationRule = {
10076
10462
  name: "logic-declaration",
10077
10463
  scope: "spec",
10078
- description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by stereotype-dependencies. Intrinsic to one component: no tree required.",
10464
+ description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by logic-dependency-class. Intrinsic to one component: no tree required.",
10079
10465
  codes: [
10080
10466
  { code: "DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR", defaultSeverity: "error", summary: "dependencyClass declared on a component that is not an Orchestrator" }
10081
10467
  ],
@@ -10154,45 +10540,61 @@ var init_portal_endpoints = __esm({
10154
10540
  };
10155
10541
  portalsRule = {
10156
10542
  name: "portal-endpoints",
10157
- description: "A Portal binds every interface method to a concrete endpoint of the matching transport, and only a Portal may carry endpoints at all.",
10543
+ description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
10158
10544
  codes: [
10159
10545
  { code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
10160
- { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" },
10161
- { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10546
+ { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
10162
10547
  ],
10163
10548
  check(ctx) {
10164
10549
  for (const comp of ctx.components) {
10550
+ if (comp.componentType !== "Portal") continue;
10551
+ const expected = comp.portalType ? PORTAL_TRANSPORT[comp.portalType] : void 0;
10552
+ if (!expected) continue;
10165
10553
  const isDraftCtx = ctx.isComponentDraft(comp.id);
10166
- const compInterfaces = ctx.interfaces.filter((i) => i.component === comp.id);
10167
- if (comp.componentType === "Portal") {
10168
- if (!comp.portalType) continue;
10169
- const expected = PORTAL_TRANSPORT[comp.portalType];
10170
- if (!expected) continue;
10171
- for (const intf of compInterfaces) {
10172
- const isIntfDraft = intf.status === "draft" || intf.status === "design";
10173
- for (const m of intf.methods) {
10174
- if (!m.endpoint) {
10175
- ctx.addIssue(
10176
- "error",
10177
- "MISSING_ENDPOINT",
10178
- `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10179
- intf.id,
10180
- isDraftCtx || isIntfDraft
10181
- );
10182
- } else if (m.endpoint.transport !== expected) {
10183
- ctx.addIssue(
10184
- "error",
10185
- "ENDPOINT_TRANSPORT_MISMATCH",
10186
- `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}").`,
10187
- intf.id,
10188
- isDraftCtx || isIntfDraft
10189
- );
10190
- }
10554
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10555
+ const isIntfDraft = intf.status === "draft" || intf.status === "design";
10556
+ for (const m of intf.methods) {
10557
+ if (!m.endpoint) {
10558
+ ctx.addIssue(
10559
+ "error",
10560
+ "MISSING_ENDPOINT",
10561
+ `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10562
+ intf.id,
10563
+ isDraftCtx || isIntfDraft
10564
+ );
10565
+ } else if (m.endpoint.transport !== expected) {
10566
+ ctx.addIssue(
10567
+ "error",
10568
+ "ENDPOINT_TRANSPORT_MISMATCH",
10569
+ `Method "${m.name}" on interface "${intf.id}" declares a "${m.endpoint.transport}" endpoint, but its Portal "${comp.id}" is portalType "${comp.portalType}" (expects transport "${expected}").`,
10570
+ intf.id,
10571
+ isDraftCtx || isIntfDraft
10572
+ );
10191
10573
  }
10192
10574
  }
10193
- continue;
10194
10575
  }
10195
- for (const intf of compInterfaces) {
10576
+ }
10577
+ }
10578
+ };
10579
+ }
10580
+ });
10581
+
10582
+ // src/core/rules/doctrine/non-portal-endpoints.ts
10583
+ var nonPortalEndpointsRule;
10584
+ var init_non_portal_endpoints = __esm({
10585
+ "src/core/rules/doctrine/non-portal-endpoints.ts"() {
10586
+ "use strict";
10587
+ nonPortalEndpointsRule = {
10588
+ name: "non-portal-endpoints",
10589
+ description: "Only a Portal may carry endpoints: a method on a non-Portal component's interface that declares one is publishing a block that is not the system's boundary as if it were.",
10590
+ codes: [
10591
+ { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10592
+ ],
10593
+ check(ctx) {
10594
+ for (const comp of ctx.components) {
10595
+ if (comp.componentType === "Portal") continue;
10596
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10597
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10196
10598
  const isIntfDraft = intf.status === "draft" || intf.status === "design";
10197
10599
  for (const m of intf.methods) {
10198
10600
  if (m.endpoint) {
@@ -10213,20 +10615,16 @@ var init_portal_endpoints = __esm({
10213
10615
  });
10214
10616
 
10215
10617
  // src/core/rules/doctrine/portal-call-auth.ts
10216
- var COMPONENT_REF_PREFIX, portalCallAuthRule;
10618
+ var portalCallAuthRule;
10217
10619
  var init_portal_call_auth = __esm({
10218
10620
  "src/core/rules/doctrine/portal-call-auth.ts"() {
10219
10621
  "use strict";
10220
- COMPONENT_REF_PREFIX = "component:";
10221
10622
  portalCallAuthRule = {
10222
10623
  name: "portal-call-auth",
10223
- description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. When `auth.from` is a modeled reference (`component:<id>`) it must resolve to an Adapter/Store the presenter is wired to (UNKNOWN_AUTH_SOURCE / AUTH_SOURCE_NOT_PROVIDER / AUTH_SOURCE_UNWIRED). The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
10624
+ description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
10224
10625
  codes: [
10225
10626
  { code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
10226
- { 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)" },
10227
- { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10228
- { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10229
- { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10627
+ { code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" }
10230
10628
  ],
10231
10629
  check(ctx) {
10232
10630
  for (const impl of ctx.implementations) {
@@ -10249,8 +10647,7 @@ var init_portal_call_auth = __esm({
10249
10647
  draft
10250
10648
  );
10251
10649
  }
10252
- const from = step.auth?.from;
10253
- if (!from) {
10650
+ if (!step.auth?.from) {
10254
10651
  ctx.addIssue(
10255
10652
  "warning",
10256
10653
  "PORTAL_AUTH_UNMET",
@@ -10258,40 +10655,73 @@ var init_portal_call_auth = __esm({
10258
10655
  impl.id,
10259
10656
  draft
10260
10657
  );
10658
+ }
10659
+ }
10660
+ }
10661
+ }
10662
+ }
10663
+ };
10664
+ }
10665
+ });
10666
+
10667
+ // src/core/rules/doctrine/auth-source-wiring.ts
10668
+ var COMPONENT_REF_PREFIX, authSourceWiringRule;
10669
+ var init_auth_source_wiring = __esm({
10670
+ "src/core/rules/doctrine/auth-source-wiring.ts"() {
10671
+ "use strict";
10672
+ COMPONENT_REF_PREFIX = "component:";
10673
+ authSourceWiringRule = {
10674
+ name: "auth-source-wiring",
10675
+ description: "Resolves the credential source a narrative call presents to an authenticated Portal. When the step's `auth.from` is a modeled reference (`component:<id>`) it must name a component that exists (UNKNOWN_AUTH_SOURCE), that can hold or load a secret \u2014 an Adapter or a Store (AUTH_SOURCE_NOT_PROVIDER) \u2014 and that the presenting component depends on or owns (AUTH_SOURCE_UNWIRED). Opaque sources (env:/config:/vault:/free note) are design notes and are never resolved; a missing source is portal-call-auth's finding.",
10676
+ codes: [
10677
+ { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10678
+ { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10679
+ { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10680
+ ],
10681
+ check(ctx) {
10682
+ for (const impl of ctx.implementations) {
10683
+ const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
10684
+ const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
10685
+ const draft = ctx.isImplementationDraft(impl);
10686
+ for (const method of impl.methods ?? []) {
10687
+ for (const step of method.narrative ?? []) {
10688
+ if (step.type !== "call") continue;
10689
+ if (!step.targetComponent || step.targetComponent === ownComponent) continue;
10690
+ const target = ctx.componentMap.get(step.targetComponent);
10691
+ if (!target || target.componentType !== "Portal") continue;
10692
+ if (!target.auth || target.auth.scheme === "none") continue;
10693
+ const from = step.auth?.from;
10694
+ if (!from || !from.startsWith(COMPONENT_REF_PREFIX)) continue;
10695
+ const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10696
+ const src = ctx.componentMap.get(srcId);
10697
+ if (!src) {
10698
+ ctx.addIssue(
10699
+ "warning",
10700
+ "UNKNOWN_AUTH_SOURCE",
10701
+ `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10702
+ impl.id,
10703
+ draft
10704
+ );
10261
10705
  continue;
10262
10706
  }
10263
- if (from.startsWith(COMPONENT_REF_PREFIX)) {
10264
- const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10265
- const src = ctx.componentMap.get(srcId);
10266
- if (!src) {
10267
- ctx.addIssue(
10268
- "warning",
10269
- "UNKNOWN_AUTH_SOURCE",
10270
- `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10271
- impl.id,
10272
- draft
10273
- );
10274
- } else {
10275
- if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10276
- ctx.addIssue(
10277
- "warning",
10278
- "AUTH_SOURCE_NOT_PROVIDER",
10279
- `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
10280
- impl.id,
10281
- draft
10282
- );
10283
- }
10284
- const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10285
- if (presenter && !wired) {
10286
- ctx.addIssue(
10287
- "warning",
10288
- "AUTH_SOURCE_UNWIRED",
10289
- `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
10290
- impl.id,
10291
- draft
10292
- );
10293
- }
10294
- }
10707
+ if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10708
+ ctx.addIssue(
10709
+ "warning",
10710
+ "AUTH_SOURCE_NOT_PROVIDER",
10711
+ `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
10712
+ impl.id,
10713
+ draft
10714
+ );
10715
+ }
10716
+ const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10717
+ if (presenter && !wired) {
10718
+ ctx.addIssue(
10719
+ "warning",
10720
+ "AUTH_SOURCE_UNWIRED",
10721
+ `Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
10722
+ impl.id,
10723
+ draft
10724
+ );
10295
10725
  }
10296
10726
  }
10297
10727
  }
@@ -10301,257 +10731,341 @@ var init_portal_call_auth = __esm({
10301
10731
  }
10302
10732
  });
10303
10733
 
10304
- // src/core/rules/doctrine/stereotype-dependencies.ts
10305
- var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf, stereotypeDepsRule;
10306
- var init_stereotype_dependencies = __esm({
10307
- "src/core/rules/doctrine/stereotype-dependencies.ts"() {
10734
+ // src/core/rules/doctrine/subsystem-boundary-dependencies.ts
10735
+ var subsystemBoundaryDepsRule;
10736
+ var init_subsystem_boundary_dependencies = __esm({
10737
+ "src/core/rules/doctrine/subsystem-boundary-dependencies.ts"() {
10308
10738
  "use strict";
10309
10739
  init_models();
10310
- NEVER_INLINE_STATE = " Never resolve this by merging the store's state into the consuming component \u2014 state hidden inside a logic block is invisible to the spec and unrecoverable.";
10311
- storeResolutionHint = (consumerType, storeId) => {
10312
- if (consumerType === "Orchestrator") {
10313
- return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
10314
- }
10315
- return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
10316
- };
10317
- isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
10318
- isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
10319
- stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
10320
- stereotypeDepsRule = {
10321
- name: "stereotype-dependencies",
10322
- description: "Enforces the component-stereotype interaction matrix and the cross-subsystem shape. The matrix: a Portal reaches the data layer only through Repository/Index READ faces, and writes route through Orchestrators (PORTAL_WRITE_SHORTCUT); Stores are depended upon; Registries write their Store; Adapters are sinks; Views stay passive; an Orchestrator's dependencyClass bounds what it may depend on; a Supervisor reaches data only through workflows; a live Actor is reached through a Supervisor that supervises it; a Query reads its Store; any block may use pure logic. Across subsystems: client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A governing pack profile may license intra-subsystem edges the matrix refuses via allowedEdges (its platform idiom, with the stated reason); boundary rules stay unrelaxable.",
10740
+ subsystemBoundaryDepsRule = {
10741
+ name: "subsystem-boundary-dependencies",
10742
+ description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A reference that names nothing in this tree is resolved against the stored surface snapshots: a hit is a DECLARED remote portal and the same Adapter requirement applies to it, snapshots of several providers that disagree make the reference ambiguous, and a reference authored to leave this root that no snapshot covers warns instead of erroring. Boundary rules are never relaxed by a pack profile.",
10323
10743
  codes: [
10324
10744
  { code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
10325
10745
  { code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
10326
10746
  { code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
10327
10747
  { code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
10328
10748
  { code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
10329
- { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" },
10330
- { code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
10331
- { code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
10332
- { code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
10333
- { code: "ARCHITECTURE_VIOLATION_REGISTRY_DEP", defaultSeverity: "warning", summary: "Registry depending on anything but its Store, a backend Adapter or pure logic (warning while new; aligned to the standard \xA77 validate\u2192write path)" },
10334
- { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
10335
- { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
10336
- { code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
10337
- { code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" },
10338
- { code: "DEPENDENCY_CLASS_VIOLATION", defaultSeverity: "error", summary: "Orchestrator depending on a component its dependencyClass does not allow: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters" },
10339
- { code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on anything but Actors, Orchestrators, Adapters or other Supervisors \u2014 it reaches data only through workflows" },
10340
- { code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise, without also depending on a Supervisor that supervises it \u2014 a live Actor is reached by id through its Supervisor" },
10341
- { code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
10749
+ { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
10342
10750
  ],
10343
10751
  check(ctx) {
10344
- for (const comp of ctx.components) {
10345
- const isDraftCtx = ctx.isComponentDraft(comp.id);
10346
- const dependencies = comp.dependsOn;
10347
- for (const depId of dependencies) {
10348
- const depComp = ctx.componentMap.get(depId);
10349
- if (!depComp) {
10350
- const external = ctx.isExternalNamespaceRef(depId);
10351
- if (external || ctx.isCollapsedCrossTreeRef(depId, comp.subsystem)) {
10352
- const resolved = ctx.resolveSurfaceRef(depId, comp.subsystem);
10353
- if (resolved.kind === "ambiguous") {
10354
- ctx.addIssue("error", "SURFACE_REF_AMBIGUOUS", ambiguityMessage(resolved, `Component "${comp.id}" depends on`, depId), comp.id, isDraftCtx);
10355
- continue;
10356
- }
10357
- if (resolved.kind === "resolved") {
10358
- if (comp.componentType !== "Adapter") {
10359
- ctx.addIssue(
10360
- "error",
10361
- "CROSS_SUBSYSTEM_NON_ADAPTER",
10362
- `Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${depId}", a surface of project "${resolved.snapshot.projectName}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
10363
- comp.id,
10364
- isDraftCtx,
10365
- true
10366
- );
10367
- }
10368
- continue;
10369
- }
10370
- if (external) {
10371
- ctx.addIssue(
10372
- "warning",
10373
- "CROSS_TREE_REF_UNRESOLVED",
10374
- `Component "${comp.id}" depends on cross-tree component "${depId}", and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
10375
- comp.id,
10376
- isDraftCtx
10377
- );
10378
- continue;
10379
- }
10380
- }
10752
+ for (const edge of ctx.dependencyEdges().all) {
10753
+ if (edge.retired) continue;
10754
+ const comp = edge.from;
10755
+ switch (edge.reach) {
10756
+ case "missing":
10381
10757
  ctx.addIssue(
10382
10758
  "error",
10383
10759
  "INVALID_DEPENDENCY_REFERENCE",
10384
- `Component "${comp.id}" lists dependency "${depId}" which does not exist.`,
10760
+ `Component "${comp.id}" lists dependency "${edge.ref}" which does not exist.`,
10385
10761
  comp.id,
10386
- isDraftCtx
10762
+ edge.draftContext
10387
10763
  );
10388
10764
  continue;
10389
- }
10390
- if (isRetired(comp) || isRetired(depComp)) continue;
10391
- if (depComp.subsystem !== comp.subsystem) {
10392
- const crossDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
10393
- const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
10394
- const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
10395
- if (comp.componentType !== "Adapter" && !licensed) {
10396
- ctx.addIssue(
10397
- "error",
10398
- "CROSS_SUBSYSTEM_NON_ADAPTER",
10399
- `Boundary violation: ${comp.componentType} "${comp.id}" (subsystem "${comp.subsystem}") depends directly on "${depComp.id}" in subsystem "${depComp.subsystem}". Only a local client Adapter may cross a subsystem boundary \u2014 route this hop through an Adapter that calls "${depComp.subsystem}"'s public interface, or declare a trustedLink on "${comp.subsystem}" (with the reason) to license a direct in-process edge to that peer's published Portal.`,
10400
- comp.id,
10401
- crossDraft
10402
- );
10403
- }
10404
- const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
10405
- if (!targetIsPublic) {
10406
- ctx.addIssue(
10407
- "error",
10408
- "CROSS_SUBSYSTEM_PRIVATE_ACCESS",
10409
- `Boundary violation: "${comp.id}" depends on "${depComp.id}", which is not part of subsystem "${depComp.subsystem}"'s published public surface. Depend on one of its publicInterfaces components instead.`,
10410
- comp.id,
10411
- crossDraft
10412
- );
10413
- } else if (depComp.componentType !== "Portal") {
10414
- const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
10415
- ctx.addIssue(
10416
- "error",
10417
- "CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
10418
- `Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
10419
- comp.id,
10420
- crossDraft
10421
- );
10422
- }
10423
- continue;
10424
- }
10425
- const governingProfile2 = ctx.ext.profiles[ctx.getComponentProfile(comp.id)];
10426
- if (governingProfile2?.allowedEdges?.some((e) => e.from.includes(comp.componentType) && e.to.includes(depComp.componentType))) {
10765
+ case "unpinned":
10766
+ ctx.addIssue(
10767
+ "warning",
10768
+ "CROSS_TREE_REF_UNRESOLVED",
10769
+ `Component "${comp.id}" depends on cross-tree component "${edge.ref}", and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
10770
+ comp.id,
10771
+ edge.draftContext
10772
+ );
10427
10773
  continue;
10428
- }
10429
- const edgeDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
10430
- const depPure = isPureLogic(depComp);
10431
- const depRead = isReadLogic(depComp);
10432
- const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10433
- if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
10774
+ case "ambiguous":
10434
10775
  ctx.addIssue(
10435
10776
  "error",
10436
- "ARCHITECTURE_VIOLATION_PORTAL_DEP",
10437
- `Architectural violation: Component "${comp.id}" cannot depend on ${depComp.componentType} component "${depComp.id}". ${depComp.componentType}s are top-level entry points/subscribers and cannot be dependencies.`,
10777
+ "SURFACE_REF_AMBIGUOUS",
10778
+ ambiguityMessage(edge.surface, `Component "${comp.id}" depends on`, edge.ref),
10438
10779
  comp.id,
10439
- edgeDraft
10780
+ edge.draftContext
10440
10781
  );
10441
10782
  continue;
10442
- }
10443
- if (comp.componentType === "Portal" || comp.componentType === "Observer") {
10444
- const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
10445
- if (forbiddenTypes.includes(depComp.componentType)) {
10783
+ case "surface":
10784
+ if (comp.componentType !== "Adapter") {
10785
+ const provider = edge.surface.kind === "resolved" ? edge.surface.snapshot.projectName : "";
10446
10786
  ctx.addIssue(
10447
10787
  "error",
10448
- "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
10449
- `Architectural violation: ${comp.componentType} component "${comp.id}" cannot depend directly on "${depComp.componentType}" component "${depComp.id}". ${comp.componentType}s coordinate through Orchestrators (and Supervisors); they do not reach the data layer directly.` + storeHint,
10788
+ "CROSS_SUBSYSTEM_NON_ADAPTER",
10789
+ `Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${edge.ref}", a surface of project "${provider}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
10450
10790
  comp.id,
10451
- edgeDraft
10791
+ edge.draftContext,
10792
+ true
10452
10793
  );
10453
10794
  }
10454
- }
10455
- if (isPureLogic(comp) && !depPure) {
10456
- ctx.addIssue(
10457
- "error",
10458
- "DEPENDENCY_CLASS_VIOLATION",
10459
- `Architectural violation: Orchestrator "${comp.id}" (dependencyClass pure) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Pure logic depends only on pure Orchestrators \u2014 move that dependency to the workflow that calls "${comp.id}", or declare "${comp.id}" read when it reads through read logic, a Repository, an Index or an Adapter.` + storeHint,
10460
- comp.id,
10461
- edgeDraft
10462
- );
10463
- }
10464
- if (isReadLogic(comp) && !depPure && !depRead && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
10465
- ctx.addIssue(
10466
- "error",
10467
- "DEPENDENCY_CLASS_VIOLATION",
10468
- `Architectural violation: Orchestrator "${comp.id}" (dependencyClass read) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Read logic depends only on pure or read Orchestrators, Repositories, Indexes and Adapters \u2014 move that dependency to the workflow that calls "${comp.id}", or unset the dependencyClass of "${comp.id}" when it is itself a workflow.` + storeHint,
10469
- comp.id,
10470
- edgeDraft
10471
- );
10472
- }
10473
- if (comp.componentType === "Store" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10474
- ctx.addIssue(
10475
- "error",
10476
- "ARCHITECTURE_VIOLATION_STORE_DEP",
10477
- `Architectural violation: Store component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". Stores may only depend on other Stores, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse.`,
10478
- comp.id,
10479
- edgeDraft
10480
- );
10481
- }
10482
- if (comp.componentType === "Registry" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10483
- ctx.addIssue(
10484
- "warning",
10485
- "ARCHITECTURE_VIOLATION_REGISTRY_DEP",
10486
- `Architectural violation: Registry component "${comp.id}" should not depend on "${depComp.componentType}" component "${depComp.id}". A Registry is the write path to its Store and may depend only on that Store, a backend Adapter, or pure logic that validates the write \u2014 the Registry never updates Indexes and never drives workflow.`,
10487
- comp.id,
10488
- edgeDraft
10489
- );
10490
- }
10491
- if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
10492
- ctx.addIssue(
10493
- "error",
10494
- "ARCHITECTURE_VIOLATION_ADAPTER_DEP",
10495
- `Architectural violation: Adapter component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". An Adapter may use pure logic, but never a Store or any other Orchestrator.`,
10496
- comp.id,
10497
- edgeDraft
10498
- );
10499
- }
10500
- if (comp.componentType === "Index" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10501
- ctx.addIssue(
10502
- "error",
10503
- "ARCHITECTURE_VIOLATION_INDEX_DEP",
10504
- `Architectural violation: Index component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". An Index is a read projection and may depend only on its Store, a backend Adapter or pure logic.`,
10505
- comp.id,
10506
- edgeDraft
10507
- );
10508
- }
10509
- if (comp.componentType === "Query" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10510
- ctx.addIssue(
10511
- "error",
10512
- "ARCHITECTURE_VIOLATION_QUERY_DEP",
10513
- `Architectural violation: Query "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Query computes reads over its Store, through a backend Adapter where one serves it, with pure logic \u2014 move any other dependency to the workflow that calls the Repository facade.`,
10514
- comp.id,
10515
- edgeDraft
10516
- );
10517
- }
10518
- if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !depPure)) {
10795
+ continue;
10796
+ case "cross-subsystem":
10797
+ break;
10798
+ default:
10799
+ continue;
10800
+ }
10801
+ const depComp = edge.to;
10802
+ const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
10803
+ const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
10804
+ if (comp.componentType !== "Adapter" && !licensed) {
10805
+ ctx.addIssue(
10806
+ "error",
10807
+ "CROSS_SUBSYSTEM_NON_ADAPTER",
10808
+ `Boundary violation: ${comp.componentType} "${comp.id}" (subsystem "${comp.subsystem}") depends directly on "${depComp.id}" in subsystem "${depComp.subsystem}". Only a local client Adapter may cross a subsystem boundary \u2014 route this hop through an Adapter that calls "${depComp.subsystem}"'s public interface, or declare a trustedLink on "${comp.subsystem}" (with the reason) to license a direct in-process edge to that peer's published Portal.`,
10809
+ comp.id,
10810
+ edge.draftContext
10811
+ );
10812
+ }
10813
+ const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
10814
+ if (!targetIsPublic) {
10815
+ ctx.addIssue(
10816
+ "error",
10817
+ "CROSS_SUBSYSTEM_PRIVATE_ACCESS",
10818
+ `Boundary violation: "${comp.id}" depends on "${depComp.id}", which is not part of subsystem "${depComp.subsystem}"'s published public surface. Depend on one of its publicInterfaces components instead.`,
10819
+ comp.id,
10820
+ edge.draftContext
10821
+ );
10822
+ } else if (depComp.componentType !== "Portal") {
10823
+ const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
10824
+ ctx.addIssue(
10825
+ "error",
10826
+ "CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
10827
+ `Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
10828
+ comp.id,
10829
+ edge.draftContext
10830
+ );
10831
+ }
10832
+ }
10833
+ }
10834
+ };
10835
+ }
10836
+ });
10837
+
10838
+ // src/core/rules/doctrine/stereotype-terms.ts
10839
+ var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf;
10840
+ var init_stereotype_terms = __esm({
10841
+ "src/core/rules/doctrine/stereotype-terms.ts"() {
10842
+ "use strict";
10843
+ NEVER_INLINE_STATE = " Never resolve this by merging the store's state into the consuming component \u2014 state hidden inside a logic block is invisible to the spec and unrecoverable.";
10844
+ storeResolutionHint = (consumerType, storeId) => {
10845
+ if (consumerType === "Orchestrator") {
10846
+ return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
10847
+ }
10848
+ return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
10849
+ };
10850
+ isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
10851
+ isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
10852
+ stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
10853
+ }
10854
+ });
10855
+
10856
+ // src/core/rules/doctrine/logic-dependency-class.ts
10857
+ var logicDependencyClassRule;
10858
+ var init_logic_dependency_class = __esm({
10859
+ "src/core/rules/doctrine/logic-dependency-class.ts"() {
10860
+ "use strict";
10861
+ init_stereotype_terms();
10862
+ logicDependencyClassRule = {
10863
+ name: "logic-dependency-class",
10864
+ description: "Bounds an Orchestrator's dependencies by the dependencyClass it declares: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters; an Orchestrator that declares no class is a workflow and may depend on anything the rest of the matrix allows. Whether read logic calls a WRITE method on one of those is judged only once facade methods carry effect tags.",
10865
+ codes: [
10866
+ { code: "DEPENDENCY_CLASS_VIOLATION", defaultSeverity: "error", summary: "Orchestrator depending on a component its dependencyClass does not allow: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters" }
10867
+ ],
10868
+ check(ctx) {
10869
+ for (const edge of ctx.dependencyEdges().matrix) {
10870
+ const comp = edge.from;
10871
+ const depComp = edge.to;
10872
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
10873
+ const depPure = isPureLogic(depComp);
10874
+ const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10875
+ if (isPureLogic(comp) && !depPure) {
10876
+ ctx.addIssue(
10877
+ "error",
10878
+ "DEPENDENCY_CLASS_VIOLATION",
10879
+ `Architectural violation: Orchestrator "${comp.id}" (dependencyClass pure) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Pure logic depends only on pure Orchestrators \u2014 move that dependency to the workflow that calls "${comp.id}", or declare "${comp.id}" read when it reads through read logic, a Repository, an Index or an Adapter.` + storeHint,
10880
+ comp.id,
10881
+ edge.draftContext
10882
+ );
10883
+ }
10884
+ if (isReadLogic(comp) && !depPure && !isReadLogic(depComp) && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
10885
+ ctx.addIssue(
10886
+ "error",
10887
+ "DEPENDENCY_CLASS_VIOLATION",
10888
+ `Architectural violation: Orchestrator "${comp.id}" (dependencyClass read) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Read logic depends only on pure or read Orchestrators, Repositories, Indexes and Adapters \u2014 move that dependency to the workflow that calls "${comp.id}", or unset the dependencyClass of "${comp.id}" when it is itself a workflow.` + storeHint,
10889
+ comp.id,
10890
+ edge.draftContext
10891
+ );
10892
+ }
10893
+ }
10894
+ }
10895
+ };
10896
+ }
10897
+ });
10898
+
10899
+ // src/core/rules/doctrine/data-block-dependencies.ts
10900
+ var dataBlockDepsRule;
10901
+ var init_data_block_dependencies = __esm({
10902
+ "src/core/rules/doctrine/data-block-dependencies.ts"() {
10903
+ "use strict";
10904
+ init_stereotype_terms();
10905
+ dataBlockDepsRule = {
10906
+ name: "data-block-dependencies",
10907
+ description: "Keeps the data layer facing down. A Store may depend only on another Store, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse. A Registry is the write path to its Store and may depend on that Store, a backend Adapter, or the pure logic that validates the write. An Index is a read projection and a Query a computed read, each over its Store through a backend Adapter where one serves it. An Adapter is a sink toward the system: it may use pure logic, but never a Store or any other Orchestrator.",
10908
+ codes: [
10909
+ { code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
10910
+ { code: "ARCHITECTURE_VIOLATION_REGISTRY_DEP", defaultSeverity: "warning", summary: "Registry depending on anything but its Store, a backend Adapter or pure logic (warning while new; aligned to the standard \xA77 validate\u2192write path)" },
10911
+ { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
10912
+ { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
10913
+ { code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
10914
+ ],
10915
+ check(ctx) {
10916
+ for (const edge of ctx.dependencyEdges().matrix) {
10917
+ const comp = edge.from;
10918
+ const depComp = edge.to;
10919
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
10920
+ const depPure = isPureLogic(depComp);
10921
+ const ownStoreOnly = depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure;
10922
+ if (comp.componentType === "Store" && ownStoreOnly) {
10923
+ ctx.addIssue(
10924
+ "error",
10925
+ "ARCHITECTURE_VIOLATION_STORE_DEP",
10926
+ `Architectural violation: Store component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". Stores may only depend on other Stores, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse.`,
10927
+ comp.id,
10928
+ edge.draftContext
10929
+ );
10930
+ }
10931
+ if (comp.componentType === "Registry" && ownStoreOnly) {
10932
+ ctx.addIssue(
10933
+ "warning",
10934
+ "ARCHITECTURE_VIOLATION_REGISTRY_DEP",
10935
+ `Architectural violation: Registry component "${comp.id}" should not depend on "${depComp.componentType}" component "${depComp.id}". A Registry is the write path to its Store and may depend only on that Store, a backend Adapter, or pure logic that validates the write \u2014 the Registry never updates Indexes and never drives workflow.`,
10936
+ comp.id,
10937
+ edge.draftContext
10938
+ );
10939
+ }
10940
+ if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
10941
+ ctx.addIssue(
10942
+ "error",
10943
+ "ARCHITECTURE_VIOLATION_ADAPTER_DEP",
10944
+ `Architectural violation: Adapter component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". An Adapter may use pure logic, but never a Store or any other Orchestrator.`,
10945
+ comp.id,
10946
+ edge.draftContext
10947
+ );
10948
+ }
10949
+ if (comp.componentType === "Index" && ownStoreOnly) {
10950
+ ctx.addIssue(
10951
+ "error",
10952
+ "ARCHITECTURE_VIOLATION_INDEX_DEP",
10953
+ `Architectural violation: Index component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". An Index is a read projection and may depend only on its Store, a backend Adapter or pure logic.`,
10954
+ comp.id,
10955
+ edge.draftContext
10956
+ );
10957
+ }
10958
+ if (comp.componentType === "Query" && ownStoreOnly) {
10959
+ ctx.addIssue(
10960
+ "error",
10961
+ "ARCHITECTURE_VIOLATION_QUERY_DEP",
10962
+ `Architectural violation: Query "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Query computes reads over its Store, through a backend Adapter where one serves it, with pure logic \u2014 move any other dependency to the workflow that calls the Repository facade.`,
10963
+ comp.id,
10964
+ edge.draftContext
10965
+ );
10966
+ }
10967
+ }
10968
+ }
10969
+ };
10970
+ }
10971
+ });
10972
+
10973
+ // src/core/rules/doctrine/entrypoint-dependencies.ts
10974
+ var entrypointDepsRule;
10975
+ var init_entrypoint_dependencies = __esm({
10976
+ "src/core/rules/doctrine/entrypoint-dependencies.ts"() {
10977
+ "use strict";
10978
+ init_stereotype_terms();
10979
+ entrypointDepsRule = {
10980
+ name: "entrypoint-dependencies",
10981
+ description: "Judges the edges at the system's entry points and its process layer. Portals and Observers are top-level entry points and subscribers, so nothing may depend on them \u2014 and that is the edge's one finding, which is why no other matrix rule judges it. Downward, a Portal dispatches to Orchestrators and may READ through Indexes and Repository facades but never reaches Store/Registry/Query or an Adapter, while an Observer forwards to one Orchestrator or Supervisor over a message-bus Adapter. A View stays a passive presenter. A Supervisor reaches data only through workflows, and a component depending on a live Actor must also depend on a Supervisor that supervises it.",
10982
+ codes: [
10983
+ { code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
10984
+ { code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
10985
+ { code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
10986
+ { code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on anything but Actors, Orchestrators, Adapters or other Supervisors \u2014 it reaches data only through workflows" },
10987
+ { code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise, without also depending on a Supervisor that supervises it \u2014 a live Actor is reached by id through its Supervisor" }
10988
+ ],
10989
+ check(ctx) {
10990
+ for (const edge of ctx.dependencyEdges().matrix) {
10991
+ const comp = edge.from;
10992
+ const depComp = edge.to;
10993
+ const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10994
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
10995
+ ctx.addIssue(
10996
+ "error",
10997
+ "ARCHITECTURE_VIOLATION_PORTAL_DEP",
10998
+ `Architectural violation: Component "${comp.id}" cannot depend on ${depComp.componentType} component "${depComp.id}". ${depComp.componentType}s are top-level entry points/subscribers and cannot be dependencies.`,
10999
+ comp.id,
11000
+ edge.draftContext
11001
+ );
11002
+ continue;
11003
+ }
11004
+ if (comp.componentType === "Portal" || comp.componentType === "Observer") {
11005
+ const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
11006
+ if (forbiddenTypes.includes(depComp.componentType)) {
10519
11007
  ctx.addIssue(
10520
11008
  "error",
10521
- "ARCHITECTURE_VIOLATION_VIEW_DEP",
10522
- `Architectural violation: View component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". Views must remain passive UI blocks that use pure logic at most.` + storeHint,
11009
+ "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
11010
+ `Architectural violation: ${comp.componentType} component "${comp.id}" cannot depend directly on "${depComp.componentType}" component "${depComp.id}". ${comp.componentType}s coordinate through Orchestrators (and Supervisors); they do not reach the data layer directly.` + storeHint,
10523
11011
  comp.id,
10524
- edgeDraft
11012
+ edge.draftContext
10525
11013
  );
10526
11014
  }
10527
- if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
11015
+ }
11016
+ if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !isPureLogic(depComp))) {
11017
+ ctx.addIssue(
11018
+ "error",
11019
+ "ARCHITECTURE_VIOLATION_VIEW_DEP",
11020
+ `Architectural violation: View component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". Views must remain passive UI blocks that use pure logic at most.` + storeHint,
11021
+ comp.id,
11022
+ edge.draftContext
11023
+ );
11024
+ }
11025
+ if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
11026
+ ctx.addIssue(
11027
+ "error",
11028
+ "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
11029
+ `Architectural violation: Supervisor "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Supervisor reaches data only through workflows \u2014 depend on the Orchestrator, Actor or Adapter that does that work instead.` + storeHint,
11030
+ comp.id,
11031
+ edge.draftContext
11032
+ );
11033
+ }
11034
+ if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
11035
+ const reachedThroughSupervisor = comp.dependsOn.some((id) => {
11036
+ const supervisor = ctx.componentMap.get(id);
11037
+ return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
11038
+ });
11039
+ if (!reachedThroughSupervisor) {
11040
+ const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
11041
+ const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
10528
11042
  ctx.addIssue(
10529
11043
  "error",
10530
- "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
10531
- `Architectural violation: Supervisor "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Supervisor reaches data only through workflows \u2014 depend on the Orchestrator, Actor or Adapter that does that work instead.` + storeHint,
11044
+ "ACTOR_REACHED_WITHOUT_SUPERVISOR",
11045
+ `Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
10532
11046
  comp.id,
10533
- edgeDraft
11047
+ edge.draftContext
10534
11048
  );
10535
11049
  }
10536
- if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
10537
- const reachedThroughSupervisor = comp.dependsOn.some((id) => {
10538
- const supervisor = ctx.componentMap.get(id);
10539
- return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
10540
- });
10541
- if (!reachedThroughSupervisor) {
10542
- const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
10543
- const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
10544
- ctx.addIssue(
10545
- "error",
10546
- "ACTOR_REACHED_WITHOUT_SUPERVISOR",
10547
- `Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
10548
- comp.id,
10549
- edgeDraft
10550
- );
10551
- }
10552
- }
10553
11050
  }
10554
11051
  }
11052
+ }
11053
+ };
11054
+ }
11055
+ });
11056
+
11057
+ // src/core/rules/doctrine/portal-write-shortcut.ts
11058
+ var portalWriteShortcutRule;
11059
+ var init_portal_write_shortcut = __esm({
11060
+ "src/core/rules/doctrine/portal-write-shortcut.ts"() {
11061
+ "use strict";
11062
+ portalWriteShortcutRule = {
11063
+ name: "portal-write-shortcut",
11064
+ description: "A Portal narrative call step or dispatch-table binding that reaches a write-effect method on a Repository or Index directly is the write shortcut: the Portal\u2192data-facade edge is licensed for reads only, and a write routes through an Orchestrator that owns the workflow. A dispatch step reaches its server only through a table binding, so judging every binding judges each dispatch step that takes it, once, where the route is declared. Methods that carry no effect tag are not judged.",
11065
+ codes: [
11066
+ { code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
11067
+ ],
11068
+ check(ctx) {
10555
11069
  for (const impl of ctx.implementations) {
10556
11070
  const contract = ctx.interfaceMap.get(impl.contract);
10557
11071
  const component = contract ? ctx.componentMap.get(contract.component) : void 0;
@@ -10596,31 +11110,23 @@ var init_stereotype_dependencies = __esm({
10596
11110
  }
10597
11111
  });
10598
11112
 
10599
- // src/core/rules/doctrine/pattern-ownership.ts
10600
- var patternsRule;
10601
- var init_pattern_ownership = __esm({
10602
- "src/core/rules/doctrine/pattern-ownership.ts"() {
11113
+ // src/core/rules/doctrine/pattern-membership.ts
11114
+ var patternMembershipRule;
11115
+ var init_pattern_membership = __esm({
11116
+ "src/core/rules/doctrine/pattern-membership.ts"() {
10603
11117
  "use strict";
10604
11118
  init_models();
10605
- patternsRule = {
10606
- name: "pattern-ownership",
10607
- description: "Patterns (Repository/FeatureComponent/RouterComponent) own member blocks with the containment their definition prescribes; blocks own nothing; a member has exactly one owner; and private members are reachable only via their facade or siblings.",
11119
+ patternMembershipRule = {
11120
+ name: "pattern-membership",
11121
+ description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern to claim it.",
10608
11122
  codes: [
10609
11123
  { code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
10610
11124
  { code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block using owns" },
10611
11125
  { code: "INVALID_OWNED_MEMBER", defaultSeverity: "error", summary: "owns names a non-existent component" },
10612
11126
  { code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
10613
- { code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" },
10614
- { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
10615
- { code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
10616
- { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
10617
- { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
10618
- { code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
10619
- { code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" },
10620
- { code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
11127
+ { code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
10621
11128
  ],
10622
11129
  check(ctx) {
10623
- const ownedBy = /* @__PURE__ */ new Map();
10624
11130
  for (const comp of ctx.components) {
10625
11131
  if (isRetired(comp)) continue;
10626
11132
  const isDraftCtx = ctx.isComponentDraft(comp.id);
@@ -10631,6 +11137,12 @@ var init_pattern_ownership = __esm({
10631
11137
  if (!pattern && comp.owns.length > 0) {
10632
11138
  ctx.addIssue("error", "BLOCK_OWNS_MEMBERS", `Building block "${comp.id}" (${comp.componentType}) cannot own members; only patterns (${Array.from(PATTERN_TYPES).join("/")}) use "owns".`, comp.id, isDraftCtx);
10633
11139
  }
11140
+ }
11141
+ const ownership = ctx.ownershipIndex();
11142
+ for (const comp of ctx.components) {
11143
+ if (isRetired(comp)) continue;
11144
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11145
+ const pattern = isPattern(comp);
10634
11146
  for (const memberId of comp.owns) {
10635
11147
  const member = ctx.componentMap.get(memberId);
10636
11148
  if (!member) {
@@ -10642,26 +11154,50 @@ var init_pattern_ownership = __esm({
10642
11154
  ctx.addIssue("error", "PATTERN_OWNS_PATTERN", `Pattern "${comp.id}" owns "${memberId}", which is itself a pattern. Patterns own only building blocks \u2014 compose patterns at the subsystem (L1) level.`, comp.id, isDraftCtx);
10643
11155
  continue;
10644
11156
  }
10645
- const firstOwner = ownedBy.get(memberId);
11157
+ const firstOwner = ownership.ownerOf(memberId);
10646
11158
  if (firstOwner && firstOwner !== comp.id) {
10647
11159
  ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${firstOwner}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
10648
11160
  }
10649
- if (!firstOwner) ownedBy.set(memberId, comp.id);
10650
11161
  }
10651
- if ((comp.componentType === "FeatureComponent" || comp.componentType === "RouterComponent") && comp.owns.some((memberId) => {
11162
+ }
11163
+ }
11164
+ };
11165
+ }
11166
+ });
11167
+
11168
+ // src/core/rules/doctrine/pattern-containment.ts
11169
+ var REPOSITORY_MEMBERS, patternContainmentRule;
11170
+ var init_pattern_containment = __esm({
11171
+ "src/core/rules/doctrine/pattern-containment.ts"() {
11172
+ "use strict";
11173
+ init_models();
11174
+ REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
11175
+ patternContainmentRule = {
11176
+ name: "pattern-containment",
11177
+ description: "Holds each pattern to the containment its definition prescribes. A Repository may own only Store, Registry, Index, Query and (optionally) Adapter, judged member by member. A FeatureComponent owns exactly one Orchestrator (the logic side) and one or more Views (its faces \u2014 list, detail, form \u2014 sharing the one logic component), and nothing else. A RouterComponent owns exactly one Portal as its facade and at least one other child to route to. A counting pattern that still owns a retired member is not judged until that member is migrated: its counts change with the migration, and STEREOTYPE_RETIRED is the one finding.",
11178
+ codes: [
11179
+ { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
11180
+ { code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
11181
+ { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" }
11182
+ ],
11183
+ check(ctx) {
11184
+ for (const comp of ctx.components) {
11185
+ if (comp.componentType !== "Repository" || isRetired(comp)) continue;
11186
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11187
+ for (const memberId of comp.owns) {
10652
11188
  const member = ctx.componentMap.get(memberId);
10653
- return member !== void 0 && isRetired(member);
10654
- })) {
10655
- continue;
10656
- }
10657
- if (comp.componentType === "Repository") {
10658
- const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
10659
- for (const memberId of comp.owns) {
10660
- const member = ctx.componentMap.get(memberId);
10661
- if (!member || isRetired(member) || allowed.has(member.componentType)) continue;
10662
- ctx.addIssue("error", "REPOSITORY_CONTAINMENT", `Repository "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Repository may own only Store, Registry, Index, Query, and (optionally) Adapter.`, comp.id, isDraftCtx);
10663
- }
11189
+ if (!member || isRetired(member) || REPOSITORY_MEMBERS.has(member.componentType)) continue;
11190
+ ctx.addIssue("error", "REPOSITORY_CONTAINMENT", `Repository "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Repository may own only Store, Registry, Index, Query, and (optionally) Adapter.`, comp.id, isDraftCtx);
10664
11191
  }
11192
+ }
11193
+ for (const comp of ctx.components) {
11194
+ if (comp.componentType !== "FeatureComponent" && comp.componentType !== "RouterComponent") continue;
11195
+ if (isRetired(comp)) continue;
11196
+ if (comp.owns.some((memberId) => {
11197
+ const member = ctx.componentMap.get(memberId);
11198
+ return member !== void 0 && isRetired(member);
11199
+ })) continue;
11200
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10665
11201
  if (comp.componentType === "FeatureComponent") {
10666
11202
  let orchestrators = 0;
10667
11203
  let views = 0;
@@ -10692,45 +11228,83 @@ var init_pattern_ownership = __esm({
10692
11228
  }
10693
11229
  }
10694
11230
  }
11231
+ }
11232
+ };
11233
+ }
11234
+ });
11235
+
11236
+ // src/core/rules/doctrine/unowned-blocks.ts
11237
+ var unownedBlocksRule;
11238
+ var init_unowned_blocks = __esm({
11239
+ "src/core/rules/doctrine/unowned-blocks.ts"() {
11240
+ "use strict";
11241
+ unownedBlocksRule = {
11242
+ name: "unowned-blocks",
11243
+ description: `Judges the data blocks no pattern owns. A Store may stand alone deliberately \u2014 the sanctioned lightweight form for genuinely simple state, acknowledged with a lint.allow \u2014 while the recommended shape stays a Repository; what must never happen is folding the state into a consuming component, where no spec, diagram or conformance check can see it again. A Registry standing alone with no Store to write to is either mistyped (the "file-backed Registry" idiom, a fused persistent store that belongs typed Store) or orphaned. A Query has no standalone form at all: it computes reads over its own Repository's Store.`,
11244
+ codes: [
11245
+ { code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
11246
+ { code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" },
11247
+ { code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
11248
+ ],
11249
+ check(ctx) {
11250
+ const ownership = ctx.ownershipIndex();
10695
11251
  for (const comp of ctx.components) {
10696
- if (comp.componentType !== "Store" || ownedBy.has(comp.id)) continue;
10697
- ctx.addIssue(
10698
- "warning",
10699
- "UNOWNED_STORE",
10700
- `Store "${comp.id}" is not owned by any pattern. The recommended shape for held state is a Repository (owns: ["${comp.id}", its Registry, its Index]) with consumers on the facade. For genuinely simple state a deliberately standalone Store is the sanctioned lightweight form \u2014 keep it visible as this Store, reachable from the workflow layer (Orchestrator/Supervisor/Actor), and acknowledge it with a lint.allow reason. Never take the third path of merging the state into a consuming component: state hidden inside a logic block disappears from the architecture permanently.`,
10701
- comp.id,
10702
- ctx.isComponentDraft(comp.id)
10703
- );
10704
- }
10705
- for (const comp of ctx.components) {
10706
- if (comp.componentType !== "Registry" || ownedBy.has(comp.id)) continue;
10707
- const hasStoreDep = comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store");
10708
- if (hasStoreDep) continue;
10709
- ctx.addIssue(
10710
- "warning",
10711
- "REGISTRY_WITHOUT_STORE",
10712
- `Registry "${comp.id}" stands alone with no Store to write to. A Registry is the write path to a Store \u2014 if this component itself holds the persisted state (a file-backed record/config store), retype it as a Store with the honest durability (read-through for no-RAM-copy file I/O) so the durability machinery can see it; otherwise wire its Store, or lint.allow with a reason.`,
10713
- comp.id,
10714
- ctx.isComponentDraft(comp.id)
10715
- );
10716
- }
10717
- for (const comp of ctx.components) {
10718
- if (comp.componentType !== "Query" || ownedBy.has(comp.id)) continue;
10719
- ctx.addIssue(
10720
- "error",
10721
- "UNOWNED_QUERY",
10722
- `Query "${comp.id}" is not owned by any pattern. A Query computes reads over its own Repository's Store \u2014 make it a member of the Repository whose Store it reads (owns: ["${comp.id}", \u2026]).`,
10723
- comp.id,
10724
- ctx.isComponentDraft(comp.id)
10725
- );
11252
+ if (ownership.ownedMembers.has(comp.id)) continue;
11253
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11254
+ if (comp.componentType === "Store") {
11255
+ ctx.addIssue(
11256
+ "warning",
11257
+ "UNOWNED_STORE",
11258
+ `Store "${comp.id}" is not owned by any pattern. The recommended shape for held state is a Repository (owns: ["${comp.id}", its Registry, its Index]) with consumers on the facade. For genuinely simple state a deliberately standalone Store is the sanctioned lightweight form \u2014 keep it visible as this Store, reachable from the workflow layer (Orchestrator/Supervisor/Actor), and acknowledge it with a lint.allow reason. Never take the third path of merging the state into a consuming component: state hidden inside a logic block disappears from the architecture permanently.`,
11259
+ comp.id,
11260
+ isDraftCtx
11261
+ );
11262
+ }
11263
+ if (comp.componentType === "Registry" && !comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store")) {
11264
+ ctx.addIssue(
11265
+ "warning",
11266
+ "REGISTRY_WITHOUT_STORE",
11267
+ `Registry "${comp.id}" stands alone with no Store to write to. A Registry is the write path to a Store \u2014 if this component itself holds the persisted state (a file-backed record/config store), retype it as a Store with the honest durability (read-through for no-RAM-copy file I/O) so the durability machinery can see it; otherwise wire its Store, or lint.allow with a reason.`,
11268
+ comp.id,
11269
+ isDraftCtx
11270
+ );
11271
+ }
11272
+ if (comp.componentType === "Query") {
11273
+ ctx.addIssue(
11274
+ "error",
11275
+ "UNOWNED_QUERY",
11276
+ `Query "${comp.id}" is not owned by any pattern. A Query computes reads over its own Repository's Store \u2014 make it a member of the Repository whose Store it reads (owns: ["${comp.id}", \u2026]).`,
11277
+ comp.id,
11278
+ isDraftCtx
11279
+ );
11280
+ }
10726
11281
  }
11282
+ }
11283
+ };
11284
+ }
11285
+ });
11286
+
11287
+ // src/core/rules/doctrine/member-visibility.ts
11288
+ var memberVisibilityRule;
11289
+ var init_member_visibility = __esm({
11290
+ "src/core/rules/doctrine/member-visibility.ts"() {
11291
+ "use strict";
11292
+ init_models();
11293
+ memberVisibilityRule = {
11294
+ name: "member-visibility",
11295
+ description: "A component may depend on a block within its own group (it is the owning pattern, or a sibling member of the same pattern), on any pattern facade, or on a standalone block \u2014 never on a block privately owned by ANOTHER pattern, which must be reached through that pattern's facade. A retired depending component is skipped, as everywhere in this family.",
11296
+ codes: [
11297
+ { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
11298
+ ],
11299
+ check(ctx) {
11300
+ const ownership = ctx.ownershipIndex();
10727
11301
  for (const comp of ctx.components) {
10728
11302
  if (isRetired(comp)) continue;
10729
11303
  for (const depId of comp.dependsOn) {
10730
- const owner = ownedBy.get(depId);
11304
+ const owner = ownership.ownerOf(depId);
10731
11305
  if (!owner) continue;
10732
11306
  if (owner === comp.id) continue;
10733
- if (ownedBy.get(comp.id) === owner) continue;
11307
+ if (ownership.ownerOf(comp.id) === owner) continue;
10734
11308
  ctx.addIssue("error", "VISIBILITY_VIOLATION", `Component "${comp.id}" depends on "${depId}", which is privately owned by pattern "${owner}". Depend on the facade "${owner}" instead.`, comp.id, ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(depId));
10735
11309
  }
10736
11310
  }
@@ -11023,17 +11597,135 @@ var init_component_variants = __esm({
11023
11597
  }
11024
11598
  });
11025
11599
 
11026
- // src/core/rules/extension/declarative-assertions.ts
11600
+ // src/core/rules/extension/declared-assertion.ts
11601
+ function assertionsOfKind(ctx, kind) {
11602
+ return ctx.ext.assertions.filter(
11603
+ (a) => a.kind === kind
11604
+ );
11605
+ }
11027
11606
  function globToRegExp(glob) {
11028
11607
  const escaped = glob.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? ".*" : `\\${ch}`);
11029
11608
  return new RegExp(`^${escaped}$`);
11030
11609
  }
11031
- function matches(sel, comp, ctx) {
11610
+ function matchesSelector(sel, comp, ctx) {
11032
11611
  if (sel.componentType?.length && !sel.componentType.includes(comp.componentType)) return false;
11033
11612
  if (sel.profile?.length && !sel.profile.includes(ctx.getComponentProfile(comp.id))) return false;
11034
11613
  if (sel.id && !globToRegExp(sel.id).test(comp.id)) return false;
11035
11614
  return true;
11036
11615
  }
11616
+ function reportAssertion(ctx, a, message, specId, isDraftCtx) {
11617
+ const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11618
+ ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11619
+ }
11620
+ var init_declared_assertion = __esm({
11621
+ "src/core/rules/extension/declared-assertion.ts"() {
11622
+ "use strict";
11623
+ }
11624
+ });
11625
+
11626
+ // src/core/rules/extension/assertion-forbidden-edges.ts
11627
+ var assertionForbiddenEdgesRule;
11628
+ var init_assertion_forbidden_edges = __esm({
11629
+ "src/core/rules/extension/assertion-forbidden-edges.ts"() {
11630
+ "use strict";
11631
+ init_declared_assertion();
11632
+ assertionForbiddenEdgesRule = {
11633
+ name: "assertion-forbidden-edges",
11634
+ description: "Evaluates the `forbid-edge` assertions loaded packs declare: for every component matching the assertion's from-selector, every dependsOn/owns id whose target matches the to-selector is reported. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11635
+ // Static codes are unknown here — packs bring their own. The validator
11636
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11637
+ codes: [],
11638
+ check(ctx) {
11639
+ for (const a of assertionsOfKind(ctx, "forbid-edge")) {
11640
+ for (const comp of ctx.components) {
11641
+ if (!matchesSelector(a.from, comp, ctx)) continue;
11642
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11643
+ const edges = a.relation.flatMap(
11644
+ (relation) => comp[relation].map((targetId) => ({ relation, targetId }))
11645
+ );
11646
+ for (const { relation, targetId } of edges) {
11647
+ const target = ctx.componentMap.get(targetId);
11648
+ if (!target || !matchesSelector(a.to, target, ctx)) continue;
11649
+ reportAssertion(
11650
+ ctx,
11651
+ a,
11652
+ `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`,
11653
+ comp.id,
11654
+ isDraftCtx
11655
+ );
11656
+ }
11657
+ }
11658
+ }
11659
+ }
11660
+ };
11661
+ }
11662
+ });
11663
+
11664
+ // src/core/rules/extension/assertion-required-fields.ts
11665
+ function fieldValue(spec, field) {
11666
+ const segments = field.split(".");
11667
+ if (segments[0] !== "ext") {
11668
+ return segments.length === 1 ? spec[field] : void 0;
11669
+ }
11670
+ let cur = spec.ext;
11671
+ for (const seg of segments.slice(1)) {
11672
+ if (cur === null || typeof cur !== "object") return void 0;
11673
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11674
+ cur = cur[seg];
11675
+ }
11676
+ return cur;
11677
+ }
11678
+ function holdersAt(ctx, level) {
11679
+ const holders = [];
11680
+ if (level === "component") {
11681
+ for (const c of ctx.components) {
11682
+ holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11683
+ }
11684
+ } else if (level === "interface") {
11685
+ for (const i of ctx.interfaces) {
11686
+ const comp = ctx.componentMap.get(i.component);
11687
+ if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11688
+ }
11689
+ } else {
11690
+ for (const impl of ctx.implementations) {
11691
+ const contract = ctx.interfaceMap.get(impl.contract);
11692
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11693
+ if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11694
+ }
11695
+ }
11696
+ return holders;
11697
+ }
11698
+ var assertionRequiredFieldsRule;
11699
+ var init_assertion_required_fields = __esm({
11700
+ "src/core/rules/extension/assertion-required-fields.ts"() {
11701
+ "use strict";
11702
+ init_declared_assertion();
11703
+ assertionRequiredFieldsRule = {
11704
+ name: "assertion-required-fields",
11705
+ description: "Evaluates the `require-field` assertions loaded packs declare: at the assertion's level (component / interface / implementation), every spec whose owning component matches the on-selector must declare the named field \u2014 a top-level spec field or an `ext.*` path \u2014 and, when the assertion states a closed value set, hold one of those values. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11706
+ // Static codes are unknown here — packs bring their own. The validator
11707
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11708
+ codes: [],
11709
+ check(ctx) {
11710
+ for (const a of assertionsOfKind(ctx, "require-field")) {
11711
+ for (const h of holdersAt(ctx, a.level)) {
11712
+ if (!matchesSelector(a.on, h.comp, ctx)) continue;
11713
+ const v = fieldValue(h.spec, a.field);
11714
+ if (v === void 0 || v === null) {
11715
+ reportAssertion(ctx, a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11716
+ continue;
11717
+ }
11718
+ if (a.values && !a.values.includes(String(v))) {
11719
+ reportAssertion(ctx, a, `${a.level} spec "${h.specId}" declares "${a.field}" = "${String(v)}", outside the allowed set (${a.values.join(", ")}) of assertion ${a.code}`, h.specId, h.draft);
11720
+ }
11721
+ }
11722
+ }
11723
+ }
11724
+ };
11725
+ }
11726
+ });
11727
+
11728
+ // src/core/rules/extension/assertion-endpoint-shapes.ts
11037
11729
  function endpointAddress(ep) {
11038
11730
  switch (ep.transport) {
11039
11731
  case "HTTP":
@@ -11054,92 +11746,42 @@ function endpointAddress(ep) {
11054
11746
  return ep.address;
11055
11747
  }
11056
11748
  }
11057
- function fieldValue(spec, field) {
11058
- const segments = field.split(".");
11059
- if (segments[0] !== "ext") {
11060
- return segments.length === 1 ? spec[field] : void 0;
11061
- }
11062
- let cur = spec.ext;
11063
- for (const seg of segments.slice(1)) {
11064
- if (cur === null || typeof cur !== "object") return void 0;
11065
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11066
- cur = cur[seg];
11749
+ function boundEndpoints(ctx, on) {
11750
+ const out = [];
11751
+ for (const intf of ctx.interfaces) {
11752
+ const comp = ctx.componentMap.get(intf.component);
11753
+ if (!comp || !matchesSelector(on, comp, ctx)) continue;
11754
+ const draft = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11755
+ for (const method of intf.methods) {
11756
+ if (!method.endpoint) continue;
11757
+ out.push({ intf, method, endpoint: method.endpoint, draft });
11758
+ }
11067
11759
  }
11068
- return cur;
11760
+ return out;
11069
11761
  }
11070
- var declarativeAssertionsRule;
11071
- var init_declarative_assertions = __esm({
11072
- "src/core/rules/extension/declarative-assertions.ts"() {
11762
+ var assertionEndpointShapesRule;
11763
+ var init_assertion_endpoint_shapes = __esm({
11764
+ "src/core/rules/extension/assertion-endpoint-shapes.ts"() {
11073
11765
  "use strict";
11074
- declarativeAssertionsRule = {
11075
- name: "declarative-assertions",
11076
- description: "Evaluates the declarative rule assertions loaded packs declare (forbid-edge / require-field / endpoint-shape) \u2014 closed kinds instantiated with pack data, the hosted-safe doctrine channel. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11766
+ init_declared_assertion();
11767
+ assertionEndpointShapesRule = {
11768
+ name: "assertion-endpoint-shapes",
11769
+ description: "Evaluates the `endpoint-shape` assertions loaded packs declare: every endpoint bound on a contract whose component matches the on-selector must use an allowed transport and bind an address (path / topic / command / pipe / channel) matching the assertion's anchored pattern. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11077
11770
  // Static codes are unknown here — packs bring their own. The validator
11078
11771
  // gathers every loaded assertion's fullCode into knownIssueCodes.
11079
11772
  codes: [],
11080
11773
  check(ctx) {
11081
- const assertions = ctx.ext.assertions;
11082
- if (!assertions.length) return;
11083
- const emit = (a, message, specId, isDraftCtx) => {
11084
- const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11085
- ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11086
- };
11087
- for (const a of assertions) {
11088
- if (a.kind === "forbid-edge") {
11089
- for (const comp of ctx.components) {
11090
- if (!matches(a.from, comp, ctx)) continue;
11091
- const isDraftCtx = ctx.isComponentDraft(comp.id);
11092
- for (const relation of a.relation) {
11093
- for (const targetId of comp[relation]) {
11094
- const target = ctx.componentMap.get(targetId);
11095
- if (!target || !matches(a.to, target, ctx)) continue;
11096
- emit(a, `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`, comp.id, isDraftCtx);
11097
- }
11098
- }
11099
- }
11100
- } else if (a.kind === "require-field") {
11101
- const holders = [];
11102
- if (a.level === "component") {
11103
- for (const c of ctx.components) holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11104
- } else if (a.level === "interface") {
11105
- for (const i of ctx.interfaces) {
11106
- const comp = ctx.componentMap.get(i.component);
11107
- if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11108
- }
11109
- } else {
11110
- for (const impl of ctx.implementations) {
11111
- const contract = ctx.interfaceMap.get(impl.contract);
11112
- const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11113
- if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11114
- }
11115
- }
11116
- for (const h of holders) {
11117
- if (!matches(a.on, h.comp, ctx)) continue;
11118
- const v = fieldValue(h.spec, a.field);
11119
- if (v === void 0 || v === null) {
11120
- emit(a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11121
- } else if (a.values && !a.values.includes(String(v))) {
11122
- 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);
11123
- }
11774
+ for (const a of assertionsOfKind(ctx, "endpoint-shape")) {
11775
+ const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11776
+ for (const bound2 of boundEndpoints(ctx, a.on)) {
11777
+ const { intf, method, endpoint } = bound2;
11778
+ if (a.transport?.length && !a.transport.includes(endpoint.transport)) {
11779
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method.name}" uses transport ${endpoint.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, bound2.draft);
11780
+ continue;
11124
11781
  }
11125
- } else if (a.kind === "endpoint-shape") {
11126
- const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11127
- for (const intf of ctx.interfaces) {
11128
- const comp = ctx.componentMap.get(intf.component);
11129
- if (!comp || !matches(a.on, comp, ctx)) continue;
11130
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11131
- for (const method of intf.methods) {
11132
- if (!method.endpoint) continue;
11133
- const ep = method.endpoint;
11134
- if (a.transport?.length && !a.transport.includes(ep.transport)) {
11135
- emit(a, `Endpoint of "${intf.id}.${method.name}" uses transport ${ep.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, isDraftCtx);
11136
- continue;
11137
- }
11138
- const address = endpointAddress(ep);
11139
- if (pattern && !pattern.test(address)) {
11140
- emit(a, `Endpoint of "${intf.id}.${method.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, isDraftCtx);
11141
- }
11142
- }
11782
+ const address = endpointAddress(endpoint);
11783
+ if (pattern && !pattern.test(address)) {
11784
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, bound2.draft);
11143
11785
  }
11144
11786
  }
11145
11787
  }
@@ -11579,16 +12221,13 @@ var reachabilityRule;
11579
12221
  var init_unused_detection = __esm({
11580
12222
  "src/core/rules/wiring/unused-detection.ts"() {
11581
12223
  "use strict";
11582
- init_models();
11583
12224
  init_narrative_graph_projector();
11584
12225
  reachabilityRule = {
11585
12226
  name: "unused-detection",
11586
- description: "Walks the narrative execution graph (call steps, register handoffs, dispatch-table routing, lifecycle flows) from every entrypoint (Portal/Observer/published component/lifecycle entrypoint/invokedBy-declared method) and flags components and methods no execution chain reaches, plus types no field or signature references. An interface method declaring invokedBy (a real caller outside the modeled graph) seeds the walk, so its narrative propagates reachability; the declaration itself is audited (thin caller prose, or a method the internal walk already reaches).",
12227
+ description: "Walks the narrative execution graph (call steps, register handoffs, dispatch-table routing, lifecycle flows) from every entrypoint (Portal/Observer/published component/lifecycle entrypoint/invokedBy-declared method) and flags components and methods no execution chain reaches. An interface method declaring invokedBy (a real caller outside the modeled graph) seeds the walk, so its narrative propagates reachability; a declaration the INTERNAL walk (without those seeds) already reaches is stale and is reported.",
11587
12228
  codes: [
11588
12229
  { code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
11589
12230
  { code: "UNUSED_METHOD", defaultSeverity: "warning", summary: "Method never called by any narrative step" },
11590
- { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" },
11591
- { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" },
11592
12231
  { code: "INVOKED_BY_REDUNDANT", defaultSeverity: "warning", summary: "invokedBy declaration on a method the internal narrative walk already reaches \u2014 stale, remove it" }
11593
12232
  ],
11594
12233
  check(ctx) {
@@ -11616,17 +12255,8 @@ var init_unused_detection = __esm({
11616
12255
  if (!m.invokedBy) continue;
11617
12256
  invokedBySeeds.push({ compId: intf.component, methodName: m.name });
11618
12257
  if (!ctx.isSpecInScope(intf.id)) continue;
11619
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
11620
- if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
11621
- ctx.addIssue(
11622
- "warning",
11623
- "INVOKED_BY_UNDESCRIBED",
11624
- `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.`,
11625
- intf.id,
11626
- isDraftCtx
11627
- );
11628
- }
11629
12258
  if (baseWalk.reachesMethod(intf.component, m.name)) {
12259
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
11630
12260
  ctx.addIssue(
11631
12261
  "warning",
11632
12262
  "INVOKED_BY_REDUNDANT",
@@ -11649,23 +12279,75 @@ var init_unused_detection = __esm({
11649
12279
  comp.id,
11650
12280
  isDraftCtx
11651
12281
  );
11652
- } else {
11653
- for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
11654
- for (const m of intf.methods) {
11655
- if (!reach.reachesMethod(comp.id, m.name)) {
11656
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11657
- ctx.addIssue(
11658
- "warning",
11659
- "UNUSED_METHOD",
11660
- `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
11661
- intf.id,
11662
- isDraftCtx
11663
- );
11664
- }
12282
+ continue;
12283
+ }
12284
+ for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
12285
+ for (const m of intf.methods) {
12286
+ if (!reach.reachesMethod(comp.id, m.name)) {
12287
+ const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
12288
+ ctx.addIssue(
12289
+ "warning",
12290
+ "UNUSED_METHOD",
12291
+ `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
12292
+ intf.id,
12293
+ isDraftCtx
12294
+ );
11665
12295
  }
11666
12296
  }
11667
12297
  }
11668
12298
  }
12299
+ }
12300
+ };
12301
+ }
12302
+ });
12303
+
12304
+ // src/core/rules/wiring/invoked-by-description.ts
12305
+ var invokedByDescriptionRule;
12306
+ var init_invoked_by_description = __esm({
12307
+ "src/core/rules/wiring/invoked-by-description.ts"() {
12308
+ "use strict";
12309
+ init_models();
12310
+ invokedByDescriptionRule = {
12311
+ name: "invoked-by-description",
12312
+ description: 'An invokedBy declaration names a caller outside the modeled graph and silences unused-detection for its method, so the claim must stay reviewable: its "caller" prose is held to the intent floor (missing or placeholder-thin prose is reported), stating WHO invokes the method and when.',
12313
+ codes: [
12314
+ { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
12315
+ ],
12316
+ check(ctx) {
12317
+ for (const intf of ctx.interfaces) {
12318
+ if (!ctx.componentMap.has(intf.component)) continue;
12319
+ if (!ctx.isSpecInScope(intf.id)) continue;
12320
+ for (const m of intf.methods) {
12321
+ if (!m.invokedBy) continue;
12322
+ if (passesIntentFloor(m.invokedBy.caller, m.name)) continue;
12323
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
12324
+ ctx.addIssue(
12325
+ "warning",
12326
+ "INVOKED_BY_UNDESCRIBED",
12327
+ `Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
12328
+ intf.id,
12329
+ isDraftCtx
12330
+ );
12331
+ }
12332
+ }
12333
+ }
12334
+ };
12335
+ }
12336
+ });
12337
+
12338
+ // src/core/rules/wiring/unused-types.ts
12339
+ var unusedTypesRule;
12340
+ var init_unused_types = __esm({
12341
+ "src/core/rules/wiring/unused-types.ts"() {
12342
+ "use strict";
12343
+ init_models();
12344
+ unusedTypesRule = {
12345
+ name: "unused-types",
12346
+ description: "Flags types no field of any type and no interface method signature references. References are matched through the type-reference grammar (generic arguments, collections, qualified ids), and the declaring type's own generic parameters are left out \u2014 a type parameter is not a reference to a type.",
12347
+ codes: [
12348
+ { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" }
12349
+ ],
12350
+ check(ctx) {
11669
12351
  const referencedTypes = /* @__PURE__ */ new Set();
11670
12352
  const markTypeReferenced = (ref) => {
11671
12353
  if (ctx.isBuiltinType(ref)) return;
@@ -11899,7 +12581,44 @@ var init_prose_claims = __esm({
11899
12581
  }
11900
12582
  });
11901
12583
 
11902
- // src/core/rules/wiring/invariant-backing.ts
12584
+ // src/core/rules/wiring/unique-invariant-ids.ts
12585
+ var uniqueInvariantIdsRule;
12586
+ var init_unique_invariant_ids = __esm({
12587
+ "src/core/rules/wiring/unique-invariant-ids.ts"() {
12588
+ "use strict";
12589
+ init_models();
12590
+ uniqueInvariantIdsRule = {
12591
+ name: "unique-invariant-ids",
12592
+ description: `An entity's declared invariant ids must be unique within the entity: a narrative step asserts an invariant by "<type-id>.<invariant-id>", so a repeated id makes one assertion stand for two properties and satisfying either silences both.`,
12593
+ codes: [
12594
+ { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
12595
+ ],
12596
+ check(ctx) {
12597
+ for (const t of ctx.types) {
12598
+ const invariants = t.invariants ?? [];
12599
+ if (invariants.length === 0) continue;
12600
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
12601
+ const entityDraft = sub !== void 0 && isDraftSubsystem(sub);
12602
+ const seen = /* @__PURE__ */ new Set();
12603
+ for (const inv of invariants) {
12604
+ if (seen.has(inv.id)) {
12605
+ ctx.addIssue(
12606
+ "error",
12607
+ "DUPLICATE_INVARIANT_ID",
12608
+ `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
12609
+ t.id,
12610
+ entityDraft
12611
+ );
12612
+ }
12613
+ seen.add(inv.id);
12614
+ }
12615
+ }
12616
+ }
12617
+ };
12618
+ }
12619
+ });
12620
+
12621
+ // src/core/rules/wiring/invariant-ref.ts
11903
12622
  function splitInvariantRef(ref) {
11904
12623
  const at = ref.lastIndexOf(".");
11905
12624
  if (at <= 0 || at === ref.length - 1) return null;
@@ -11923,6 +12642,14 @@ function refMatchesInvariant(ref, type, invariantId) {
11923
12642
  if (!(type.invariants ?? []).some((inv) => inv.id === invariantId)) return false;
11924
12643
  return typeMatchesRef(type, parts.typeRef);
11925
12644
  }
12645
+ var init_invariant_ref = __esm({
12646
+ "src/core/rules/wiring/invariant-ref.ts"() {
12647
+ "use strict";
12648
+ init_models();
12649
+ }
12650
+ });
12651
+
12652
+ // src/core/rules/wiring/invariant-backing.ts
11926
12653
  function stepAsserts(impl, methodName, type, invariantId) {
11927
12654
  const method = impl.methods.find((m) => m.name === methodName);
11928
12655
  if (!method) return false;
@@ -11947,33 +12674,19 @@ var init_invariant_backing = __esm({
11947
12674
  "src/core/rules/wiring/invariant-backing.ts"() {
11948
12675
  "use strict";
11949
12676
  init_models();
12677
+ init_invariant_ref();
11950
12678
  invariantBackingRule = {
11951
12679
  name: "invariant-backing",
11952
- description: 'The invariant registry: entities may declare domain invariants (type.invariants), anchored through their componentClass. Every write-effect contract method of the owning component must carry a narrative step asserting each invariant (step.assertsInvariants: "<type-id>.<invariant-id>") \u2014 the same declared-and-backed shape as semantic guarantees. This is an HONEST lint over declarations: a green run means every write path visibly claims the invariant, never that the narrative or code actually enforces it. An invariant with no resolvable owner or no declared write path is unanchored; dangling assertion references are errors.',
12680
+ description: 'The invariant registry: entities may declare domain invariants (type.invariants), anchored through their componentClass. Every write-effect contract method of the owning component must carry a narrative step asserting each invariant (step.assertsInvariants: "<type-id>.<invariant-id>") \u2014 the same declared-and-backed shape as semantic guarantees. This is an HONEST lint over declarations: a green run means every write path visibly claims the invariant, never that the narrative or code actually enforces it. An invariant with no resolvable owner or no declared write path is unanchored, and nothing further is claimed about it.',
11953
12681
  codes: [
11954
- { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" },
11955
12682
  { code: "INVARIANT_UNANCHORED", defaultSeverity: "warning", summary: "An entity declares invariants but has no componentClass, its componentClass does not resolve, or the owning component declares no write-effect contract methods" },
11956
- { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" },
11957
- { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12683
+ { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
11958
12684
  ],
11959
12685
  check(ctx) {
11960
12686
  for (const t of ctx.types) {
11961
12687
  const invariants = t.invariants ?? [];
11962
12688
  if (invariants.length === 0) continue;
11963
12689
  const entityDraft = isEntityDraft(t, ctx);
11964
- const seen = /* @__PURE__ */ new Set();
11965
- for (const inv of invariants) {
11966
- if (seen.has(inv.id)) {
11967
- ctx.addIssue(
11968
- "error",
11969
- "DUPLICATE_INVARIANT_ID",
11970
- `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
11971
- t.id,
11972
- entityDraft
11973
- );
11974
- }
11975
- seen.add(inv.id);
11976
- }
11977
12690
  const comp = resolveComponentClass(t, ctx);
11978
12691
  if (!comp) {
11979
12692
  ctx.addIssue(
@@ -12015,6 +12728,24 @@ var init_invariant_backing = __esm({
12015
12728
  }
12016
12729
  }
12017
12730
  }
12731
+ }
12732
+ };
12733
+ }
12734
+ });
12735
+
12736
+ // src/core/rules/wiring/invariant-references.ts
12737
+ var invariantReferencesRule;
12738
+ var init_invariant_references = __esm({
12739
+ "src/core/rules/wiring/invariant-references.ts"() {
12740
+ "use strict";
12741
+ init_invariant_ref();
12742
+ invariantReferencesRule = {
12743
+ name: "invariant-references",
12744
+ description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere.",
12745
+ codes: [
12746
+ { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12747
+ ],
12748
+ check(ctx) {
12018
12749
  for (const impl of ctx.implementations) {
12019
12750
  const isDraftCtx = ctx.isImplementationDraft(impl);
12020
12751
  for (const method of impl.methods) {
@@ -12128,8 +12859,7 @@ var init_source_file_linkage = __esm({
12128
12859
  { code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
12129
12860
  ],
12130
12861
  check(ctx) {
12131
- const factsByPath = /* @__PURE__ */ new Map();
12132
- for (const facts of ctx.codeModel.files) factsByPath.set(pathKey(facts.path), facts);
12862
+ const code = ctx.codeIndex();
12133
12863
  const degradedTsFiles = ctx.codeModel.files.filter(
12134
12864
  (f) => f.status === "analyzed" && (f.language === "typescript" || f.language === "javascript") && f.analysisGrade !== "exact"
12135
12865
  );
@@ -12177,7 +12907,7 @@ var init_source_file_linkage = __esm({
12177
12907
  const key = pathKey(file);
12178
12908
  if (reported.has(key)) continue;
12179
12909
  reported.add(key);
12180
- const facts = factsByPath.get(key);
12910
+ const facts = code.factsAt(key);
12181
12911
  if (!facts || facts.status === "analyzed") continue;
12182
12912
  const isImplementationFile = !!impl.sourcePath && pathKey(impl.sourcePath) === key;
12183
12913
  const users = isImplementationFile ? [] : impl.methods.filter((m) => m.sourcePath && pathKey(m.sourcePath) === key).map((m) => m.name);
@@ -12227,14 +12957,7 @@ var init_method_realization = __esm({
12227
12957
  { code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" }
12228
12958
  ],
12229
12959
  check(ctx) {
12230
- const lookups = /* @__PURE__ */ new Map();
12231
- for (const facts of ctx.codeModel.files) {
12232
- lookups.set(pathKey(facts.path), {
12233
- declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
12234
- anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
12235
- facts
12236
- });
12237
- }
12960
+ const code = ctx.codeIndex();
12238
12961
  for (const impl of ctx.implementations) {
12239
12962
  const contract = ctx.interfaceMap.get(impl.contract);
12240
12963
  if (!contract) continue;
@@ -12249,11 +12972,11 @@ var init_method_realization = __esm({
12249
12972
  if (tier === "off") continue;
12250
12973
  const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
12251
12974
  if (!file) continue;
12252
- const lookup = lookups.get(pathKey(file));
12253
- if (!lookup || lookup.facts.status !== "analyzed") continue;
12975
+ const facts = code.factsAt(file);
12976
+ if (!facts || facts.status !== "analyzed") continue;
12254
12977
  const symbol = methodImpl?.symbol ?? method.name;
12255
- const inDeclared = lookup.declared.has(symbol);
12256
- const inAnchored = inDeclared || lookup.anchored.has(symbol);
12978
+ const inDeclared = code.declarationsAt(file).has(symbol);
12979
+ const inAnchored = inDeclared || code.anchorsAt(file).has(symbol);
12257
12980
  const realized = tier === "declared" ? inDeclared : inAnchored;
12258
12981
  if (realized) continue;
12259
12982
  const label = methodImpl?.symbol ? `"${method.name}" (symbol "${symbol}")` : `"${method.name}"`;
@@ -12261,7 +12984,7 @@ var init_method_realization = __esm({
12261
12984
  ctx.addIssue(
12262
12985
  "warning",
12263
12986
  "UNREALIZED_METHOD",
12264
- `Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${lookup.facts.analysisGrade}).${weakHint}`,
12987
+ `Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${facts.analysisGrade}).${weakHint}`,
12265
12988
  impl.id,
12266
12989
  draft
12267
12990
  );
@@ -12285,10 +13008,7 @@ var init_finding_realization = __esm({
12285
13008
  { code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" }
12286
13009
  ],
12287
13010
  check(ctx) {
12288
- const lookups = /* @__PURE__ */ new Map();
12289
- for (const facts of ctx.codeModel.files) {
12290
- lookups.set(pathKey(facts.path), { findingAnchors: new Set(facts.anchoredNames), facts });
12291
- }
13011
+ const code = ctx.codeIndex();
12292
13012
  for (const impl of ctx.implementations) {
12293
13013
  const contract = ctx.interfaceMap.get(impl.contract);
12294
13014
  if (!contract) continue;
@@ -12304,14 +13024,15 @@ var init_finding_realization = __esm({
12304
13024
  if (tier === "off") continue;
12305
13025
  const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
12306
13026
  if (!file) continue;
12307
- const lookup = lookups.get(pathKey(file));
12308
- if (!lookup || lookup.facts.status !== "analyzed") continue;
13027
+ const facts = code.factsAt(file);
13028
+ if (!facts || facts.status !== "analyzed") continue;
13029
+ const findingAnchors = code.findingAnchorsAt(file);
12309
13030
  for (const finding of method.findings) {
12310
- if (lookup.findingAnchors.has(finding.code)) continue;
13031
+ if (findingAnchors.has(finding.code)) continue;
12311
13032
  ctx.addIssue(
12312
13033
  "warning",
12313
13034
  "UNREALIZED_FINDING",
12314
- `Method "${method.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
13035
+ `Method "${method.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
12315
13036
  impl.id,
12316
13037
  draft
12317
13038
  );
@@ -12343,68 +13064,58 @@ function closedCallees(facts, fn) {
12343
13064
  }
12344
13065
  return closed;
12345
13066
  }
12346
- var callConformanceRule;
12347
- var init_call_conformance = __esm({
12348
- "src/core/rules/conformance/call-conformance.ts"() {
12349
- "use strict";
12350
- init_models();
12351
- callConformanceRule = {
12352
- name: "call-conformance",
12353
- description: "Code\u2194spec Level 3 (opener): every narrative `call` step of an exactly-analyzed method must be realized as a call in the realized function, read from the method's own source file (its sourcePath, else the implementation's) \u2014 the target method's contract name or its per-method symbol override must appear among the function's callees, closed transitively over same-file named helpers. Set membership only: order, arguments, and conditions are deliberately unverified, and dispatch steps (runtime-table routed) are skipped. Respects the conformance dial (off skips) and fires only at exact analysis grade \u2014 weaker grades never guess.",
12354
- codes: [
12355
- { code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step whose target method name (or symbol) never appears among the realized function's callees in the method's source file (exact grade, set membership)" }
12356
- ],
12357
- check(ctx) {
12358
- const factsByPath = /* @__PURE__ */ new Map();
12359
- for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
12360
- for (const impl of ctx.implementations) {
12361
- const contract = ctx.interfaceMap.get(impl.contract);
12362
- if (!contract) continue;
12363
- const component = ctx.componentMap.get(contract.component);
12364
- if (!component) continue;
12365
- if (ctx.isInChainedSubproject(component.subsystem)) continue;
12366
- const specTier = impl.conformance ?? defaultConformanceTier(component);
12367
- const isDraftCtx = ctx.isImplementationDraft(impl);
12368
- for (const implMethod of impl.methods) {
12369
- const tier = implMethod.conformance ?? specTier;
12370
- if (tier === "off") continue;
12371
- if (!implMethod.narrative.length) continue;
12372
- const file = methodSourceFile(implMethod, impl.sourcePath);
12373
- if (!file) continue;
12374
- const facts = factsByPath.get(pathKey(file));
12375
- if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12376
- const fnSymbol = implMethod.symbol ?? implMethod.name;
12377
- const callees = closedCallees(facts, fnSymbol);
12378
- if (!callees) continue;
12379
- const missing = [];
12380
- for (const step of implMethod.narrative) {
12381
- if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
12382
- if (!ctx.componentMap.has(step.targetComponent)) continue;
12383
- const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
12384
- for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
12385
- for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
12386
- const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
12387
- if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12388
- }
13067
+ var callConformanceRule;
13068
+ var init_call_conformance = __esm({
13069
+ "src/core/rules/conformance/call-conformance.ts"() {
13070
+ "use strict";
13071
+ init_models();
13072
+ callConformanceRule = {
13073
+ name: "call-conformance",
13074
+ description: "Code\u2194spec Level 3 (opener): every narrative `call` step of an exactly-analyzed method must be realized as a call in the realized function, read from the method's own source file (its sourcePath, else the implementation's) \u2014 the target method's contract name or its per-method symbol override must appear among the function's callees, closed transitively over same-file named helpers. Set membership only: order, arguments, and conditions are deliberately unverified, and dispatch steps (runtime-table routed) are skipped. Respects the conformance dial (off skips) and fires only at exact analysis grade \u2014 weaker grades never guess.",
13075
+ codes: [
13076
+ { code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step whose target method name (or symbol) never appears among the realized function's callees in the method's source file (exact grade, set membership)" }
13077
+ ],
13078
+ check(ctx) {
13079
+ const code = ctx.codeIndex();
13080
+ for (const entry of ctx.implementationMethods()) {
13081
+ const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
13082
+ const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
13083
+ if (tier === "off") continue;
13084
+ if (!implMethod.narrative.length) continue;
13085
+ if (!file) continue;
13086
+ const facts = code.factsAt(file);
13087
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
13088
+ const fnSymbol = implMethod.symbol ?? implMethod.name;
13089
+ const callees = closedCallees(facts, fnSymbol);
13090
+ if (!callees) continue;
13091
+ const missing = [];
13092
+ for (const step of implMethod.narrative) {
13093
+ if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
13094
+ if (!ctx.componentMap.has(step.targetComponent)) continue;
13095
+ const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
13096
+ for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
13097
+ for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
13098
+ const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
13099
+ if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12389
13100
  }
12390
- if (accepted.has(fnSymbol)) continue;
12391
- if ([...accepted].some((name) => callees.has(name))) continue;
12392
- missing.push({
12393
- step: step.stepNumber,
12394
- target: `${step.targetComponent}.${step.targetMethod}`,
12395
- accepted: [...accepted]
12396
- });
12397
13101
  }
12398
- if (missing.length === 0) continue;
12399
- const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
12400
- ctx.addIssue(
12401
- "warning",
12402
- "CALL_STEP_UNREALIZED",
12403
- `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.`,
12404
- impl.id,
12405
- isDraftCtx
12406
- );
13102
+ if (accepted.has(fnSymbol)) continue;
13103
+ if ([...accepted].some((name) => callees.has(name))) continue;
13104
+ missing.push({
13105
+ step: step.stepNumber,
13106
+ target: `${step.targetComponent}.${step.targetMethod}`,
13107
+ accepted: [...accepted]
13108
+ });
12407
13109
  }
13110
+ if (missing.length === 0) continue;
13111
+ const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
13112
+ ctx.addIssue(
13113
+ "warning",
13114
+ "CALL_STEP_UNREALIZED",
13115
+ `Method "${implMethod.name}" in implementation "${impl.id}": ${missing.length} narrative call step(s) are not realized as calls of the function "${fnSymbol}" in "${file}" \u2014 ${detail}. Callees are matched by name, closed over same-file helpers (exact grade, set membership \u2014 order and arguments are not checked). Realize the calls, fix the narrative, or map code names via per-method symbols on the targets.`,
13116
+ impl.id,
13117
+ entry.draftContext
13118
+ );
12408
13119
  }
12409
13120
  }
12410
13121
  };
@@ -12425,6 +13136,7 @@ var init_hidden_state = __esm({
12425
13136
  { code: "HIDDEN_STATE", defaultSeverity: "warning", summary: "Module-scope mutable binding in a file mapped only to stateless logic (Orchestrators) \u2014 held state hiding outside a Store, Supervisor or Actor" }
12426
13137
  ],
12427
13138
  check(ctx) {
13139
+ const code = ctx.codeIndex();
12428
13140
  const byPath = /* @__PURE__ */ new Map();
12429
13141
  for (const impl of ctx.implementations) {
12430
13142
  const contract = ctx.interfaceMap.get(impl.contract);
@@ -12446,11 +13158,11 @@ var init_hidden_state = __esm({
12446
13158
  byPath.set(key, list);
12447
13159
  }
12448
13160
  }
12449
- for (const facts of ctx.codeModel.files) {
12450
- if (facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
13161
+ for (const path27 of code.exactPaths) {
13162
+ const facts = code.factsAt(path27);
12451
13163
  const bindings = facts.topLevelMutableBindings ?? [];
12452
13164
  if (bindings.length === 0) continue;
12453
- const mapped = byPath.get(pathKey(facts.path)) ?? [];
13165
+ const mapped = byPath.get(path27) ?? [];
12454
13166
  if (mapped.length === 0) continue;
12455
13167
  if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
12456
13168
  const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
@@ -12482,54 +13194,21 @@ var init_dependency_conformance = __esm({
12482
13194
  { code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
12483
13195
  ],
12484
13196
  check(ctx) {
12485
- const factsByPath = new Map(ctx.codeModel.files.map((f) => [pathKey(f.path), f]));
12486
- const nodes = /* @__PURE__ */ new Map();
12487
- const filesByComponent = /* @__PURE__ */ new Map();
12488
- const implsByComponent = /* @__PURE__ */ new Map();
12489
- for (const impl of ctx.implementations) {
12490
- const contract = ctx.interfaceMap.get(impl.contract);
12491
- if (!contract) continue;
12492
- const component = ctx.componentMap.get(contract.component);
12493
- if (!component) continue;
12494
- if (ctx.isInChainedSubproject(component.subsystem)) continue;
12495
- let mapped = false;
12496
- for (const file of implementationSourceFiles(impl)) {
12497
- const p = pathKey(file);
12498
- const facts = factsByPath.get(p);
12499
- if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12500
- let node = nodes.get(p);
12501
- if (!node) {
12502
- node = { path: p, components: [], anchorImplId: impl.id, imports: facts.imports, reexports: facts.reexports, draft: false };
12503
- nodes.set(p, node);
12504
- }
12505
- if (!node.components.some((c) => c.id === component.id)) node.components.push(component);
12506
- node.draft = node.draft || ctx.isImplementationDraft(impl);
12507
- if (!filesByComponent.has(component.id)) filesByComponent.set(component.id, /* @__PURE__ */ new Set());
12508
- filesByComponent.get(component.id).add(p);
12509
- mapped = true;
12510
- }
12511
- if (mapped) {
12512
- if (!implsByComponent.has(component.id)) implsByComponent.set(component.id, []);
12513
- implsByComponent.get(component.id).push(impl);
12514
- }
12515
- }
12516
- const mappedPaths = new Set(nodes.keys());
12517
- const edges = /* @__PURE__ */ new Map();
12518
- const realizationEdges = /* @__PURE__ */ new Map();
12519
- for (const node of nodes.values()) {
12520
- const targets = /* @__PURE__ */ new Set();
12521
- for (const spec of node.imports) {
12522
- const resolved = resolveImport(node.path, spec, mappedPaths);
12523
- if (resolved && resolved !== node.path) targets.add(resolved);
12524
- }
12525
- edges.set(node.path, targets);
12526
- const realized = new Set(targets);
12527
- for (const spec of node.reexports) {
12528
- const resolved = resolveImport(node.path, spec, mappedPaths);
12529
- if (resolved && resolved !== node.path) realized.add(resolved);
12530
- }
12531
- realizationEdges.set(node.path, realized);
12532
- }
13197
+ const code = ctx.codeIndex();
13198
+ const realization = ctx.realizationIndex();
13199
+ const isExact = (p) => code.exactPaths.has(p);
13200
+ const mappedPaths = new Set(realization.paths.filter(isExact));
13201
+ const graph = ctx.importGraph(mappedPaths);
13202
+ const exactFiles = /* @__PURE__ */ new Map();
13203
+ const filesOf = (compId) => {
13204
+ let files = exactFiles.get(compId);
13205
+ if (!files) {
13206
+ files = realization.filesOf(compId).filter(isExact);
13207
+ exactFiles.set(compId, files);
13208
+ }
13209
+ return files;
13210
+ };
13211
+ const mappedImplsOf = (compId) => realization.implementationsOf(compId).filter((impl) => implementationSourceFiles(impl).some((f) => isExact(pathKey(f))));
12533
13212
  const dependsOrOwns = (from, toId) => from.dependsOn.includes(toId) || (from.owns ?? []).includes(toId);
12534
13213
  const ownerOf = /* @__PURE__ */ new Map();
12535
13214
  for (const c of ctx.components) {
@@ -12540,9 +13219,9 @@ var init_dependency_conformance = __esm({
12540
13219
  if (!published || published.size === 0) return false;
12541
13220
  return from.dependsOn.some((d) => published.has(d));
12542
13221
  };
12543
- const edgeJustified = (fromNode, toNode) => {
12544
- for (const cf of fromNode.components) {
12545
- for (const cg of toNode.components) {
13222
+ const edgeJustified = (from, to) => {
13223
+ for (const cf of from) {
13224
+ for (const cg of to) {
12546
13225
  if (cf.id === cg.id) return true;
12547
13226
  if (dependsOrOwns(cf, cg.id)) return true;
12548
13227
  if (cf.subsystem === cg.subsystem && dependsOrOwns(cg, cf.id) && (cg.componentType === "Portal" || cg.componentType === "Observer")) return true;
@@ -12556,60 +13235,35 @@ var init_dependency_conformance = __esm({
12556
13235
  }
12557
13236
  return false;
12558
13237
  };
12559
- for (const [fromPath, targets] of edges) {
12560
- const fromNode = nodes.get(fromPath);
12561
- for (const toPath of targets) {
12562
- const toNode = nodes.get(toPath);
12563
- if (edgeJustified(fromNode, toNode)) continue;
13238
+ const draftAt = (path27) => realization.implementationsAt(path27).some((impl) => ctx.isImplementationDraft(impl));
13239
+ for (const fromPath of mappedPaths) {
13240
+ const fromComponents = realization.componentsAt(fromPath);
13241
+ for (const toPath of graph.importsOf(fromPath)) {
13242
+ const toComponents = realization.componentsAt(toPath);
13243
+ if (edgeJustified(fromComponents, toComponents)) continue;
12564
13244
  ctx.addIssue(
12565
13245
  "warning",
12566
13246
  "UNDECLARED_DEPENDENCY",
12567
- `"${fromPath}" (realizing ${fromNode.components.map((c) => c.id).join(", ")}) imports "${toPath}" (realizing ${toNode.components.map((c) => c.id).join(", ")}) but no declared dependsOn/owns edge justifies it \u2014 declare the collaboration on the component that actually uses it, or route the cross-subsystem hop through the target's published surface.`,
12568
- fromNode.anchorImplId,
12569
- fromNode.draft || toNode.draft
13247
+ `"${fromPath}" (realizing ${fromComponents.map((c) => c.id).join(", ")}) imports "${toPath}" (realizing ${toComponents.map((c) => c.id).join(", ")}) but no declared dependsOn/owns edge justifies it \u2014 declare the collaboration on the component that actually uses it, or route the cross-subsystem hop through the target's published surface.`,
13248
+ realization.implementationsAt(fromPath)[0]?.id,
13249
+ draftAt(fromPath) || draftAt(toPath)
12570
13250
  );
12571
13251
  }
12572
13252
  }
12573
- const hasImportBetween = (fromFiles, toFiles, allowReverse) => {
12574
- for (const f of fromFiles) {
12575
- const targets = realizationEdges.get(f);
12576
- if (!targets) continue;
12577
- for (const t of toFiles) if (targets.has(t)) return true;
12578
- }
12579
- if (allowReverse) {
12580
- for (const t of toFiles) {
12581
- const reverse = realizationEdges.get(t);
12582
- if (!reverse) continue;
12583
- for (const f of fromFiles) if (reverse.has(f)) return true;
12584
- }
12585
- }
12586
- return false;
12587
- };
12588
13253
  for (const component of ctx.components) {
12589
- const fromFiles = filesByComponent.get(component.id);
12590
- if (!fromFiles) continue;
13254
+ const fromFiles = filesOf(component.id);
13255
+ if (fromFiles.length === 0) continue;
12591
13256
  const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
12592
13257
  const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
12593
13258
  for (const targetId of declaredTargets) {
12594
13259
  const target = ctx.componentMap.get(targetId);
12595
13260
  if (!target) continue;
12596
- if (component.subsystem !== target.subsystem) {
12597
- const subsystemFiles = /* @__PURE__ */ new Set();
12598
- for (const node of nodes.values()) {
12599
- if (node.components.some((c) => c.subsystem === target.subsystem)) subsystemFiles.add(node.path);
12600
- }
12601
- if (subsystemFiles.size === 0) continue;
12602
- for (const f of fromFiles) subsystemFiles.delete(f);
12603
- if (subsystemFiles.size === 0) continue;
12604
- if (hasImportBetween(fromFiles, subsystemFiles, isMountingDeclarer)) continue;
12605
- } else {
12606
- const toFiles = filesByComponent.get(targetId);
12607
- if (!toFiles) continue;
12608
- if ([...fromFiles].some((f) => toFiles.has(f))) continue;
12609
- if (hasImportBetween(fromFiles, toFiles, isMountingDeclarer)) continue;
12610
- }
12611
- const impls = implsByComponent.get(component.id) ?? [];
12612
- const anchor = impls[0];
13261
+ const crossSubsystem = component.subsystem !== target.subsystem;
13262
+ const toFiles = crossSubsystem ? realization.filesIn(target.subsystem).filter((p) => isExact(p) && !fromFiles.includes(p)) : filesOf(targetId);
13263
+ if (toFiles.length === 0) continue;
13264
+ if (fromFiles.some((f) => toFiles.includes(f))) continue;
13265
+ if (graph.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
13266
+ const impls = mappedImplsOf(component.id);
12613
13267
  const declaredDependsOn = component.dependsOn.includes(targetId);
12614
13268
  const declaredOwns = (component.owns ?? []).includes(targetId);
12615
13269
  const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
@@ -12617,8 +13271,8 @@ var init_dependency_conformance = __esm({
12617
13271
  ctx.addIssue(
12618
13272
  "warning",
12619
13273
  "UNREALIZED_DEPENDENCY",
12620
- `Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${[...fromFiles].join(", ")} \u219B ${target.subsystem !== component.subsystem ? `subsystem ${target.subsystem}` : [...filesByComponent.get(targetId) ?? []].join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
12621
- anchor?.id ?? component.id,
13274
+ `Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${fromFiles.join(", ")} \u219B ${crossSubsystem ? `subsystem ${target.subsystem}` : filesOf(targetId).join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
13275
+ impls[0]?.id ?? component.id,
12622
13276
  draft
12623
13277
  );
12624
13278
  }
@@ -12628,124 +13282,171 @@ var init_dependency_conformance = __esm({
12628
13282
  }
12629
13283
  });
12630
13284
 
12631
- // src/core/rules/conformance/integration-conformance.ts
12632
- var integrationConformanceRule;
12633
- var init_integration_conformance = __esm({
12634
- "src/core/rules/conformance/integration-conformance.ts"() {
13285
+ // src/core/rules/conformance/integration-sim-declaration.ts
13286
+ var integrationSimDeclarationRule;
13287
+ var init_integration_sim_declaration = __esm({
13288
+ "src/core/rules/conformance/integration-sim-declaration.ts"() {
12635
13289
  "use strict";
12636
- init_models();
12637
- integrationConformanceRule = {
12638
- name: "integration-conformance",
12639
- 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.",
13290
+ integrationSimDeclarationRule = {
13291
+ name: "integration-sim-declaration",
13292
+ description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim, and a subsystem that has adopted nothing is never flooded.",
12640
13293
  codes: [
12641
- { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" },
12642
- { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" },
12643
- { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" },
12644
- { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13294
+ { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
12645
13295
  ],
12646
13296
  check(ctx) {
12647
- const factsByPath = new Map(ctx.codeModel.files.map((f) => [pathKey(f.path), f]));
12648
- const allPaths = new Set(factsByPath.keys());
12649
- const filesByComponent = /* @__PURE__ */ new Map();
12650
- const filesBySubsystem = /* @__PURE__ */ new Map();
12651
- const implsByComponent = /* @__PURE__ */ new Map();
12652
13297
  const adopted = /* @__PURE__ */ new Set();
12653
- const exists = (p) => {
12654
- const status = factsByPath.get(p)?.status;
12655
- return status === "analyzed" || status === "unreadable";
12656
- };
12657
13298
  for (const impl of ctx.implementations) {
13299
+ if (!impl.simPath) continue;
12658
13300
  const contract = ctx.interfaceMap.get(impl.contract);
12659
13301
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12660
13302
  if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
12661
- if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
12662
- implsByComponent.get(comp.id).push(impl);
12663
- for (const file of implementationSourceFiles(impl)) {
12664
- const p = pathKey(file);
12665
- if (!exists(p)) continue;
12666
- if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
12667
- filesByComponent.get(comp.id).add(p);
12668
- if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
12669
- filesBySubsystem.get(comp.subsystem).add(p);
12670
- }
12671
- if (impl.simPath) adopted.add(comp.subsystem);
12672
- }
12673
- const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
12674
- const reachFrom = (start) => {
12675
- const seen = /* @__PURE__ */ new Set([start]);
12676
- const stack = [start];
12677
- while (stack.length) {
12678
- const from = stack.pop();
12679
- const facts = factsByPath.get(from);
12680
- if (!facts || facts.status !== "analyzed") continue;
12681
- for (const spec of [...facts.imports, ...facts.reexports]) {
12682
- const to = resolveImport(from, spec, allPaths);
12683
- if (to && !seen.has(to)) {
12684
- seen.add(to);
12685
- stack.push(to);
12686
- }
12687
- }
12688
- }
12689
- return seen;
12690
- };
13303
+ adopted.add(comp.subsystem);
13304
+ }
12691
13305
  for (const impl of ctx.implementations) {
13306
+ if (impl.simPath) continue;
12692
13307
  const contract = ctx.interfaceMap.get(impl.contract);
12693
13308
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12694
13309
  if (!comp) continue;
12695
13310
  if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13311
+ if (!adopted.has(comp.subsystem)) continue;
13312
+ if (ctx.isImplementationDraft(impl)) continue;
12696
13313
  const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
12697
- if (!impl.simPath) {
12698
- if (!adopted.has(comp.subsystem)) continue;
12699
- if (ctx.isImplementationDraft(impl)) continue;
12700
- if (deps.length === 0) continue;
12701
- ctx.addIssue(
12702
- "warning",
12703
- "MISSING_INTEGRATION_SIM",
12704
- `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.`,
12705
- impl.id
12706
- );
12707
- continue;
12708
- }
13314
+ if (deps.length === 0) continue;
13315
+ ctx.addIssue(
13316
+ "warning",
13317
+ "MISSING_INTEGRATION_SIM",
13318
+ `Implementation "${impl.id}" (component "${comp.id}") is complete with ${deps.length} declared dependenc${deps.length === 1 ? "y" : "ies"}, its subsystem "${comp.subsystem}" has adopted integration sims, but it declares no simPath \u2014 its unit suite proves contract shape against mocks, not that the wired components run together. Declare the committed harness that constructs it with its REAL dependencies (N:1 sharing allowed), or lint.allow with the reason.`,
13319
+ impl.id
13320
+ );
13321
+ }
13322
+ }
13323
+ };
13324
+ }
13325
+ });
13326
+
13327
+ // src/core/rules/conformance/integration-sim-file.ts
13328
+ var integrationSimFileRule;
13329
+ var init_integration_sim_file = __esm({
13330
+ "src/core/rules/conformance/integration-sim-file.ts"() {
13331
+ "use strict";
13332
+ init_models();
13333
+ integrationSimFileRule = {
13334
+ name: "integration-sim-file",
13335
+ description: "A declared L4 simPath must resolve to a readable, committed file inside the project root \u2014 the integration harness is a re-runnable file, not a claim. Execution stays CI's job.",
13336
+ codes: [
13337
+ { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
13338
+ ],
13339
+ check(ctx) {
13340
+ const code = ctx.codeIndex();
13341
+ for (const impl of ctx.implementations) {
13342
+ if (!impl.simPath) continue;
13343
+ const contract = ctx.interfaceMap.get(impl.contract);
13344
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13345
+ if (!comp) continue;
13346
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13347
+ const facts = code.factsAt(pathKey(impl.simPath));
13348
+ if (facts && facts.status === "analyzed") continue;
13349
+ const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
13350
+ ctx.addIssue(
13351
+ "warning",
13352
+ "SIM_FILE_MISSING",
13353
+ `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
13354
+ impl.id,
13355
+ ctx.isImplementationDraft(impl)
13356
+ );
13357
+ }
13358
+ }
13359
+ };
13360
+ }
13361
+ });
13362
+
13363
+ // src/core/rules/conformance/integration-sim-wiring.ts
13364
+ var integrationSimWiringRule;
13365
+ var init_integration_sim_wiring = __esm({
13366
+ "src/core/rules/conformance/integration-sim-wiring.ts"() {
13367
+ "use strict";
13368
+ init_models();
13369
+ integrationSimWiringRule = {
13370
+ name: "integration-sim-wiring",
13371
+ description: "A committed integration harness's import graph \u2014 closed transitively over the analyzed module set, exact grade only \u2014 must reach at least one of the component's own source modules (its implementation's or a method's) and at least one source module of each direct dependsOn/owns component (technology-boundary dependencies exempt: their contract-faithful fakes are sanctioned). Execution is CI's job \u2014 this proves wiring, never that the sim passes.",
13372
+ codes: [
13373
+ { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
13374
+ ],
13375
+ check(ctx) {
13376
+ const code = ctx.codeIndex();
13377
+ const realization = ctx.realizationIndex();
13378
+ const graph = ctx.importGraph();
13379
+ const exists = (p) => {
13380
+ const status = code.factsAt(p)?.status;
13381
+ return status === "analyzed" || status === "unreadable";
13382
+ };
13383
+ const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
13384
+ const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
13385
+ const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
13386
+ for (const impl of ctx.implementations) {
13387
+ if (!impl.simPath) continue;
13388
+ const contract = ctx.interfaceMap.get(impl.contract);
13389
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13390
+ if (!comp) continue;
13391
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
12709
13392
  const simPath = pathKey(impl.simPath);
12710
- const facts = factsByPath.get(simPath);
12711
- const isDraftCtx = ctx.isImplementationDraft(impl);
12712
- if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
12713
- 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";
12714
- ctx.addIssue(
12715
- "warning",
12716
- "SIM_FILE_MISSING",
12717
- `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
12718
- impl.id,
12719
- isDraftCtx
12720
- );
12721
- continue;
12722
- }
13393
+ const facts = code.factsAt(simPath);
13394
+ if (!facts || facts.status !== "analyzed") continue;
12723
13395
  if (facts.analysisGrade !== "exact") continue;
12724
- const reach = reachFrom(simPath);
12725
- const ownFiles = filesByComponent.get(comp.id) ?? /* @__PURE__ */ new Set();
13396
+ const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
13397
+ const reach = graph.reachFrom(simPath);
13398
+ const ownFiles = modulesOf(comp.id);
12726
13399
  const missing = [];
12727
- if (ownFiles.size > 0 && ![...ownFiles].some((f) => reach.has(f))) {
12728
- missing.push(`the component's own module (${[...ownFiles].join(" | ")})`);
13400
+ if (ownFiles.length > 0 && !ownFiles.some((f) => reach.has(f))) {
13401
+ missing.push(`the component's own module (${ownFiles.join(" | ")})`);
12729
13402
  }
12730
13403
  for (const dep of deps) {
12731
13404
  if (isTechBoundary(dep)) continue;
12732
13405
  const depComp = ctx.componentMap.get(dep);
12733
- const depFiles = depComp.subsystem !== comp.subsystem ? filesBySubsystem.get(depComp.subsystem) : filesByComponent.get(dep);
12734
- if (!depFiles || depFiles.size === 0) continue;
12735
- if (![...depFiles].some((f) => reach.has(f))) {
12736
- const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${[...depFiles].join(" | ")})`;
13406
+ const depFiles = depComp.subsystem !== comp.subsystem ? modulesIn(depComp.subsystem) : modulesOf(dep);
13407
+ if (depFiles.length === 0) continue;
13408
+ if (!depFiles.some((f) => reach.has(f))) {
13409
+ const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${depFiles.join(" | ")})`;
12737
13410
  missing.push(label);
12738
13411
  }
12739
13412
  }
12740
- if (missing.length) {
12741
- ctx.addIssue(
12742
- "warning",
12743
- "UNWIRED_INTEGRATION_SIM",
12744
- `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.`,
12745
- impl.id,
12746
- isDraftCtx
12747
- );
12748
- }
13413
+ if (missing.length === 0) continue;
13414
+ ctx.addIssue(
13415
+ "warning",
13416
+ "UNWIRED_INTEGRATION_SIM",
13417
+ `Sim "${impl.simPath}" of implementation "${impl.id}" does not reach ${missing.join(", nor ")} through its import graph (closed over the analyzed modules, exact grade) \u2014 the harness is not wiring the real implementations it claims to exercise. Import the real modules (technology-boundary adapters may stay contract-faithfully faked), or fix the simPath.`,
13418
+ impl.id,
13419
+ ctx.isImplementationDraft(impl)
13420
+ );
13421
+ }
13422
+ }
13423
+ };
13424
+ }
13425
+ });
13426
+
13427
+ // src/core/rules/conformance/integration-sim-coverage.ts
13428
+ var integrationSimCoverageRule;
13429
+ var init_integration_sim_coverage = __esm({
13430
+ "src/core/rules/conformance/integration-sim-coverage.ts"() {
13431
+ "use strict";
13432
+ init_models();
13433
+ integrationSimCoverageRule = {
13434
+ name: "integration-sim-coverage",
13435
+ description: "Once a committed integration harness carries sim:<component-id>. anchors, that component claims path coverage: every narrated method's happy path and every labeled throw step needs its exact sim:<component>.<method>[:<label>] string-literal anchor. Anchors prove the path is NAMED \u2014 assertion quality and execution stay CI's job.",
13436
+ codes: [
13437
+ { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13438
+ ],
13439
+ check(ctx) {
13440
+ const code = ctx.codeIndex();
13441
+ for (const impl of ctx.implementations) {
13442
+ if (!impl.simPath) continue;
13443
+ const contract = ctx.interfaceMap.get(impl.contract);
13444
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13445
+ if (!comp) continue;
13446
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13447
+ const facts = code.factsAt(pathKey(impl.simPath));
13448
+ if (!facts || facts.status !== "analyzed") continue;
13449
+ if (facts.analysisGrade !== "exact") continue;
12749
13450
  const simAnchors = new Set(facts.anchoredNames.filter((a) => a.startsWith("sim:")));
12750
13451
  if (simAnchors.size === 0) continue;
12751
13452
  const compPrefix = `sim:${comp.id}.`;
@@ -12766,15 +13467,14 @@ var init_integration_conformance = __esm({
12766
13467
  }
12767
13468
  }
12768
13469
  }
12769
- if (uncovered.length) {
12770
- ctx.addIssue(
12771
- "warning",
12772
- "SIM_PATH_UNCOVERED",
12773
- `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.`,
12774
- impl.id,
12775
- isDraftCtx
12776
- );
12777
- }
13470
+ if (uncovered.length === 0) continue;
13471
+ ctx.addIssue(
13472
+ "warning",
13473
+ "SIM_PATH_UNCOVERED",
13474
+ `Sim "${impl.simPath}" declares path coverage for component "${comp.id}" (it carries sim: anchors) but does not name ${uncovered.join(", nor ")}. Drive the path and anchor it with the exact string literal, or drop the component's sim: anchors to withdraw the coverage claim. Unlabeled throw steps are not expected \u2014 a step's label is the path's identity. Anchors prove the path is NAMED; assertion quality and execution stay CI's job.`,
13475
+ impl.id,
13476
+ ctx.isImplementationDraft(impl)
13477
+ );
12778
13478
  }
12779
13479
  }
12780
13480
  };
@@ -12884,10 +13584,10 @@ var init_coupling_health = __esm({
12884
13584
  }
12885
13585
  });
12886
13586
 
12887
- // src/core/rules/heuristic/target-language.ts
12888
- var LANGUAGE_MARKERS, UNSUPPORTED_FLOW, languageRule;
12889
- var init_target_language = __esm({
12890
- "src/core/rules/heuristic/target-language.ts"() {
13587
+ // src/core/rules/heuristic/signature-language-builtins.ts
13588
+ var LANGUAGE_MARKERS, signatureLanguageBuiltinsRule;
13589
+ var init_signature_language_builtins = __esm({
13590
+ "src/core/rules/heuristic/signature-language-builtins.ts"() {
12891
13591
  "use strict";
12892
13592
  init_models();
12893
13593
  LANGUAGE_MARKERS = {
@@ -12922,6 +13622,68 @@ var init_target_language = __esm({
12922
13622
  csharp: /* @__PURE__ */ new Set(["task"]),
12923
13623
  go: /* @__PURE__ */ new Set(["chan", "rune"])
12924
13624
  };
13625
+ signatureLanguageBuiltinsRule = {
13626
+ name: "signature-language-builtins",
13627
+ description: "Contracts must speak the declared target language: builtin types that unambiguously belong to another language family are flagged in method signatures (e.g. `usize` in a TypeScript system, `Promise` in a Rust one). Set targetLanguage on the system (L0) or override per subsystem (L1).",
13628
+ codes: [
13629
+ { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
13630
+ ],
13631
+ check(ctx) {
13632
+ const markersFor = (family) => {
13633
+ const base = LANGUAGE_MARKERS[family];
13634
+ const extra = ctx.ext.languages[family]?.foreignBuiltins;
13635
+ if (!extra?.length) return base;
13636
+ return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
13637
+ };
13638
+ const families = [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)])];
13639
+ const scans = [];
13640
+ for (const intf of ctx.interfaces) {
13641
+ const comp = ctx.componentMap.get(intf.component);
13642
+ const lang = ctx.targetLanguageFor(comp?.subsystem);
13643
+ if (!lang) continue;
13644
+ const ownMarkers = markersFor(lang);
13645
+ if (!ownMarkers || ownMarkers.size === 0) continue;
13646
+ const draft = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
13647
+ for (const method of intf.methods) {
13648
+ for (const ref of methodTypeRefs(method)) scans.push({ intf, method, ref, lang, ownMarkers, draft });
13649
+ }
13650
+ }
13651
+ for (const scan of scans) {
13652
+ const refLower = scan.ref.toLowerCase();
13653
+ if (scan.ownMarkers.has(refLower)) continue;
13654
+ const foreign = families.find((family) => family !== scan.lang && markersFor(family)?.has(refLower));
13655
+ if (!foreign) continue;
13656
+ ctx.addIssue(
13657
+ "warning",
13658
+ "LANGUAGE_FOREIGN_BUILTIN",
13659
+ `Method "${scan.method.name}" on interface "${scan.intf.id}" uses "${scan.ref}", a ${foreign} builtin, but the target language here is ${scan.lang}. Use the ${scan.lang} equivalent so implementers generate idiomatic code.`,
13660
+ scan.intf.id,
13661
+ scan.draft
13662
+ );
13663
+ }
13664
+ }
13665
+ };
13666
+ }
13667
+ });
13668
+
13669
+ // src/core/rules/heuristic/narrative-language-constructs.ts
13670
+ function constructsOf(step) {
13671
+ const constructs = [
13672
+ step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13673
+ ];
13674
+ if (step.detach) constructs.push("detach");
13675
+ return constructs;
13676
+ }
13677
+ function constructLabel(construct) {
13678
+ if (construct === "doWhile") return "a do-while loop";
13679
+ if (construct === "forEach" || construct === "for" || construct === "while") return `a ${construct} loop`;
13680
+ if (construct === "detach") return "a detached (fire-and-forget) call";
13681
+ return `a ${construct} step`;
13682
+ }
13683
+ var UNSUPPORTED_FLOW, narrativeLanguageConstructsRule;
13684
+ var init_narrative_language_constructs = __esm({
13685
+ "src/core/rules/heuristic/narrative-language-constructs.ts"() {
13686
+ "use strict";
12925
13687
  UNSUPPORTED_FLOW = {
12926
13688
  rust: {
12927
13689
  try: "Rust models errors as values (Result + ?) \u2014 specify the guarded logic as explicit error branches, or note the Result mapping in the step description",
@@ -12941,53 +13703,18 @@ var init_target_language = __esm({
12941
13703
  doWhile: "Python has no do-while \u2014 prefer loopKind: while, or state the emulation (while True + break)"
12942
13704
  }
12943
13705
  };
12944
- languageRule = {
12945
- name: "target-language",
12946
- description: "Contracts must speak the declared target language: builtin types that unambiguously belong to another language family are flagged in method signatures, and narrative flow steps using constructs the language lacks (e.g. exceptions in Rust/Go, do-while in Python) are flagged too. Set targetLanguage on the system (L0) or override per subsystem (L1).",
13706
+ narrativeLanguageConstructsRule = {
13707
+ name: "narrative-language-constructs",
13708
+ description: "Narratives must describe flows the declared target language can express: steps using constructs the language lacks (e.g. exceptions in Rust/Go, do-while in Python) are flagged with the idiomatic alternative. Set targetLanguage on the system (L0) or override per subsystem (L1).",
12947
13709
  codes: [
12948
- { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" },
12949
13710
  { code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
12950
13711
  ],
12951
13712
  check(ctx) {
12952
- const markersFor = (family) => {
12953
- const base = LANGUAGE_MARKERS[family];
12954
- const extra = ctx.ext.languages[family]?.foreignBuiltins;
12955
- if (!extra?.length) return base;
12956
- return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
12957
- };
12958
- const families = /* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)]);
12959
13713
  const gapsFor = (lang) => ({
12960
13714
  ...UNSUPPORTED_FLOW[lang] ?? {},
12961
13715
  ...ctx.ext.languages[lang]?.unsupportedFlow ?? {}
12962
13716
  });
12963
- for (const intf of ctx.interfaces) {
12964
- const comp = ctx.componentMap.get(intf.component);
12965
- const lang = ctx.targetLanguageFor(comp?.subsystem);
12966
- if (!lang) continue;
12967
- const ownMarkers = markersFor(lang);
12968
- if (!ownMarkers || ownMarkers.size === 0) continue;
12969
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
12970
- for (const m of intf.methods) {
12971
- const refs = methodTypeRefs(m);
12972
- for (const ref of refs) {
12973
- const refLower = ref.toLowerCase();
12974
- if (ownMarkers.has(refLower)) continue;
12975
- for (const family of families) {
12976
- if (family === lang) continue;
12977
- if (markersFor(family)?.has(refLower)) {
12978
- ctx.addIssue(
12979
- "warning",
12980
- "LANGUAGE_FOREIGN_BUILTIN",
12981
- `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.`,
12982
- intf.id,
12983
- isDraftCtx
12984
- );
12985
- break;
12986
- }
12987
- }
12988
- }
12989
- }
12990
- }
13717
+ const scans = [];
12991
13718
  for (const impl of ctx.implementations) {
12992
13719
  const contract = ctx.interfaceMap.get(impl.contract);
12993
13720
  if (!contract) continue;
@@ -12996,26 +13723,22 @@ var init_target_language = __esm({
12996
13723
  if (!lang) continue;
12997
13724
  const gaps = gapsFor(lang);
12998
13725
  if (Object.keys(gaps).length === 0) continue;
12999
- const isDraftCtx = ctx.isImplementationDraft(impl);
13000
- for (const implMethod of impl.methods) {
13001
- for (const step of implMethod.narrative) {
13002
- const constructs = [
13003
- step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13004
- ];
13005
- if (step.detach) constructs.push("detach");
13006
- for (const construct of constructs) {
13007
- const guidance = gaps[construct];
13008
- if (!guidance) continue;
13009
- 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`;
13010
- ctx.addIssue(
13011
- "warning",
13012
- "LANGUAGE_FOREIGN_FLOW",
13013
- `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" uses ${label}, but the target language is ${lang}: ${guidance}.`,
13014
- impl.id,
13015
- isDraftCtx
13016
- );
13017
- }
13018
- }
13726
+ const draft = ctx.isImplementationDraft(impl);
13727
+ for (const method of impl.methods) {
13728
+ for (const step of method.narrative) scans.push({ implementation: impl, method, step, lang, gaps, draft });
13729
+ }
13730
+ }
13731
+ for (const scan of scans) {
13732
+ for (const construct of constructsOf(scan.step)) {
13733
+ const guidance = scan.gaps[construct];
13734
+ if (!guidance) continue;
13735
+ ctx.addIssue(
13736
+ "warning",
13737
+ "LANGUAGE_FOREIGN_FLOW",
13738
+ `Step ${scan.step.stepNumber} of "${scan.method.name}" in implementation "${scan.implementation.id}" uses ${constructLabel(construct)}, but the target language is ${scan.lang}: ${guidance}.`,
13739
+ scan.implementation.id,
13740
+ scan.draft
13741
+ );
13019
13742
  }
13020
13743
  }
13021
13744
  }
@@ -13023,6 +13746,36 @@ var init_target_language = __esm({
13023
13746
  }
13024
13747
  });
13025
13748
 
13749
+ // src/core/rules/heuristic/technology-binding.ts
13750
+ var DATA_LAYER, technologyBindingRule;
13751
+ var init_technology_binding = __esm({
13752
+ "src/core/rules/heuristic/technology-binding.ts"() {
13753
+ "use strict";
13754
+ DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13755
+ technologyBindingRule = {
13756
+ name: "technology-binding",
13757
+ description: "Only data-layer stereotypes (Adapter/Store/Registry/Index) should bind a technology directly: an L4 that declares `technologies` on logic has no swap seam. No hardcoded vendor lists \u2014 only declared tokens are policed, so the rule never fires on a tree that doesn't opt in.",
13758
+ codes: [
13759
+ { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13760
+ ],
13761
+ check(ctx) {
13762
+ for (const impl of ctx.implementations) {
13763
+ const intf = ctx.interfaceMap.get(impl.contract);
13764
+ const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13765
+ if (!impl.technologies?.length || !comp || DATA_LAYER.has(comp.componentType)) continue;
13766
+ ctx.addIssue(
13767
+ "warning",
13768
+ "TECH_ON_LOGIC_COMPONENT",
13769
+ `Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
13770
+ impl.id,
13771
+ ctx.isImplementationDraft(impl)
13772
+ );
13773
+ }
13774
+ }
13775
+ };
13776
+ }
13777
+ });
13778
+
13026
13779
  // src/core/rules/heuristic/technology-boundaries.ts
13027
13780
  function makeMatcher(tech) {
13028
13781
  const parts = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
@@ -13046,19 +13799,29 @@ function contractIdentifiers(m) {
13046
13799
  if (m.endpoint) parts.push(Object.values(m.endpoint).map(String).join(" "));
13047
13800
  return parts.join(" ");
13048
13801
  }
13049
- var DATA_LAYER, technologyRule;
13802
+ function implementationText(impl) {
13803
+ const parts = [impl.description, ...implementationSourceFiles(impl)];
13804
+ for (const m of impl.methods) {
13805
+ parts.push(m.intent);
13806
+ for (const s of m.narrative ?? []) {
13807
+ parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13808
+ for (const c of s.cases ?? []) parts.push(c.value);
13809
+ for (const c of s.catches ?? []) parts.push(c.error);
13810
+ }
13811
+ }
13812
+ return parts.filter(Boolean).join(" ");
13813
+ }
13814
+ var technologyRule;
13050
13815
  var init_technology_boundaries = __esm({
13051
13816
  "src/core/rules/heuristic/technology-boundaries.ts"() {
13052
13817
  "use strict";
13053
13818
  init_models();
13054
- DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13055
13819
  technologyRule = {
13056
13820
  name: "technology-boundaries",
13057
- description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, L3 contract identifiers must stay intent-language (the contract is the swap seam), and only data-layer stereotypes (Adapter/Store/Registry/Index) should bind a technology directly.",
13821
+ description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface.",
13058
13822
  codes: [
13059
13823
  { code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
13060
- { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" },
13061
- { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13824
+ { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
13062
13825
  ],
13063
13826
  check(ctx) {
13064
13827
  const ownerOf = /* @__PURE__ */ new Map();
@@ -13086,21 +13849,18 @@ var init_technology_boundaries = __esm({
13086
13849
  }
13087
13850
  return out;
13088
13851
  };
13089
- const homes = /* @__PURE__ */ new Map();
13852
+ const declarations = [];
13090
13853
  for (const impl of ctx.implementations) {
13091
13854
  if (!impl.technologies?.length) continue;
13092
13855
  const intf = ctx.interfaceMap.get(impl.contract);
13093
13856
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13094
13857
  if (!comp) continue;
13095
- if (!DATA_LAYER.has(comp.componentType)) {
13096
- ctx.addIssue(
13097
- "warning",
13098
- "TECH_ON_LOGIC_COMPONENT",
13099
- `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.`,
13100
- impl.id,
13101
- ctx.isImplementationDraft(impl)
13102
- );
13103
- }
13858
+ for (const tech of impl.technologies) declarations.push({ comp, tech });
13859
+ }
13860
+ const scopes = /* @__PURE__ */ new Map();
13861
+ const scopeOf = (comp) => {
13862
+ const cached = scopes.get(comp.id);
13863
+ if (cached) return cached;
13104
13864
  const compSet = ownsClosure(ownershipRoot(comp.id));
13105
13865
  const scope = new Set(compSet);
13106
13866
  for (const i of ctx.interfaces) if (compSet.has(i.component)) scope.add(i.id);
@@ -13115,15 +13875,18 @@ var init_technology_boundaries = __esm({
13115
13875
  if (subId === s.id || subId.startsWith(`${s.id}::`)) scope.add(s.id);
13116
13876
  }
13117
13877
  }
13118
- for (const tech of impl.technologies) {
13119
- const match = makeMatcher(tech);
13120
- if (!match) continue;
13121
- const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13122
- const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13123
- home.ownerComponents.add(comp.id);
13124
- for (const id of scope) home.scope.add(id);
13125
- homes.set(key, home);
13126
- }
13878
+ scopes.set(comp.id, scope);
13879
+ return scope;
13880
+ };
13881
+ const homes = /* @__PURE__ */ new Map();
13882
+ for (const { comp, tech } of declarations) {
13883
+ const match = makeMatcher(tech);
13884
+ if (!match) continue;
13885
+ const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13886
+ const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13887
+ home.ownerComponents.add(comp.id);
13888
+ for (const id of scopeOf(comp)) home.scope.add(id);
13889
+ homes.set(key, home);
13127
13890
  }
13128
13891
  if (homes.size === 0) return;
13129
13892
  const ownersDesc = (h) => [...h.ownerComponents].map((c) => `"${c}"`).join(", ");
@@ -13187,16 +13950,7 @@ var init_technology_boundaries = __esm({
13187
13950
  }
13188
13951
  for (const impl of ctx.implementations) {
13189
13952
  if (home.scope.has(impl.id)) continue;
13190
- const parts = [impl.description, ...implementationSourceFiles(impl)];
13191
- for (const m of impl.methods) {
13192
- parts.push(m.intent);
13193
- for (const s of m.narrative ?? []) {
13194
- parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13195
- for (const c of s.cases ?? []) parts.push(c.value);
13196
- for (const c of s.catches ?? []) parts.push(c.error);
13197
- }
13198
- }
13199
- if (home.match(parts.filter(Boolean).join(" "))) {
13953
+ if (home.match(implementationText(impl))) {
13200
13954
  ctx.addIssue(
13201
13955
  "warning",
13202
13956
  "TECH_LEAKAGE",
@@ -13885,12 +14639,15 @@ var init_repository = __esm({
13885
14639
  "src/core/rules/repository.ts"() {
13886
14640
  "use strict";
13887
14641
  init_hierarchy_integrity();
13888
- init_namespace_hygiene();
14642
+ init_reserved_id_segments();
14643
+ init_namespace_shadowing();
13889
14644
  init_roundtrip_serialization();
13890
14645
  init_type_declarations();
13891
14646
  init_field_type_references();
13892
14647
  init_signature_type_references();
13893
- init_public_surface();
14648
+ init_public_surface_binding();
14649
+ init_public_surface_declared_type();
14650
+ init_public_surface_bound_contract();
13894
14651
  init_lint_allows();
13895
14652
  init_contract_symmetry();
13896
14653
  init_narrative_target_references();
@@ -13900,23 +14657,37 @@ var init_repository = __esm({
13900
14657
  init_narrative_step_config();
13901
14658
  init_narrative_reachability();
13902
14659
  init_narrative_jump_edges();
13903
- init_narrative_antipatterns();
14660
+ init_meaningless_branches();
14661
+ init_inescapable_cycles();
14662
+ init_unconditional_call_cycles();
13904
14663
  init_narrative_detail();
14664
+ init_detail_sufficiency();
13905
14665
  init_portal_fields();
13906
14666
  init_durability_declaration();
13907
14667
  init_logic_declaration();
13908
14668
  init_retired_stereotypes();
13909
14669
  init_portal_endpoints();
14670
+ init_non_portal_endpoints();
13910
14671
  init_portal_call_auth();
13911
- init_stereotype_dependencies();
13912
- init_pattern_ownership();
14672
+ init_auth_source_wiring();
14673
+ init_subsystem_boundary_dependencies();
14674
+ init_logic_dependency_class();
14675
+ init_data_block_dependencies();
14676
+ init_entrypoint_dependencies();
14677
+ init_portal_write_shortcut();
14678
+ init_pattern_membership();
14679
+ init_pattern_containment();
14680
+ init_unowned_blocks();
14681
+ init_member_visibility();
13913
14682
  init_facade_forwarding();
13914
14683
  init_profile_registration();
13915
14684
  init_profile_stereotype_fencing();
13916
14685
  init_pack_profile_stereotypes();
13917
14686
  init_pattern_references();
13918
14687
  init_component_variants();
13919
- init_declarative_assertions();
14688
+ init_assertion_forbidden_edges();
14689
+ init_assertion_required_fields();
14690
+ init_assertion_endpoint_shapes();
13920
14691
  init_pack_resolution();
13921
14692
  init_pack_reproducibility();
13922
14693
  init_dependency_cycles();
@@ -13924,10 +14695,14 @@ var init_repository = __esm({
13924
14695
  init_dispatch_step_routing();
13925
14696
  init_lifecycle_entrypoints();
13926
14697
  init_unused_detection();
14698
+ init_invoked_by_description();
14699
+ init_unused_types();
13927
14700
  init_durability_round_trip();
13928
14701
  init_untyped_seams();
13929
14702
  init_prose_claims();
14703
+ init_unique_invariant_ids();
13930
14704
  init_invariant_backing();
14705
+ init_invariant_references();
13931
14706
  init_event_topology();
13932
14707
  init_source_file_linkage();
13933
14708
  init_method_realization();
@@ -13935,9 +14710,14 @@ var init_repository = __esm({
13935
14710
  init_call_conformance();
13936
14711
  init_hidden_state();
13937
14712
  init_dependency_conformance();
13938
- init_integration_conformance();
14713
+ init_integration_sim_declaration();
14714
+ init_integration_sim_file();
14715
+ init_integration_sim_wiring();
14716
+ init_integration_sim_coverage();
13939
14717
  init_coupling_health();
13940
- init_target_language();
14718
+ init_signature_language_builtins();
14719
+ init_narrative_language_constructs();
14720
+ init_technology_binding();
13941
14721
  init_technology_boundaries();
13942
14722
  init_naming_conventions();
13943
14723
  init_complexity_and_metadata();
@@ -13947,8 +14727,11 @@ var init_repository = __esm({
13947
14727
  SDD_RULES = [
13948
14728
  hierarchyRule,
13949
14729
  // Namespace integrity right after hierarchy: unresolvable/unwritable ids
13950
- // explain many downstream findings, so surface them early in the list.
13951
- namespaceHygieneRule,
14730
+ // explain many downstream findings, so surface them early in the list. Two
14731
+ // questions of the same ids: the segment no id may spend, and the local name
14732
+ // that would anchor a bare reference to the root instead.
14733
+ reservedIdSegmentsRule,
14734
+ namespaceShadowingRule,
13952
14735
  roundtripRule,
13953
14736
  // The type vocabulary in three questions: what a type declares about
13954
14737
  // itself, then the identifiers its fields name, then the ones its
@@ -13974,20 +14757,48 @@ var init_repository = __esm({
13974
14757
  narrativeStepConfigRule,
13975
14758
  narrativeReachabilityRule,
13976
14759
  narrativeJumpEdgesRule,
13977
- // Antipatterns right after flow soundness: they analyze the same step
13978
- // graphs and only make sense once the graphs are structurally valid.
13979
- narrativeAntipatternsRule,
14760
+ // Antipatterns right after flow soundness: they read the same step graphs
14761
+ // and only make sense once the graphs are structurally valid. Three things
14762
+ // structure alone can prove, in widening scope: a decision that decides
14763
+ // nothing, a step cycle nothing leaves, and a call cycle with no guard.
14764
+ meaninglessBranchesRule,
14765
+ inescapableCyclesRule,
14766
+ unconditionalCallCyclesRule,
14767
+ // The detail dial in two questions: does a method carry the detail its level
14768
+ // promises, and is that level low enough to be hiding something.
13980
14769
  narrativeDetailRule,
14770
+ detailSufficiencyRule,
13981
14771
  // Field shape before endpoint bindings: a Portal-only field on the wrong
13982
14772
  // stereotype explains the endpoint findings around it, and this half is
13983
14773
  // spec-scoped so the write boundary refuses it first.
13984
14774
  portalFieldsRule,
14775
+ // Endpoints from both sides: a Portal binds every method to its transport,
14776
+ // and nothing that is not a Portal may bind one at all.
13985
14777
  portalsRule,
13986
- // Cross-call auth: a narrative call into an authed Portal must name its
13987
- // credential source (rides with the portal family).
14778
+ nonPortalEndpointsRule,
14779
+ // Cross-call auth rides with the portal family, in two questions: does a
14780
+ // call into an authed Portal present a credential properly, and does the
14781
+ // source it names resolve to a provider it is wired to.
13988
14782
  portalCallAuthRule,
13989
- stereotypeDepsRule,
13990
- patternsRule,
14783
+ authSourceWiringRule,
14784
+ // Dependencies in five questions, one owner each: where an edge is
14785
+ // allowed to LAND (the boundary, and what an unresolved id means), then
14786
+ // the intra-subsystem matrix by the layer that answers for it — an
14787
+ // Orchestrator's declared class, the data blocks, the entry points and
14788
+ // the process layer — and finally the Portal read-face guard, the one
14789
+ // that reads narratives and dispatch tables rather than dependsOn.
14790
+ subsystemBoundaryDepsRule,
14791
+ logicDependencyClassRule,
14792
+ dataBlockDepsRule,
14793
+ entrypointDepsRule,
14794
+ portalWriteShortcutRule,
14795
+ // Patterns in four questions: who may own and what a claim must name,
14796
+ // what each pattern must contain, which data blocks are left standing
14797
+ // alone, and who may see a private member.
14798
+ patternMembershipRule,
14799
+ patternContainmentRule,
14800
+ unownedBlocksRule,
14801
+ memberVisibilityRule,
13991
14802
  // Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
13992
14803
  facadeForwardingRule,
13993
14804
  // Profiles in three questions, three owners: is the name real (the
@@ -13999,9 +14810,18 @@ var init_repository = __esm({
13999
14810
  patternReferencesRule,
14000
14811
  variantReferencesRule,
14001
14812
  // Pack-instantiated declarative doctrine rides with the pack-reference
14002
- // family: same data source, same provenance-bearing findings.
14003
- declarativeAssertionsRule,
14004
- publicSurfaceRule,
14813
+ // family: same data source, same provenance-bearing findings. One rule per
14814
+ // assertion kind — the kind is what a pack author writes, and each kind
14815
+ // asks its own question of its own collection.
14816
+ assertionForbiddenEdgesRule,
14817
+ assertionRequiredFieldsRule,
14818
+ assertionEndpointShapesRule,
14819
+ // The published surface in three questions: what backs the entry, whether
14820
+ // that component's stereotype can realize the type it declares, and whether
14821
+ // the contract it binds is that component's own.
14822
+ publicSurfaceBindingRule,
14823
+ publicSurfaceDeclaredTypeRule,
14824
+ publicSurfaceBoundContractRule,
14005
14825
  cyclesRule,
14006
14826
  // Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
14007
14827
  // reader sees the broken edge finding next to the unused-detection fallout
@@ -14009,7 +14829,11 @@ var init_repository = __esm({
14009
14829
  dispatchTableBindingsRule,
14010
14830
  dispatchStepRoutingRule,
14011
14831
  lifecycleRule,
14832
+ // The declared entrypoint's own prose before the walk that its declaration
14833
+ // silences, then the walk, then the types no walk can reach.
14834
+ invokedByDescriptionRule,
14012
14835
  reachabilityRule,
14836
+ unusedTypesRule,
14013
14837
  // The declaration (spec-scoped, refused at the write boundary) before the
14014
14838
  // round-trip consequences it enables.
14015
14839
  durabilityDeclarationRule,
@@ -14021,8 +14845,13 @@ var init_repository = __esm({
14021
14845
  untypedSeamRule,
14022
14846
  proseClaimRule,
14023
14847
  // Invariant registry rides with the semantic-edge family: declared entity
14024
- // invariants must be asserted on every write path (declarations, not proofs).
14848
+ // invariants must be asserted on every write path (declarations, not
14849
+ // proofs). Three questions of one registry: are the entity's ids unique,
14850
+ // does each invariant reach every write path of its owner, and does every
14851
+ // asserted reference name something declared.
14852
+ uniqueInvariantIdsRule,
14025
14853
  invariantBackingRule,
14854
+ invariantReferencesRule,
14026
14855
  // Pub/sub completeness: emitted topics need subscribers and vice versa.
14027
14856
  eventTopologyRule,
14028
14857
  // Code↔spec: structural conformance consumes the injected CodeModel (built
@@ -14041,10 +14870,22 @@ var init_repository = __esm({
14041
14870
  hiddenStateRule,
14042
14871
  dependencyConformanceRule,
14043
14872
  // Integration wiring proof rides after the code↔spec family: it consumes
14044
- // the same code model and speaks about the same sourcePath modules.
14045
- integrationConformanceRule,
14873
+ // the same code model and speaks about the same sourcePath modules. Four
14874
+ // questions about one harness, in the order a reader meets them: is one
14875
+ // expected here, does the declared one exist, does it wire the real
14876
+ // modules, does it name every narrated path.
14877
+ integrationSimDeclarationRule,
14878
+ integrationSimFileRule,
14879
+ integrationSimWiringRule,
14880
+ integrationSimCoverageRule,
14046
14881
  couplingRule,
14047
- languageRule,
14882
+ // Target-language fit in two questions: what a CONTRACT may name, and what
14883
+ // a NARRATIVE may describe.
14884
+ signatureLanguageBuiltinsRule,
14885
+ narrativeLanguageConstructsRule,
14886
+ // The declaration before the consequences: which stereotype may bind a
14887
+ // technology at all, then where its name may appear.
14888
+ technologyBindingRule,
14048
14889
  technologyRule,
14049
14890
  namingRule,
14050
14891
  complexityRule,
@@ -14758,11 +15599,15 @@ function buildRuleContext(opts) {
14758
15599
  }
14759
15600
  return { kind: "unresolved" };
14760
15601
  };
15602
+ const contractMethods = /* @__PURE__ */ new Map();
14761
15603
  const interfaceMethodsOf = (compId) => {
15604
+ const cached = contractMethods.get(compId);
15605
+ if (cached) return cached;
14762
15606
  const out = [];
14763
15607
  for (const intf of interfacesByComponent.get(compId) ?? []) {
14764
15608
  out.push(...intf.methods);
14765
15609
  }
15610
+ contractMethods.set(compId, out);
14766
15611
  return out;
14767
15612
  };
14768
15613
  const profileDefFor = (subsystemId) => {
@@ -14858,7 +15703,30 @@ function buildRuleContext(opts) {
14858
15703
  ...surfaceResolved ? { surfaceResolved: true } : {}
14859
15704
  });
14860
15705
  };
14861
- return {
15706
+ let codeIndexMemo;
15707
+ const codeIndex = () => codeIndexMemo ??= buildCodeIndex(ctx.codeModel);
15708
+ let realizationMemo;
15709
+ const realizationIndex = () => realizationMemo ??= buildRealizationIndex(ctx);
15710
+ const importGraphs = /* @__PURE__ */ new Map();
15711
+ const importGraph = (paths) => {
15712
+ const universe = paths ?? codeIndex().paths;
15713
+ const key = `${universe.size}\0${[...universe].join("\0")}`;
15714
+ let graph = importGraphs.get(key);
15715
+ if (!graph) {
15716
+ graph = buildImportGraph(codeIndex(), universe);
15717
+ importGraphs.set(key, graph);
15718
+ }
15719
+ return graph;
15720
+ };
15721
+ let ownershipMemo;
15722
+ const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
15723
+ let dependencyEdgesMemo;
15724
+ const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
15725
+ let specIdsMemo;
15726
+ const specIds = () => specIdsMemo ??= buildSpecIds(ctx);
15727
+ let implementationMethodsMemo;
15728
+ const implementationMethods = () => implementationMethodsMemo ??= buildImplementationMethods(ctx);
15729
+ const ctx = {
14862
15730
  system,
14863
15731
  subsystems,
14864
15732
  components,
@@ -14887,6 +15755,13 @@ function buildRuleContext(opts) {
14887
15755
  isCollapsedCrossTreeRef,
14888
15756
  resolveSurfaceRef,
14889
15757
  interfaceMethodsOf,
15758
+ codeIndex,
15759
+ realizationIndex,
15760
+ importGraph,
15761
+ ownershipIndex,
15762
+ dependencyEdges,
15763
+ specIds,
15764
+ implementationMethods,
14890
15765
  complexityConfigFor,
14891
15766
  documentationConfigFor,
14892
15767
  namingConfigFor,
@@ -14901,6 +15776,7 @@ function buildRuleContext(opts) {
14901
15776
  knownIssueCodes: opts.knownIssueCodes,
14902
15777
  addIssue
14903
15778
  };
15779
+ return ctx;
14904
15780
  }
14905
15781
  var DEPTH_RANK, DEPTH_GATED_CODES, COMPLETENESS_RULES;
14906
15782
  var init_rules = __esm({
@@ -14909,6 +15785,7 @@ var init_rules = __esm({
14909
15785
  init_models();
14910
15786
  init_extensions();
14911
15787
  init_types();
15788
+ init_read_model();
14912
15789
  init_repository();
14913
15790
  init_lint_allows();
14914
15791
  init_source_analysis();
@@ -16749,8 +17626,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
16749
17626
  const componentById = new Map(graph.components.map((c) => [c.id, c]));
16750
17627
  const resolveComponent = (id) => {
16751
17628
  if (componentById.has(id)) return componentById.get(id);
16752
- const matches2 = graph.components.filter((c) => c.id.endsWith(`::${id}`));
16753
- return matches2.length === 1 ? matches2[0] : void 0;
17629
+ const matches = graph.components.filter((c) => c.id.endsWith(`::${id}`));
17630
+ return matches.length === 1 ? matches[0] : void 0;
16754
17631
  };
16755
17632
  const entry = resolveComponent(componentId);
16756
17633
  if (!entry) {
@@ -18011,9 +18888,9 @@ var init_specs2 = __esm({
18011
18888
  }
18012
18889
  }
18013
18890
  const suffix = `::${id}`;
18014
- const matches2 = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
18015
- if (matches2.length === 1) {
18016
- return index.paths.subsystem[matches2[0]];
18891
+ const matches = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
18892
+ if (matches.length === 1) {
18893
+ return index.paths.subsystem[matches[0]];
18017
18894
  }
18018
18895
  if (pathExists(this.paths.specsSubsystemsDir()) && listFiles(this.paths.specsSubsystemsDir(), ".yaml").length > 0) {
18019
18896
  return path16.join(this.paths.specsSubsystemsDir(), `${id}.yaml`);