@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/cli/index.js CHANGED
@@ -65,7 +65,7 @@ var init_defaults = __esm({
65
65
  copilot: ".github/prompts",
66
66
  codex: ".codex/agents"
67
67
  };
68
- WAIRON_VERSION = "5.1.1-dev.25";
68
+ WAIRON_VERSION = "5.1.1-dev.27";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -1966,8 +1966,8 @@ function extractTypeIdentifiers(typeStr) {
1966
1966
  cleaned = cleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
1967
1967
  cleaned = cleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
1968
1968
  cleaned = cleaned.replace(/(["'`])(?:\\.|[^\\])*?\1/g, " ");
1969
- const matches2 = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
1970
- return matches2.filter((t) => {
1969
+ const matches = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
1970
+ return matches.filter((t) => {
1971
1971
  if (!/[a-zA-Z0-9]/.test(t)) return false;
1972
1972
  if (/^\d+$/.test(t)) return false;
1973
1973
  return true;
@@ -8748,6 +8748,240 @@ var init_types = __esm({
8748
8748
  }
8749
8749
  });
8750
8750
 
8751
+ // src/core/rules/read-model.ts
8752
+ function appendUnique(map, key, value, same) {
8753
+ const list2 = map.get(key);
8754
+ if (!list2) {
8755
+ map.set(key, [value]);
8756
+ return;
8757
+ }
8758
+ if (same ? list2.some((v) => same(v, value)) : list2.includes(value)) return;
8759
+ list2.push(value);
8760
+ }
8761
+ function buildCodeIndex(model) {
8762
+ const facts = /* @__PURE__ */ new Map();
8763
+ const paths = /* @__PURE__ */ new Set();
8764
+ const exactPaths = /* @__PURE__ */ new Set();
8765
+ for (const f of model.files) {
8766
+ const key = pathKey(f.path);
8767
+ facts.set(key, f);
8768
+ paths.add(key);
8769
+ if (f.status === "analyzed" && f.analysisGrade === "exact") exactPaths.add(key);
8770
+ }
8771
+ const declarations = /* @__PURE__ */ new Map();
8772
+ const anchors = /* @__PURE__ */ new Map();
8773
+ const findingAnchors = /* @__PURE__ */ new Map();
8774
+ const namesAt = (cache2, path70, make) => {
8775
+ const cached = cache2.get(path70);
8776
+ if (cached) return cached;
8777
+ const f = facts.get(path70);
8778
+ if (!f) return NO_NAMES;
8779
+ const built = make(f);
8780
+ cache2.set(path70, built);
8781
+ return built;
8782
+ };
8783
+ return {
8784
+ paths,
8785
+ exactPaths,
8786
+ factsAt: (path70) => facts.get(pathKey(path70)),
8787
+ declarationsAt: (path70) => namesAt(declarations, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames])),
8788
+ anchorsAt: (path70) => namesAt(anchors, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
8789
+ findingAnchorsAt: (path70) => namesAt(findingAnchors, pathKey(path70), (f) => new Set(f.anchoredNames))
8790
+ };
8791
+ }
8792
+ function buildRealizationIndex(ctx) {
8793
+ const implsByComponent = /* @__PURE__ */ new Map();
8794
+ const implsByPath = /* @__PURE__ */ new Map();
8795
+ const filesByComponent = /* @__PURE__ */ new Map();
8796
+ const filesBySubsystem = /* @__PURE__ */ new Map();
8797
+ const componentsByPath = /* @__PURE__ */ new Map();
8798
+ const paths = [];
8799
+ const seenPaths = /* @__PURE__ */ new Set();
8800
+ for (const impl of ctx.implementations) {
8801
+ const contract = ctx.interfaceMap.get(impl.contract);
8802
+ if (!contract) continue;
8803
+ const component = ctx.componentMap.get(contract.component);
8804
+ if (!component) continue;
8805
+ if (ctx.isInChainedSubproject(component.subsystem)) continue;
8806
+ appendUnique(implsByComponent, component.id, impl);
8807
+ for (const file of implementationSourceFiles(impl)) {
8808
+ const p = pathKey(file);
8809
+ if (!seenPaths.has(p)) {
8810
+ seenPaths.add(p);
8811
+ paths.push(p);
8812
+ }
8813
+ appendUnique(implsByPath, p, impl);
8814
+ appendUnique(filesByComponent, component.id, p);
8815
+ appendUnique(filesBySubsystem, component.subsystem, p);
8816
+ appendUnique(componentsByPath, p, component, (a, b) => a.id === b.id);
8817
+ }
8818
+ }
8819
+ const NONE_IMPL = [];
8820
+ const NONE_COMP = [];
8821
+ const NONE_PATH = [];
8822
+ return {
8823
+ paths,
8824
+ implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
8825
+ implementationsAt: (path70) => implsByPath.get(pathKey(path70)) ?? NONE_IMPL,
8826
+ filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
8827
+ filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
8828
+ componentsAt: (path70) => componentsByPath.get(pathKey(path70)) ?? NONE_COMP
8829
+ };
8830
+ }
8831
+ function buildImportGraph(index, universe) {
8832
+ const imports = /* @__PURE__ */ new Map();
8833
+ const traces = /* @__PURE__ */ new Map();
8834
+ const resolveAll = (from, specifiers, into) => {
8835
+ for (const specifier of specifiers) {
8836
+ const to = resolveImport(from, specifier, universe);
8837
+ if (to && to !== from) into.add(to);
8838
+ }
8839
+ };
8840
+ const importsOf = (path70) => {
8841
+ const cached = imports.get(path70);
8842
+ if (cached) return cached;
8843
+ const facts = index.factsAt(path70);
8844
+ const out = /* @__PURE__ */ new Set();
8845
+ if (facts?.status === "analyzed") resolveAll(path70, facts.imports, out);
8846
+ imports.set(path70, out);
8847
+ return out;
8848
+ };
8849
+ const tracesOf = (path70) => {
8850
+ const cached = traces.get(path70);
8851
+ if (cached) return cached;
8852
+ const facts = index.factsAt(path70);
8853
+ const out = new Set(importsOf(path70));
8854
+ if (facts?.status === "analyzed") resolveAll(path70, facts.reexports, out);
8855
+ traces.set(path70, out);
8856
+ return out;
8857
+ };
8858
+ return {
8859
+ paths: [...universe],
8860
+ importsOf,
8861
+ tracesOf,
8862
+ connects: (from, to, allowReverse) => {
8863
+ for (const f of from) {
8864
+ const forward = tracesOf(f);
8865
+ for (const t of to) if (forward.has(t)) return true;
8866
+ }
8867
+ if (allowReverse) {
8868
+ for (const t of to) {
8869
+ const reverse = tracesOf(t);
8870
+ for (const f of from) if (reverse.has(f)) return true;
8871
+ }
8872
+ }
8873
+ return false;
8874
+ },
8875
+ reachFrom: (start2) => {
8876
+ const seen = /* @__PURE__ */ new Set([start2]);
8877
+ const stack = [start2];
8878
+ while (stack.length) {
8879
+ const from = stack.pop();
8880
+ for (const to of tracesOf(from)) {
8881
+ if (seen.has(to)) continue;
8882
+ seen.add(to);
8883
+ stack.push(to);
8884
+ }
8885
+ }
8886
+ return seen;
8887
+ }
8888
+ };
8889
+ }
8890
+ function buildOwnershipIndex(ctx) {
8891
+ const ownedBy = /* @__PURE__ */ new Map();
8892
+ for (const comp of ctx.components) {
8893
+ if (isRetired(comp) || !isPattern(comp)) continue;
8894
+ for (const memberId of comp.owns) {
8895
+ const member = ctx.componentMap.get(memberId);
8896
+ if (!member || isPattern(member)) continue;
8897
+ if (!ownedBy.has(memberId)) ownedBy.set(memberId, comp.id);
8898
+ }
8899
+ }
8900
+ return {
8901
+ ownedMembers: new Set(ownedBy.keys()),
8902
+ ownerOf: (memberId) => ownedBy.get(memberId)
8903
+ };
8904
+ }
8905
+ function buildDependencyEdges(ctx) {
8906
+ const all = [];
8907
+ const matrix = [];
8908
+ for (const from of ctx.components) {
8909
+ const fromDraft = ctx.isComponentDraft(from.id);
8910
+ const profile = ctx.ext.profiles[ctx.getComponentProfile(from.id)];
8911
+ for (const ref of from.dependsOn) {
8912
+ const to = ctx.componentMap.get(ref);
8913
+ if (!to) {
8914
+ all.push(offTreeEdge(ctx, from, ref, fromDraft));
8915
+ continue;
8916
+ }
8917
+ const edge = {
8918
+ from,
8919
+ ref,
8920
+ to,
8921
+ reach: to.subsystem === from.subsystem ? "internal" : "cross-subsystem",
8922
+ retired: isRetired(from) || isRetired(to),
8923
+ draftContext: fromDraft || ctx.isComponentDraft(to.id),
8924
+ licensed: profile?.allowedEdges?.some(
8925
+ (e) => e.from.includes(from.componentType) && e.to.includes(to.componentType)
8926
+ ) ?? false
8927
+ };
8928
+ all.push(edge);
8929
+ if (edge.reach === "internal" && !edge.retired && !edge.licensed) matrix.push(edge);
8930
+ }
8931
+ }
8932
+ return { all, matrix };
8933
+ }
8934
+ function buildSpecIds(ctx) {
8935
+ const out = [];
8936
+ for (const s of ctx.subsystems) out.push({ id: s.id, kind: "Subsystem" });
8937
+ for (const c of ctx.components) out.push({ id: c.id, kind: "Component" });
8938
+ for (const i of ctx.interfaces) out.push({ id: i.id, kind: "Interface" });
8939
+ for (const im of ctx.implementations) out.push({ id: im.id, kind: "Implementation" });
8940
+ for (const t of ctx.types) out.push({ id: t.id, kind: "Type" });
8941
+ return out;
8942
+ }
8943
+ function buildImplementationMethods(ctx) {
8944
+ const out = [];
8945
+ for (const impl of ctx.implementations) {
8946
+ const contract = ctx.interfaceMap.get(impl.contract);
8947
+ if (!contract) continue;
8948
+ const component = ctx.componentMap.get(contract.component);
8949
+ if (!component) continue;
8950
+ if (ctx.isInChainedSubproject(component.subsystem)) continue;
8951
+ const draftContext = ctx.isImplementationDraft(impl);
8952
+ for (const method2 of impl.methods) {
8953
+ const sourceFile = methodSourceFile(method2, impl.sourcePath);
8954
+ out.push({
8955
+ implementation: impl,
8956
+ method: method2,
8957
+ component,
8958
+ ...sourceFile !== void 0 ? { sourceFile } : {},
8959
+ draftContext
8960
+ });
8961
+ }
8962
+ }
8963
+ return out;
8964
+ }
8965
+ function offTreeEdge(ctx, from, ref, fromDraft) {
8966
+ const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
8967
+ const external = ctx.isExternalNamespaceRef(ref);
8968
+ if (external || ctx.isCollapsedCrossTreeRef(ref, from.subsystem)) {
8969
+ const surface = ctx.resolveSurfaceRef(ref, from.subsystem);
8970
+ if (surface.kind === "ambiguous") return { ...base, reach: "ambiguous", surface };
8971
+ if (surface.kind === "resolved") return { ...base, reach: "surface", surface };
8972
+ if (external) return { ...base, reach: "unpinned" };
8973
+ }
8974
+ return { ...base, reach: "missing" };
8975
+ }
8976
+ var NO_NAMES;
8977
+ var init_read_model = __esm({
8978
+ "src/core/rules/read-model.ts"() {
8979
+ "use strict";
8980
+ init_models();
8981
+ NO_NAMES = /* @__PURE__ */ new Set();
8982
+ }
8983
+ });
8984
+
8751
8985
  // src/core/rules/integrity/hierarchy-integrity.ts
8752
8986
  var hierarchyRule;
8753
8987
  var init_hierarchy_integrity = __esm({
@@ -8828,48 +9062,58 @@ var init_hierarchy_integrity = __esm({
8828
9062
  }
8829
9063
  });
8830
9064
 
8831
- // src/core/rules/integrity/namespace-hygiene.ts
8832
- var namespaceHygieneRule;
8833
- var init_namespace_hygiene = __esm({
8834
- "src/core/rules/integrity/namespace-hygiene.ts"() {
9065
+ // src/core/rules/integrity/reserved-id-segments.ts
9066
+ var reservedIdSegmentsRule;
9067
+ var init_reserved_id_segments = __esm({
9068
+ "src/core/rules/integrity/reserved-id-segments.ts"() {
9069
+ "use strict";
9070
+ reservedIdSegmentsRule = {
9071
+ name: "reserved-id-segments",
9072
+ description: 'No segment of a spec id may be the reserved namespace keyword "super" \u2014 the :: grammar consumes it as a namespace hop, so a stored reference to such an id resolves to a different spec.',
9073
+ codes: [
9074
+ { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
9075
+ ],
9076
+ check(ctx) {
9077
+ for (const spec of ctx.specIds()) {
9078
+ if (!ctx.isSpecInScope(spec.id)) continue;
9079
+ if (!spec.id.split("::").includes("super")) continue;
9080
+ ctx.addIssue(
9081
+ "error",
9082
+ "RESERVED_ID_SEGMENT",
9083
+ `${spec.kind} id "${spec.id}" uses the reserved namespace keyword "super" \u2014 stored references to it would be consumed as a namespace hop and resolve to a different spec.`,
9084
+ spec.id
9085
+ );
9086
+ }
9087
+ }
9088
+ };
9089
+ }
9090
+ });
9091
+
9092
+ // src/core/rules/integrity/namespace-shadowing.ts
9093
+ var namespaceShadowingRule;
9094
+ var init_namespace_shadowing = __esm({
9095
+ "src/core/rules/integrity/namespace-shadowing.ts"() {
8835
9096
  "use strict";
8836
- namespaceHygieneRule = {
8837
- name: "namespace-hygiene",
8838
- 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.',
9097
+ namespaceShadowingRule = {
9098
+ name: "namespace-shadowing",
9099
+ description: "A subproject-local name must not shadow a root-level subsystem id \u2014 inside its subproject a bare reference to the shadowed name anchors to the ROOT subsystem, so the local spec cannot be addressed reliably.",
8839
9100
  codes: [
8840
- { code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' },
8841
9101
  { code: "NAMESPACE_SHADOWING", defaultSeverity: "error", summary: "Subproject-local id shadows a root subsystem id (bare references anchor to the root)" }
8842
9102
  ],
8843
9103
  check(ctx) {
8844
9104
  const rootSubs = new Set(ctx.subsystems.filter((s) => !s.id.includes("::")).map((s) => s.id));
8845
- const checkId = (id, kindLabel) => {
8846
- if (!ctx.isSpecInScope(id)) return;
8847
- const segments = id.split("::");
8848
- if (segments.includes("super")) {
8849
- ctx.addIssue(
8850
- "error",
8851
- "RESERVED_ID_SEGMENT",
8852
- `${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.`,
8853
- id
8854
- );
8855
- }
8856
- if (segments.length > 1) {
8857
- const local = segments[segments.length - 1];
8858
- if (rootSubs.has(local)) {
8859
- ctx.addIssue(
8860
- "error",
8861
- "NAMESPACE_SHADOWING",
8862
- `${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.`,
8863
- id
8864
- );
8865
- }
8866
- }
8867
- };
8868
- for (const s of ctx.subsystems) checkId(s.id, "Subsystem");
8869
- for (const c of ctx.components) checkId(c.id, "Component");
8870
- for (const i of ctx.interfaces) checkId(i.id, "Interface");
8871
- for (const im of ctx.implementations) checkId(im.id, "Implementation");
8872
- for (const t of ctx.types) checkId(t.id, "Type");
9105
+ for (const spec of ctx.specIds()) {
9106
+ if (!ctx.isSpecInScope(spec.id)) continue;
9107
+ const segments = spec.id.split("::");
9108
+ const local = segments[segments.length - 1];
9109
+ if (segments.length <= 1 || !rootSubs.has(local)) continue;
9110
+ ctx.addIssue(
9111
+ "error",
9112
+ "NAMESPACE_SHADOWING",
9113
+ `${spec.kind} "${spec.id}" shadows root subsystem "${local}" \u2014 inside its subproject a bare reference to "${local}" anchors to the ROOT subsystem, so this spec cannot be addressed reliably. Rename one of them.`,
9114
+ spec.id
9115
+ );
9116
+ }
8873
9117
  }
8874
9118
  };
8875
9119
  }
@@ -9014,10 +9258,48 @@ var init_signature_type_references = __esm({
9014
9258
  }
9015
9259
  });
9016
9260
 
9017
- // src/core/rules/integrity/public-surface.ts
9018
- var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceRule;
9019
- var init_public_surface = __esm({
9020
- "src/core/rules/integrity/public-surface.ts"() {
9261
+ // src/core/rules/integrity/public-surface-binding.ts
9262
+ var publicSurfaceBindingRule;
9263
+ var init_public_surface_binding = __esm({
9264
+ "src/core/rules/integrity/public-surface-binding.ts"() {
9265
+ "use strict";
9266
+ init_models();
9267
+ publicSurfaceBindingRule = {
9268
+ name: "public-surface-binding",
9269
+ description: "Every declared publicInterface names a backing component, that component exists, and the subsystem owns it \u2014 a subsystem may only publish its own components.",
9270
+ codes: [
9271
+ { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9272
+ { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9273
+ { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" }
9274
+ ],
9275
+ check(ctx) {
9276
+ for (const sub of ctx.subsystems) {
9277
+ const isDraftCtx = isDraftSubsystem(sub);
9278
+ for (const pi of sub.publicInterfaces) {
9279
+ if (!pi.component) {
9280
+ ctx.addIssue("error", "PUBLIC_INTERFACE_UNBOUND", `Subsystem "${sub.id}" declares a ${pi.type} public interface with no backing component. Bind it to the component that realizes it (publicInterfaces[].component).`, sub.id, isDraftCtx);
9281
+ continue;
9282
+ }
9283
+ const backing = ctx.componentMap.get(pi.component);
9284
+ if (!backing) {
9285
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9286
+ continue;
9287
+ }
9288
+ const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9289
+ if (!isSubsystemOwner) {
9290
+ ctx.addIssue("error", "PUBLIC_INTERFACE_FOREIGN_COMPONENT", `Subsystem "${sub.id}" publishes component "${pi.component}", but it belongs to subsystem "${backing.subsystem}". A subsystem may only publish its own components.`, sub.id, isDraftCtx);
9291
+ }
9292
+ }
9293
+ }
9294
+ }
9295
+ };
9296
+ }
9297
+ });
9298
+
9299
+ // src/core/rules/integrity/public-surface-declared-type.ts
9300
+ var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceDeclaredTypeRule;
9301
+ var init_public_surface_declared_type = __esm({
9302
+ "src/core/rules/integrity/public-surface-declared-type.ts"() {
9021
9303
  "use strict";
9022
9304
  init_models();
9023
9305
  pubTypeMatches = (piType, ct, portalType) => {
@@ -9051,45 +9333,22 @@ var init_public_surface = __esm({
9051
9333
  return "a compatible component";
9052
9334
  }
9053
9335
  };
9054
- publicSurfaceRule = {
9055
- name: "public-surface",
9056
- description: "Every declared publicInterface is bound to an existing component of this subsystem whose stereotype can realize the declared interface type; Custom entries whose prose implies eventing must be backed by an event-capable component.",
9336
+ publicSurfaceDeclaredTypeRule = {
9337
+ name: "public-surface-declared-type",
9338
+ description: "A publicInterface entry's declared type must be realizable by the stereotype of its backing component; Custom entries whose prose implies eventing must be backed by an event-capable component.",
9057
9339
  codes: [
9058
- { code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
9059
- { code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
9060
- { code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" },
9061
9340
  { code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
9062
- { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" },
9063
9341
  { code: "PUBLIC_INTERFACE_EVENT_MISTYPED", defaultSeverity: "warning", summary: "Custom interface describing eventing backed by a non-event component" }
9064
9342
  ],
9065
9343
  check(ctx) {
9066
9344
  for (const sub of ctx.subsystems) {
9067
9345
  const isDraftCtx = isDraftSubsystem(sub);
9068
9346
  for (const pi of sub.publicInterfaces) {
9069
- if (!pi.component) {
9070
- 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);
9071
- continue;
9072
- }
9073
- const backing = ctx.componentMap.get(pi.component);
9074
- if (!backing) {
9075
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
9076
- continue;
9077
- }
9078
- const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
9079
- if (!isSubsystemOwner) {
9080
- 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);
9081
- }
9347
+ const backing = pi.component ? ctx.componentMap.get(pi.component) : void 0;
9348
+ if (!backing) continue;
9082
9349
  if (!pubTypeMatches(pi.type, backing.componentType, backing.portalType)) {
9083
9350
  ctx.addIssue("error", "PUBLIC_INTERFACE_TYPE_MISMATCH", `Subsystem "${sub.id}" declares a ${pi.type} public interface backed by "${pi.component}" (${backing.componentType}${backing.portalType ? `/${backing.portalType}` : ""}), which cannot realize ${pi.type}. Expected ${expectedFor(pi.type)}.`, sub.id, isDraftCtx);
9084
9351
  }
9085
- if (pi.interface) {
9086
- const intf = ctx.interfaceMap.get(pi.interface);
9087
- if (!intf) {
9088
- ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9089
- } else if (intf.component !== pi.component) {
9090
- 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);
9091
- }
9092
- }
9093
9352
  if (pi.type === "Custom" && EVENT_VOCAB.test(pi.details)) {
9094
9353
  const eventCapable = backing.componentType === "Observer" || backing.componentType === "Portal" && backing.portalType === "MessageBus";
9095
9354
  if (!eventCapable) {
@@ -9103,6 +9362,37 @@ var init_public_surface = __esm({
9103
9362
  }
9104
9363
  });
9105
9364
 
9365
+ // src/core/rules/integrity/public-surface-bound-contract.ts
9366
+ var publicSurfaceBoundContractRule;
9367
+ var init_public_surface_bound_contract = __esm({
9368
+ "src/core/rules/integrity/public-surface-bound-contract.ts"() {
9369
+ "use strict";
9370
+ init_models();
9371
+ publicSurfaceBoundContractRule = {
9372
+ name: "public-surface-bound-contract",
9373
+ description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
9374
+ codes: [
9375
+ { code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
9376
+ ],
9377
+ check(ctx) {
9378
+ for (const sub of ctx.subsystems) {
9379
+ const isDraftCtx = isDraftSubsystem(sub);
9380
+ for (const pi of sub.publicInterfaces) {
9381
+ if (!pi.interface) continue;
9382
+ if (!pi.component || !ctx.componentMap.has(pi.component)) continue;
9383
+ const intf = ctx.interfaceMap.get(pi.interface);
9384
+ if (!intf) {
9385
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
9386
+ } else if (intf.component !== pi.component) {
9387
+ ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" binds interface "${pi.interface}" to component "${pi.component}", but that interface belongs to component "${intf.component}".`, sub.id, isDraftCtx);
9388
+ }
9389
+ }
9390
+ }
9391
+ }
9392
+ };
9393
+ }
9394
+ });
9395
+
9106
9396
  // src/core/rules/integrity/lint-allows.ts
9107
9397
  var lintAllowsRule;
9108
9398
  var init_lint_allows = __esm({
@@ -9708,7 +9998,7 @@ var init_narrative_jump_edges = __esm({
9708
9998
  }
9709
9999
  });
9710
10000
 
9711
- // src/core/rules/narrative/narrative-antipatterns.ts
10001
+ // src/core/rules/narrative/completed-step-graph.ts
9712
10002
  function completedStepGraph(steps) {
9713
10003
  const numbers = steps.flatMap((s) => [
9714
10004
  s.stepNumber,
@@ -9738,6 +10028,14 @@ function isUnavoidable(steps, target) {
9738
10028
  if (s.type === "return" || s.type === "throw") return true;
9739
10029
  const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
9740
10030
  if (arms.length) return arms.every((e) => completes.has(e));
10031
+ const guaranteed = s.endStep !== void 0 && (s.type === "try" || s.type === "loop" && s.loopKind === "doWhile");
10032
+ if (guaranteed) {
10033
+ const bodyEntry = graph.nextOf(n);
10034
+ if (bodyEntry !== void 0 && bodyEntry <= s.endStep) {
10035
+ if (s.type === "loop") return completes.has(bodyEntry);
10036
+ if (bodyEntry === target) return false;
10037
+ }
10038
+ }
9741
10039
  return graph.successorsOf(n).some((t) => completes.has(t));
9742
10040
  };
9743
10041
  const order = [...graph.stepNumbers].reverse();
@@ -9801,25 +10099,27 @@ function stronglyConnected(nums, successorsOf) {
9801
10099
  }
9802
10100
  return sccs;
9803
10101
  }
9804
- var byCodeUnit, narrativeAntipatternsRule;
9805
- var init_narrative_antipatterns = __esm({
9806
- "src/core/rules/narrative/narrative-antipatterns.ts"() {
10102
+ var init_completed_step_graph = __esm({
10103
+ "src/core/rules/narrative/completed-step-graph.ts"() {
9807
10104
  "use strict";
9808
10105
  init_models();
9809
- byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
9810
- narrativeAntipatternsRule = {
9811
- name: "narrative-antipatterns",
9812
- description: "Provable narrative bugs, caught at spec level before implementation: a step-graph cycle with no exit edge and no return/throw member never terminates by construction (INESCAPABLE_CYCLE); a branch or switch whose arms all target the same step decides nothing (MEANINGLESS_BRANCH); a method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path is unbounded recursion (UNCONDITIONAL_CALL_CYCLE \u2014 dispatch steps resolved through their portal tables; a cycle with even one guarded edge is NOT flagged, since prose conditions are never judged). Structure-only claims \u2014 warnings, lint.allow-suppressible.",
10106
+ }
10107
+ });
10108
+
10109
+ // src/core/rules/narrative/meaningless-branches.ts
10110
+ var meaninglessBranchesRule;
10111
+ var init_meaningless_branches = __esm({
10112
+ "src/core/rules/narrative/meaningless-branches.ts"() {
10113
+ "use strict";
10114
+ init_completed_step_graph();
10115
+ meaninglessBranchesRule = {
10116
+ name: "meaningless-branches",
10117
+ description: "A branch or switch whose arms all target the same step decides nothing: the condition is evaluated and flow continues at one place whatever it answers. A decision's targets are its explicit ones plus, where it leaves its fall-through field unset, the steps the step graph continues it at \u2014 so the classic authoring slip of pointing both arms at the next step is caught. Structure-only \u2014 a warning, lint.allow-suppressible.",
9813
10118
  codes: [
9814
- { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" },
9815
- { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" },
9816
- { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10119
+ { code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
9817
10120
  ],
9818
10121
  check(ctx) {
9819
- const callEdges = [];
9820
10122
  for (const impl of ctx.implementations) {
9821
- const contract = ctx.interfaceMap.get(impl.contract);
9822
- const component = contract ? ctx.componentMap.get(contract.component) : void 0;
9823
10123
  const isDraftCtx = ctx.isImplementationDraft(impl);
9824
10124
  for (const implMethod of impl.methods) {
9825
10125
  const steps = implMethod.narrative;
@@ -9858,6 +10158,33 @@ var init_narrative_antipatterns = __esm({
9858
10158
  }
9859
10159
  }
9860
10160
  }
10161
+ }
10162
+ }
10163
+ }
10164
+ };
10165
+ }
10166
+ });
10167
+
10168
+ // src/core/rules/narrative/inescapable-cycles.ts
10169
+ var inescapableCyclesRule;
10170
+ var init_inescapable_cycles = __esm({
10171
+ "src/core/rules/narrative/inescapable-cycles.ts"() {
10172
+ "use strict";
10173
+ init_completed_step_graph();
10174
+ inescapableCyclesRule = {
10175
+ name: "inescapable-cycles",
10176
+ description: "A cycle in a narrative's step graph with no exit edge and no return or throw member never terminates, by construction: every path that enters it stays inside it forever. Read over the step graph closed by a completion step, so falling off the end of the narrative counts as leaving. Structure-only \u2014 prose conditions are never judged \u2014 and reported as a warning, lint.allow-suppressible.",
10177
+ codes: [
10178
+ { code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
10179
+ ],
10180
+ check(ctx) {
10181
+ for (const impl of ctx.implementations) {
10182
+ const isDraftCtx = ctx.isImplementationDraft(impl);
10183
+ for (const implMethod of impl.methods) {
10184
+ const steps = implMethod.narrative;
10185
+ if (!steps.length) continue;
10186
+ const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
10187
+ const graph = completedStepGraph(steps);
9861
10188
  for (const scc of stronglyConnected(graph.stepNumbers, graph.successorsOf)) {
9862
10189
  const inScc = new Set(scc);
9863
10190
  const isCycle = scc.length > 1 || graph.successorsOf(scc[0]).includes(scc[0]);
@@ -9878,7 +10205,35 @@ var init_narrative_antipatterns = __esm({
9878
10205
  );
9879
10206
  }
9880
10207
  }
9881
- if (!component) continue;
10208
+ }
10209
+ }
10210
+ }
10211
+ };
10212
+ }
10213
+ });
10214
+
10215
+ // src/core/rules/narrative/unconditional-call-cycles.ts
10216
+ var byCodeUnit, unconditionalCallCyclesRule;
10217
+ var init_unconditional_call_cycles = __esm({
10218
+ "src/core/rules/narrative/unconditional-call-cycles.ts"() {
10219
+ "use strict";
10220
+ init_completed_step_graph();
10221
+ byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
10222
+ unconditionalCallCyclesRule = {
10223
+ name: "unconditional-call-cycles",
10224
+ description: "A method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path of its narrative is unbounded recursion, by construction. Dispatch steps are resolved through their portal's table; register steps are never edges (a handoff defers the invocation to the runtime, so it cannot recurse). A cycle with even one guarded edge is NOT flagged, since prose conditions are never judged. Structure-only \u2014 a warning, lint.allow-suppressible, anchored on the member edge that sorts first so one allow covers the cycle.",
10225
+ codes: [
10226
+ { code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
10227
+ ],
10228
+ check(ctx) {
10229
+ const callEdges = [];
10230
+ for (const impl of ctx.implementations) {
10231
+ const contract = ctx.interfaceMap.get(impl.contract);
10232
+ const component = contract ? ctx.componentMap.get(contract.component) : void 0;
10233
+ if (!component) continue;
10234
+ for (const implMethod of impl.methods) {
10235
+ const steps = implMethod.narrative;
10236
+ if (!steps.length) continue;
9882
10237
  const fromKey = `${component.id}::${implMethod.name}`;
9883
10238
  for (const s of steps) {
9884
10239
  let toComponent;
@@ -9940,35 +10295,29 @@ var init_narrative_antipatterns = __esm({
9940
10295
  });
9941
10296
 
9942
10297
  // src/core/rules/narrative/narrative-detail.ts
9943
- var DEFAULT_MAX_UNNARRATED_COMPLEXITY, narrativeDetailRule;
10298
+ var narrativeDetailRule;
9944
10299
  var init_narrative_detail = __esm({
9945
10300
  "src/core/rules/narrative/narrative-detail.ts"() {
9946
10301
  "use strict";
9947
10302
  init_models();
9948
- DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
9949
10303
  narrativeDetailRule = {
9950
10304
  name: "narrative-detail",
9951
- description: "The narrative detail dial: each method resolves to full | calls-only | intent (method override \u2192 spec default \u2192 stereotype default). full requires a narrative; intent-level methods without a narrative must specify behavior as non-trivial prose (L4 intent or L3 description) \u2014 dialing detail down never means leaving behavior unspecified. Explicit declarations are held to their promise as errors; stereotype-defaulted gaps surface as warnings. Detail sufficiency rides along: a method whose realized function \u2014 in the method's own source file, else the implementation's \u2014 measures real branching (cyclomatic complexity above rules.complexity.maxUnnarratedComplexity, exact AST grade only) may not hide below detail: full without a narrative (UNNARRATED_COMPLEXITY), and an explicit dial below a full-floor logic stereotype without a narrative is a visible, lint.allow-justifiable choice (DETAIL_BELOW_STEREOTYPE). Both are honest lints over declarations \u2014 they never claim the narrative or prose is CORRECT.",
10305
+ description: "The narrative detail dial: each method resolves to full | calls-only | intent (method override \u2192 spec default \u2192 stereotype default). full requires a narrative; intent-level methods without a narrative must specify behavior as non-trivial prose (L4 intent or L3 description) \u2014 dialing detail down never means leaving behavior unspecified. Explicit declarations are held to their promise as errors; stereotype-defaulted gaps surface as warnings. An honest lint over declarations \u2014 it never claims the narrative or the prose is CORRECT.",
9952
10306
  codes: [
9953
10307
  { code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
9954
- { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" },
9955
- { 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)" },
9956
- { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10308
+ { code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
9957
10309
  ],
9958
10310
  check(ctx) {
9959
- const factsByPath = /* @__PURE__ */ new Map();
9960
- for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
9961
10311
  for (const impl of ctx.implementations) {
9962
10312
  const contract = ctx.interfaceMap.get(impl.contract);
9963
10313
  if (!contract) continue;
9964
10314
  const component = ctx.componentMap.get(contract.component);
9965
- const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract.status === "draft" || contract.status === "design" || ctx.isComponentDraft(contract.component);
10315
+ const isDraftCtx = ctx.isImplementationDraft(impl);
9966
10316
  for (const implMethod of impl.methods) {
9967
10317
  const contractMethod = contract.methods.find((m) => m.name === implMethod.name);
9968
10318
  if (!contractMethod) continue;
10319
+ if (implMethod.narrative.length > 0) continue;
9969
10320
  const eff = effectiveDetail(implMethod, impl, component);
9970
- const hasNarrative = implMethod.narrative.length > 0;
9971
- if (hasNarrative) continue;
9972
10321
  if (eff.level === "full") {
9973
10322
  ctx.addIssue(
9974
10323
  eff.explicit ? "error" : "warning",
@@ -9979,34 +10328,6 @@ var init_narrative_detail = __esm({
9979
10328
  );
9980
10329
  continue;
9981
10330
  }
9982
- let complexityFired = false;
9983
- const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
9984
- const file = methodSourceFile(implMethod, impl.sourcePath);
9985
- if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
9986
- const facts = factsByPath.get(pathKey(file));
9987
- const symbol = implMethod.symbol ?? implMethod.name;
9988
- const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
9989
- const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
9990
- if (complexity !== void 0 && complexity > limit) {
9991
- complexityFired = true;
9992
- ctx.addIssue(
9993
- "warning",
9994
- "UNNARRATED_COMPLEXITY",
9995
- `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.`,
9996
- impl.id,
9997
- isDraftCtx
9998
- );
9999
- }
10000
- }
10001
- if (!complexityFired && eff.explicit && component && isLogic(component)) {
10002
- ctx.addIssue(
10003
- "warning",
10004
- "DETAIL_BELOW_STEREOTYPE",
10005
- `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.`,
10006
- impl.id,
10007
- isDraftCtx
10008
- );
10009
- }
10010
10331
  const prose = implMethod.intent ?? contractMethod.description;
10011
10332
  if (!passesIntentFloor(prose, implMethod.name)) {
10012
10333
  ctx.addIssue(
@@ -10024,6 +10345,71 @@ var init_narrative_detail = __esm({
10024
10345
  }
10025
10346
  });
10026
10347
 
10348
+ // src/core/rules/narrative/detail-sufficiency.ts
10349
+ var DEFAULT_MAX_UNNARRATED_COMPLEXITY, detailSufficiencyRule;
10350
+ var init_detail_sufficiency = __esm({
10351
+ "src/core/rules/narrative/detail-sufficiency.ts"() {
10352
+ "use strict";
10353
+ init_models();
10354
+ DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
10355
+ detailSufficiencyRule = {
10356
+ name: "detail-sufficiency",
10357
+ description: "A method dialed below detail: full with no narrative may not be hiding behavior it owes a reader. A method whose realized function \u2014 in the method's own source file, else the implementation's \u2014 measures real branching (cyclomatic complexity above rules.complexity.maxUnnarratedComplexity, exact AST grade only) is reported (UNNARRATED_COMPLEXITY), and an explicit dial below a full-floor logic stereotype without a narrative is a visible, lint.allow-justifiable choice (DETAIL_BELOW_STEREOTYPE) \u2014 reported only where the measured finding did not already fire, since evidence outranks expectation. Both are honest lints over declarations: they never claim a narrative would be correct, only that the dial hides something.",
10358
+ codes: [
10359
+ { code: "UNNARRATED_COMPLEXITY", defaultSeverity: "warning", summary: "Method below detail: full with no narrative whose realized function, in the method's source file, has real branching (cyclomatic complexity over the configured threshold, exact-grade analysis only)" },
10360
+ { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
10361
+ ],
10362
+ check(ctx) {
10363
+ const code = ctx.codeIndex();
10364
+ const dialedDown = [];
10365
+ for (const impl of ctx.implementations) {
10366
+ const contract = ctx.interfaceMap.get(impl.contract);
10367
+ if (!contract) continue;
10368
+ const component = ctx.componentMap.get(contract.component);
10369
+ const draftContext = ctx.isImplementationDraft(impl);
10370
+ for (const method2 of impl.methods) {
10371
+ const detail = effectiveDetail(method2, impl, component);
10372
+ if (contract.methods.some((m) => m.name === method2.name) && method2.narrative.length === 0 && detail.level !== "full") {
10373
+ dialedDown.push({ implementation: impl, method: method2, component, detail, draftContext });
10374
+ }
10375
+ }
10376
+ }
10377
+ for (const entry of dialedDown) {
10378
+ const { implementation: impl, method: method2, component, detail } = entry;
10379
+ const tier = method2.conformance ?? impl.conformance ?? defaultConformanceTier(component);
10380
+ const file = methodSourceFile(method2, impl.sourcePath);
10381
+ const symbol = method2.symbol ?? method2.name;
10382
+ const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
10383
+ let complexity;
10384
+ if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
10385
+ const facts = code.factsAt(file);
10386
+ complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
10387
+ }
10388
+ if (complexity !== void 0 && complexity > limit) {
10389
+ ctx.addIssue(
10390
+ "warning",
10391
+ "UNNARRATED_COMPLEXITY",
10392
+ `Method "${method2.name}" in implementation "${impl.id}" sits at detail: ${detail.level} with no narrative, but its realized function "${symbol}" in "${file}" measures cyclomatic complexity ${complexity} (limit ${limit}) \u2014 real branching is hiding behind ${detail.level}. Write the narrative, or keep the dial with a lint.allow stating why the branching needs no choreography.`,
10393
+ impl.id,
10394
+ entry.draftContext
10395
+ );
10396
+ continue;
10397
+ }
10398
+ if (detail.explicit && component && isLogic(component)) {
10399
+ ctx.addIssue(
10400
+ "warning",
10401
+ "DETAIL_BELOW_STEREOTYPE",
10402
+ `Method "${method2.name}" in implementation "${impl.id}" is explicitly dialed to detail: ${detail.level}, below the full narrative floor of its ${component.componentType} stereotype, and has no narrative. Logic behavior belongs in a narrative \u2014 write one, or keep the dial with a lint.allow stating why.`,
10403
+ impl.id,
10404
+ entry.draftContext
10405
+ );
10406
+ }
10407
+ }
10408
+ }
10409
+ };
10410
+ }
10411
+ });
10412
+
10027
10413
  // src/core/rules/intrinsic/portal-fields.ts
10028
10414
  var portalFieldsRule;
10029
10415
  var init_portal_fields = __esm({
@@ -10129,7 +10515,7 @@ var init_logic_declaration = __esm({
10129
10515
  logicDeclarationRule = {
10130
10516
  name: "logic-declaration",
10131
10517
  scope: "spec",
10132
- 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.",
10518
+ 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.",
10133
10519
  codes: [
10134
10520
  { code: "DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR", defaultSeverity: "error", summary: "dependencyClass declared on a component that is not an Orchestrator" }
10135
10521
  ],
@@ -10208,45 +10594,61 @@ var init_portal_endpoints = __esm({
10208
10594
  };
10209
10595
  portalsRule = {
10210
10596
  name: "portal-endpoints",
10211
- description: "A Portal binds every interface method to a concrete endpoint of the matching transport, and only a Portal may carry endpoints at all.",
10597
+ description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
10212
10598
  codes: [
10213
10599
  { code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
10214
- { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" },
10215
- { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10600
+ { code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
10216
10601
  ],
10217
10602
  check(ctx) {
10218
10603
  for (const comp of ctx.components) {
10604
+ if (comp.componentType !== "Portal") continue;
10605
+ const expected = comp.portalType ? PORTAL_TRANSPORT[comp.portalType] : void 0;
10606
+ if (!expected) continue;
10219
10607
  const isDraftCtx = ctx.isComponentDraft(comp.id);
10220
- const compInterfaces = ctx.interfaces.filter((i) => i.component === comp.id);
10221
- if (comp.componentType === "Portal") {
10222
- if (!comp.portalType) continue;
10223
- const expected = PORTAL_TRANSPORT[comp.portalType];
10224
- if (!expected) continue;
10225
- for (const intf of compInterfaces) {
10226
- const isIntfDraft = intf.status === "draft" || intf.status === "design";
10227
- for (const m of intf.methods) {
10228
- if (!m.endpoint) {
10229
- ctx.addIssue(
10230
- "error",
10231
- "MISSING_ENDPOINT",
10232
- `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10233
- intf.id,
10234
- isDraftCtx || isIntfDraft
10235
- );
10236
- } else if (m.endpoint.transport !== expected) {
10237
- ctx.addIssue(
10238
- "error",
10239
- "ENDPOINT_TRANSPORT_MISMATCH",
10240
- `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}").`,
10241
- intf.id,
10242
- isDraftCtx || isIntfDraft
10243
- );
10244
- }
10608
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10609
+ const isIntfDraft = intf.status === "draft" || intf.status === "design";
10610
+ for (const m of intf.methods) {
10611
+ if (!m.endpoint) {
10612
+ ctx.addIssue(
10613
+ "error",
10614
+ "MISSING_ENDPOINT",
10615
+ `Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
10616
+ intf.id,
10617
+ isDraftCtx || isIntfDraft
10618
+ );
10619
+ } else if (m.endpoint.transport !== expected) {
10620
+ ctx.addIssue(
10621
+ "error",
10622
+ "ENDPOINT_TRANSPORT_MISMATCH",
10623
+ `Method "${m.name}" on interface "${intf.id}" declares a "${m.endpoint.transport}" endpoint, but its Portal "${comp.id}" is portalType "${comp.portalType}" (expects transport "${expected}").`,
10624
+ intf.id,
10625
+ isDraftCtx || isIntfDraft
10626
+ );
10245
10627
  }
10246
10628
  }
10247
- continue;
10248
10629
  }
10249
- for (const intf of compInterfaces) {
10630
+ }
10631
+ }
10632
+ };
10633
+ }
10634
+ });
10635
+
10636
+ // src/core/rules/doctrine/non-portal-endpoints.ts
10637
+ var nonPortalEndpointsRule;
10638
+ var init_non_portal_endpoints = __esm({
10639
+ "src/core/rules/doctrine/non-portal-endpoints.ts"() {
10640
+ "use strict";
10641
+ nonPortalEndpointsRule = {
10642
+ name: "non-portal-endpoints",
10643
+ description: "Only a Portal may carry endpoints: a method on a non-Portal component's interface that declares one is publishing a block that is not the system's boundary as if it were.",
10644
+ codes: [
10645
+ { code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
10646
+ ],
10647
+ check(ctx) {
10648
+ for (const comp of ctx.components) {
10649
+ if (comp.componentType === "Portal") continue;
10650
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10651
+ for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
10250
10652
  const isIntfDraft = intf.status === "draft" || intf.status === "design";
10251
10653
  for (const m of intf.methods) {
10252
10654
  if (m.endpoint) {
@@ -10267,20 +10669,16 @@ var init_portal_endpoints = __esm({
10267
10669
  });
10268
10670
 
10269
10671
  // src/core/rules/doctrine/portal-call-auth.ts
10270
- var COMPONENT_REF_PREFIX, portalCallAuthRule;
10672
+ var portalCallAuthRule;
10271
10673
  var init_portal_call_auth = __esm({
10272
10674
  "src/core/rules/doctrine/portal-call-auth.ts"() {
10273
10675
  "use strict";
10274
- COMPONENT_REF_PREFIX = "component:";
10275
10676
  portalCallAuthRule = {
10276
10677
  name: "portal-call-auth",
10277
- description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. When `auth.from` is a modeled reference (`component:<id>`) it must resolve to an Adapter/Store the presenter is wired to (UNKNOWN_AUTH_SOURCE / AUTH_SOURCE_NOT_PROVIDER / AUTH_SOURCE_UNWIRED). The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
10678
+ description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
10278
10679
  codes: [
10279
10680
  { code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
10280
- { 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)" },
10281
- { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10282
- { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10283
- { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10681
+ { code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" }
10284
10682
  ],
10285
10683
  check(ctx) {
10286
10684
  for (const impl of ctx.implementations) {
@@ -10303,8 +10701,7 @@ var init_portal_call_auth = __esm({
10303
10701
  draft
10304
10702
  );
10305
10703
  }
10306
- const from = step.auth?.from;
10307
- if (!from) {
10704
+ if (!step.auth?.from) {
10308
10705
  ctx.addIssue(
10309
10706
  "warning",
10310
10707
  "PORTAL_AUTH_UNMET",
@@ -10312,40 +10709,73 @@ var init_portal_call_auth = __esm({
10312
10709
  impl.id,
10313
10710
  draft
10314
10711
  );
10712
+ }
10713
+ }
10714
+ }
10715
+ }
10716
+ }
10717
+ };
10718
+ }
10719
+ });
10720
+
10721
+ // src/core/rules/doctrine/auth-source-wiring.ts
10722
+ var COMPONENT_REF_PREFIX, authSourceWiringRule;
10723
+ var init_auth_source_wiring = __esm({
10724
+ "src/core/rules/doctrine/auth-source-wiring.ts"() {
10725
+ "use strict";
10726
+ COMPONENT_REF_PREFIX = "component:";
10727
+ authSourceWiringRule = {
10728
+ name: "auth-source-wiring",
10729
+ description: "Resolves the credential source a narrative call presents to an authenticated Portal. When the step's `auth.from` is a modeled reference (`component:<id>`) it must name a component that exists (UNKNOWN_AUTH_SOURCE), that can hold or load a secret \u2014 an Adapter or a Store (AUTH_SOURCE_NOT_PROVIDER) \u2014 and that the presenting component depends on or owns (AUTH_SOURCE_UNWIRED). Opaque sources (env:/config:/vault:/free note) are design notes and are never resolved; a missing source is portal-call-auth's finding.",
10730
+ codes: [
10731
+ { code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
10732
+ { code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
10733
+ { code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
10734
+ ],
10735
+ check(ctx) {
10736
+ for (const impl of ctx.implementations) {
10737
+ const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
10738
+ const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
10739
+ const draft = ctx.isImplementationDraft(impl);
10740
+ for (const method2 of impl.methods ?? []) {
10741
+ for (const step of method2.narrative ?? []) {
10742
+ if (step.type !== "call") continue;
10743
+ if (!step.targetComponent || step.targetComponent === ownComponent) continue;
10744
+ const target = ctx.componentMap.get(step.targetComponent);
10745
+ if (!target || target.componentType !== "Portal") continue;
10746
+ if (!target.auth || target.auth.scheme === "none") continue;
10747
+ const from = step.auth?.from;
10748
+ if (!from || !from.startsWith(COMPONENT_REF_PREFIX)) continue;
10749
+ const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10750
+ const src = ctx.componentMap.get(srcId);
10751
+ if (!src) {
10752
+ ctx.addIssue(
10753
+ "warning",
10754
+ "UNKNOWN_AUTH_SOURCE",
10755
+ `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10756
+ impl.id,
10757
+ draft
10758
+ );
10315
10759
  continue;
10316
10760
  }
10317
- if (from.startsWith(COMPONENT_REF_PREFIX)) {
10318
- const srcId = from.slice(COMPONENT_REF_PREFIX.length);
10319
- const src = ctx.componentMap.get(srcId);
10320
- if (!src) {
10321
- ctx.addIssue(
10322
- "warning",
10323
- "UNKNOWN_AUTH_SOURCE",
10324
- `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
10325
- impl.id,
10326
- draft
10327
- );
10328
- } else {
10329
- if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10330
- ctx.addIssue(
10331
- "warning",
10332
- "AUTH_SOURCE_NOT_PROVIDER",
10333
- `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
10334
- impl.id,
10335
- draft
10336
- );
10337
- }
10338
- const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10339
- if (presenter && !wired) {
10340
- ctx.addIssue(
10341
- "warning",
10342
- "AUTH_SOURCE_UNWIRED",
10343
- `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
10344
- impl.id,
10345
- draft
10346
- );
10347
- }
10348
- }
10761
+ if (src.componentType !== "Adapter" && src.componentType !== "Store") {
10762
+ ctx.addIssue(
10763
+ "warning",
10764
+ "AUTH_SOURCE_NOT_PROVIDER",
10765
+ `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
10766
+ impl.id,
10767
+ draft
10768
+ );
10769
+ }
10770
+ const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
10771
+ if (presenter && !wired) {
10772
+ ctx.addIssue(
10773
+ "warning",
10774
+ "AUTH_SOURCE_UNWIRED",
10775
+ `Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
10776
+ impl.id,
10777
+ draft
10778
+ );
10349
10779
  }
10350
10780
  }
10351
10781
  }
@@ -10355,257 +10785,341 @@ var init_portal_call_auth = __esm({
10355
10785
  }
10356
10786
  });
10357
10787
 
10358
- // src/core/rules/doctrine/stereotype-dependencies.ts
10359
- var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf, stereotypeDepsRule;
10360
- var init_stereotype_dependencies = __esm({
10361
- "src/core/rules/doctrine/stereotype-dependencies.ts"() {
10788
+ // src/core/rules/doctrine/subsystem-boundary-dependencies.ts
10789
+ var subsystemBoundaryDepsRule;
10790
+ var init_subsystem_boundary_dependencies = __esm({
10791
+ "src/core/rules/doctrine/subsystem-boundary-dependencies.ts"() {
10362
10792
  "use strict";
10363
10793
  init_models();
10364
- 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.";
10365
- storeResolutionHint = (consumerType, storeId) => {
10366
- if (consumerType === "Orchestrator") {
10367
- return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
10368
- }
10369
- return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
10370
- };
10371
- isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
10372
- isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
10373
- stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
10374
- stereotypeDepsRule = {
10375
- name: "stereotype-dependencies",
10376
- 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.",
10794
+ subsystemBoundaryDepsRule = {
10795
+ name: "subsystem-boundary-dependencies",
10796
+ 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.",
10377
10797
  codes: [
10378
10798
  { code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
10379
10799
  { code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
10380
10800
  { code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
10381
10801
  { code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
10382
10802
  { code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
10383
- { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" },
10384
- { code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
10385
- { code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
10386
- { code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
10387
- { 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)" },
10388
- { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
10389
- { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
10390
- { code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
10391
- { 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)" },
10392
- { 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" },
10393
- { 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" },
10394
- { 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" },
10395
- { code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
10803
+ { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
10396
10804
  ],
10397
10805
  check(ctx) {
10398
- for (const comp of ctx.components) {
10399
- const isDraftCtx = ctx.isComponentDraft(comp.id);
10400
- const dependencies = comp.dependsOn;
10401
- for (const depId of dependencies) {
10402
- const depComp = ctx.componentMap.get(depId);
10403
- if (!depComp) {
10404
- const external = ctx.isExternalNamespaceRef(depId);
10405
- if (external || ctx.isCollapsedCrossTreeRef(depId, comp.subsystem)) {
10406
- const resolved = ctx.resolveSurfaceRef(depId, comp.subsystem);
10407
- if (resolved.kind === "ambiguous") {
10408
- ctx.addIssue("error", "SURFACE_REF_AMBIGUOUS", ambiguityMessage(resolved, `Component "${comp.id}" depends on`, depId), comp.id, isDraftCtx);
10409
- continue;
10410
- }
10411
- if (resolved.kind === "resolved") {
10412
- if (comp.componentType !== "Adapter") {
10413
- ctx.addIssue(
10414
- "error",
10415
- "CROSS_SUBSYSTEM_NON_ADAPTER",
10416
- `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.`,
10417
- comp.id,
10418
- isDraftCtx,
10419
- true
10420
- );
10421
- }
10422
- continue;
10423
- }
10424
- if (external) {
10425
- ctx.addIssue(
10426
- "warning",
10427
- "CROSS_TREE_REF_UNRESOLVED",
10428
- `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.`,
10429
- comp.id,
10430
- isDraftCtx
10431
- );
10432
- continue;
10433
- }
10434
- }
10806
+ for (const edge of ctx.dependencyEdges().all) {
10807
+ if (edge.retired) continue;
10808
+ const comp = edge.from;
10809
+ switch (edge.reach) {
10810
+ case "missing":
10435
10811
  ctx.addIssue(
10436
10812
  "error",
10437
10813
  "INVALID_DEPENDENCY_REFERENCE",
10438
- `Component "${comp.id}" lists dependency "${depId}" which does not exist.`,
10814
+ `Component "${comp.id}" lists dependency "${edge.ref}" which does not exist.`,
10439
10815
  comp.id,
10440
- isDraftCtx
10816
+ edge.draftContext
10441
10817
  );
10442
10818
  continue;
10443
- }
10444
- if (isRetired(comp) || isRetired(depComp)) continue;
10445
- if (depComp.subsystem !== comp.subsystem) {
10446
- const crossDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
10447
- const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
10448
- const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
10449
- if (comp.componentType !== "Adapter" && !licensed) {
10450
- ctx.addIssue(
10451
- "error",
10452
- "CROSS_SUBSYSTEM_NON_ADAPTER",
10453
- `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.`,
10454
- comp.id,
10455
- crossDraft
10456
- );
10457
- }
10458
- const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
10459
- if (!targetIsPublic) {
10460
- ctx.addIssue(
10461
- "error",
10462
- "CROSS_SUBSYSTEM_PRIVATE_ACCESS",
10463
- `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.`,
10464
- comp.id,
10465
- crossDraft
10466
- );
10467
- } else if (depComp.componentType !== "Portal") {
10468
- const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
10469
- ctx.addIssue(
10470
- "error",
10471
- "CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
10472
- `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.`,
10473
- comp.id,
10474
- crossDraft
10475
- );
10476
- }
10477
- continue;
10478
- }
10479
- const governingProfile2 = ctx.ext.profiles[ctx.getComponentProfile(comp.id)];
10480
- if (governingProfile2?.allowedEdges?.some((e) => e.from.includes(comp.componentType) && e.to.includes(depComp.componentType))) {
10819
+ case "unpinned":
10820
+ ctx.addIssue(
10821
+ "warning",
10822
+ "CROSS_TREE_REF_UNRESOLVED",
10823
+ `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.`,
10824
+ comp.id,
10825
+ edge.draftContext
10826
+ );
10481
10827
  continue;
10482
- }
10483
- const edgeDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
10484
- const depPure = isPureLogic(depComp);
10485
- const depRead = isReadLogic(depComp);
10486
- const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10487
- if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
10828
+ case "ambiguous":
10488
10829
  ctx.addIssue(
10489
10830
  "error",
10490
- "ARCHITECTURE_VIOLATION_PORTAL_DEP",
10491
- `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.`,
10831
+ "SURFACE_REF_AMBIGUOUS",
10832
+ ambiguityMessage(edge.surface, `Component "${comp.id}" depends on`, edge.ref),
10492
10833
  comp.id,
10493
- edgeDraft
10834
+ edge.draftContext
10494
10835
  );
10495
10836
  continue;
10496
- }
10497
- if (comp.componentType === "Portal" || comp.componentType === "Observer") {
10498
- const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
10499
- if (forbiddenTypes.includes(depComp.componentType)) {
10837
+ case "surface":
10838
+ if (comp.componentType !== "Adapter") {
10839
+ const provider = edge.surface.kind === "resolved" ? edge.surface.snapshot.projectName : "";
10500
10840
  ctx.addIssue(
10501
10841
  "error",
10502
- "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
10503
- `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,
10842
+ "CROSS_SUBSYSTEM_NON_ADAPTER",
10843
+ `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.`,
10504
10844
  comp.id,
10505
- edgeDraft
10845
+ edge.draftContext,
10846
+ true
10506
10847
  );
10507
10848
  }
10508
- }
10509
- if (isPureLogic(comp) && !depPure) {
10510
- ctx.addIssue(
10511
- "error",
10512
- "DEPENDENCY_CLASS_VIOLATION",
10513
- `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,
10514
- comp.id,
10515
- edgeDraft
10516
- );
10517
- }
10518
- if (isReadLogic(comp) && !depPure && !depRead && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
10519
- ctx.addIssue(
10520
- "error",
10521
- "DEPENDENCY_CLASS_VIOLATION",
10522
- `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,
10523
- comp.id,
10524
- edgeDraft
10525
- );
10526
- }
10527
- if (comp.componentType === "Store" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10528
- ctx.addIssue(
10529
- "error",
10530
- "ARCHITECTURE_VIOLATION_STORE_DEP",
10531
- `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.`,
10532
- comp.id,
10533
- edgeDraft
10534
- );
10535
- }
10536
- if (comp.componentType === "Registry" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10537
- ctx.addIssue(
10538
- "warning",
10539
- "ARCHITECTURE_VIOLATION_REGISTRY_DEP",
10540
- `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.`,
10541
- comp.id,
10542
- edgeDraft
10543
- );
10544
- }
10545
- if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
10546
- ctx.addIssue(
10547
- "error",
10548
- "ARCHITECTURE_VIOLATION_ADAPTER_DEP",
10549
- `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.`,
10550
- comp.id,
10551
- edgeDraft
10552
- );
10553
- }
10554
- if (comp.componentType === "Index" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10555
- ctx.addIssue(
10556
- "error",
10557
- "ARCHITECTURE_VIOLATION_INDEX_DEP",
10558
- `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.`,
10559
- comp.id,
10560
- edgeDraft
10561
- );
10562
- }
10563
- if (comp.componentType === "Query" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10564
- ctx.addIssue(
10565
- "error",
10566
- "ARCHITECTURE_VIOLATION_QUERY_DEP",
10567
- `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.`,
10568
- comp.id,
10569
- edgeDraft
10570
- );
10571
- }
10572
- if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !depPure)) {
10849
+ continue;
10850
+ case "cross-subsystem":
10851
+ break;
10852
+ default:
10853
+ continue;
10854
+ }
10855
+ const depComp = edge.to;
10856
+ const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
10857
+ const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
10858
+ if (comp.componentType !== "Adapter" && !licensed) {
10859
+ ctx.addIssue(
10860
+ "error",
10861
+ "CROSS_SUBSYSTEM_NON_ADAPTER",
10862
+ `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.`,
10863
+ comp.id,
10864
+ edge.draftContext
10865
+ );
10866
+ }
10867
+ const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
10868
+ if (!targetIsPublic) {
10869
+ ctx.addIssue(
10870
+ "error",
10871
+ "CROSS_SUBSYSTEM_PRIVATE_ACCESS",
10872
+ `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.`,
10873
+ comp.id,
10874
+ edge.draftContext
10875
+ );
10876
+ } else if (depComp.componentType !== "Portal") {
10877
+ const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
10878
+ ctx.addIssue(
10879
+ "error",
10880
+ "CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
10881
+ `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.`,
10882
+ comp.id,
10883
+ edge.draftContext
10884
+ );
10885
+ }
10886
+ }
10887
+ }
10888
+ };
10889
+ }
10890
+ });
10891
+
10892
+ // src/core/rules/doctrine/stereotype-terms.ts
10893
+ var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf;
10894
+ var init_stereotype_terms = __esm({
10895
+ "src/core/rules/doctrine/stereotype-terms.ts"() {
10896
+ "use strict";
10897
+ 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.";
10898
+ storeResolutionHint = (consumerType, storeId) => {
10899
+ if (consumerType === "Orchestrator") {
10900
+ return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
10901
+ }
10902
+ return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
10903
+ };
10904
+ isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
10905
+ isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
10906
+ stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
10907
+ }
10908
+ });
10909
+
10910
+ // src/core/rules/doctrine/logic-dependency-class.ts
10911
+ var logicDependencyClassRule;
10912
+ var init_logic_dependency_class = __esm({
10913
+ "src/core/rules/doctrine/logic-dependency-class.ts"() {
10914
+ "use strict";
10915
+ init_stereotype_terms();
10916
+ logicDependencyClassRule = {
10917
+ name: "logic-dependency-class",
10918
+ 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.",
10919
+ codes: [
10920
+ { 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" }
10921
+ ],
10922
+ check(ctx) {
10923
+ for (const edge of ctx.dependencyEdges().matrix) {
10924
+ const comp = edge.from;
10925
+ const depComp = edge.to;
10926
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
10927
+ const depPure = isPureLogic(depComp);
10928
+ const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10929
+ if (isPureLogic(comp) && !depPure) {
10930
+ ctx.addIssue(
10931
+ "error",
10932
+ "DEPENDENCY_CLASS_VIOLATION",
10933
+ `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,
10934
+ comp.id,
10935
+ edge.draftContext
10936
+ );
10937
+ }
10938
+ if (isReadLogic(comp) && !depPure && !isReadLogic(depComp) && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
10939
+ ctx.addIssue(
10940
+ "error",
10941
+ "DEPENDENCY_CLASS_VIOLATION",
10942
+ `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,
10943
+ comp.id,
10944
+ edge.draftContext
10945
+ );
10946
+ }
10947
+ }
10948
+ }
10949
+ };
10950
+ }
10951
+ });
10952
+
10953
+ // src/core/rules/doctrine/data-block-dependencies.ts
10954
+ var dataBlockDepsRule;
10955
+ var init_data_block_dependencies = __esm({
10956
+ "src/core/rules/doctrine/data-block-dependencies.ts"() {
10957
+ "use strict";
10958
+ init_stereotype_terms();
10959
+ dataBlockDepsRule = {
10960
+ name: "data-block-dependencies",
10961
+ 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.",
10962
+ codes: [
10963
+ { code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
10964
+ { 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)" },
10965
+ { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
10966
+ { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
10967
+ { code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
10968
+ ],
10969
+ check(ctx) {
10970
+ for (const edge of ctx.dependencyEdges().matrix) {
10971
+ const comp = edge.from;
10972
+ const depComp = edge.to;
10973
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
10974
+ const depPure = isPureLogic(depComp);
10975
+ const ownStoreOnly = depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure;
10976
+ if (comp.componentType === "Store" && ownStoreOnly) {
10977
+ ctx.addIssue(
10978
+ "error",
10979
+ "ARCHITECTURE_VIOLATION_STORE_DEP",
10980
+ `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.`,
10981
+ comp.id,
10982
+ edge.draftContext
10983
+ );
10984
+ }
10985
+ if (comp.componentType === "Registry" && ownStoreOnly) {
10986
+ ctx.addIssue(
10987
+ "warning",
10988
+ "ARCHITECTURE_VIOLATION_REGISTRY_DEP",
10989
+ `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.`,
10990
+ comp.id,
10991
+ edge.draftContext
10992
+ );
10993
+ }
10994
+ if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
10995
+ ctx.addIssue(
10996
+ "error",
10997
+ "ARCHITECTURE_VIOLATION_ADAPTER_DEP",
10998
+ `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.`,
10999
+ comp.id,
11000
+ edge.draftContext
11001
+ );
11002
+ }
11003
+ if (comp.componentType === "Index" && ownStoreOnly) {
11004
+ ctx.addIssue(
11005
+ "error",
11006
+ "ARCHITECTURE_VIOLATION_INDEX_DEP",
11007
+ `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.`,
11008
+ comp.id,
11009
+ edge.draftContext
11010
+ );
11011
+ }
11012
+ if (comp.componentType === "Query" && ownStoreOnly) {
11013
+ ctx.addIssue(
11014
+ "error",
11015
+ "ARCHITECTURE_VIOLATION_QUERY_DEP",
11016
+ `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.`,
11017
+ comp.id,
11018
+ edge.draftContext
11019
+ );
11020
+ }
11021
+ }
11022
+ }
11023
+ };
11024
+ }
11025
+ });
11026
+
11027
+ // src/core/rules/doctrine/entrypoint-dependencies.ts
11028
+ var entrypointDepsRule;
11029
+ var init_entrypoint_dependencies = __esm({
11030
+ "src/core/rules/doctrine/entrypoint-dependencies.ts"() {
11031
+ "use strict";
11032
+ init_stereotype_terms();
11033
+ entrypointDepsRule = {
11034
+ name: "entrypoint-dependencies",
11035
+ 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.",
11036
+ codes: [
11037
+ { code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
11038
+ { code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
11039
+ { code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
11040
+ { 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" },
11041
+ { 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" }
11042
+ ],
11043
+ check(ctx) {
11044
+ for (const edge of ctx.dependencyEdges().matrix) {
11045
+ const comp = edge.from;
11046
+ const depComp = edge.to;
11047
+ const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
11048
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
11049
+ ctx.addIssue(
11050
+ "error",
11051
+ "ARCHITECTURE_VIOLATION_PORTAL_DEP",
11052
+ `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.`,
11053
+ comp.id,
11054
+ edge.draftContext
11055
+ );
11056
+ continue;
11057
+ }
11058
+ if (comp.componentType === "Portal" || comp.componentType === "Observer") {
11059
+ const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
11060
+ if (forbiddenTypes.includes(depComp.componentType)) {
10573
11061
  ctx.addIssue(
10574
11062
  "error",
10575
- "ARCHITECTURE_VIOLATION_VIEW_DEP",
10576
- `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,
11063
+ "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
11064
+ `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,
10577
11065
  comp.id,
10578
- edgeDraft
11066
+ edge.draftContext
10579
11067
  );
10580
11068
  }
10581
- if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
11069
+ }
11070
+ if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !isPureLogic(depComp))) {
11071
+ ctx.addIssue(
11072
+ "error",
11073
+ "ARCHITECTURE_VIOLATION_VIEW_DEP",
11074
+ `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,
11075
+ comp.id,
11076
+ edge.draftContext
11077
+ );
11078
+ }
11079
+ if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
11080
+ ctx.addIssue(
11081
+ "error",
11082
+ "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
11083
+ `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,
11084
+ comp.id,
11085
+ edge.draftContext
11086
+ );
11087
+ }
11088
+ if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
11089
+ const reachedThroughSupervisor = comp.dependsOn.some((id) => {
11090
+ const supervisor = ctx.componentMap.get(id);
11091
+ return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
11092
+ });
11093
+ if (!reachedThroughSupervisor) {
11094
+ const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
11095
+ 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`;
10582
11096
  ctx.addIssue(
10583
11097
  "error",
10584
- "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
10585
- `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,
11098
+ "ACTOR_REACHED_WITHOUT_SUPERVISOR",
11099
+ `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}.`,
10586
11100
  comp.id,
10587
- edgeDraft
11101
+ edge.draftContext
10588
11102
  );
10589
11103
  }
10590
- if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
10591
- const reachedThroughSupervisor = comp.dependsOn.some((id) => {
10592
- const supervisor = ctx.componentMap.get(id);
10593
- return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
10594
- });
10595
- if (!reachedThroughSupervisor) {
10596
- const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
10597
- 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`;
10598
- ctx.addIssue(
10599
- "error",
10600
- "ACTOR_REACHED_WITHOUT_SUPERVISOR",
10601
- `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}.`,
10602
- comp.id,
10603
- edgeDraft
10604
- );
10605
- }
10606
- }
10607
11104
  }
10608
11105
  }
11106
+ }
11107
+ };
11108
+ }
11109
+ });
11110
+
11111
+ // src/core/rules/doctrine/portal-write-shortcut.ts
11112
+ var portalWriteShortcutRule;
11113
+ var init_portal_write_shortcut = __esm({
11114
+ "src/core/rules/doctrine/portal-write-shortcut.ts"() {
11115
+ "use strict";
11116
+ portalWriteShortcutRule = {
11117
+ name: "portal-write-shortcut",
11118
+ 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.",
11119
+ codes: [
11120
+ { 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)" }
11121
+ ],
11122
+ check(ctx) {
10609
11123
  for (const impl of ctx.implementations) {
10610
11124
  const contract = ctx.interfaceMap.get(impl.contract);
10611
11125
  const component = contract ? ctx.componentMap.get(contract.component) : void 0;
@@ -10650,31 +11164,23 @@ var init_stereotype_dependencies = __esm({
10650
11164
  }
10651
11165
  });
10652
11166
 
10653
- // src/core/rules/doctrine/pattern-ownership.ts
10654
- var patternsRule;
10655
- var init_pattern_ownership = __esm({
10656
- "src/core/rules/doctrine/pattern-ownership.ts"() {
11167
+ // src/core/rules/doctrine/pattern-membership.ts
11168
+ var patternMembershipRule;
11169
+ var init_pattern_membership = __esm({
11170
+ "src/core/rules/doctrine/pattern-membership.ts"() {
10657
11171
  "use strict";
10658
11172
  init_models();
10659
- patternsRule = {
10660
- name: "pattern-ownership",
10661
- 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.",
11173
+ patternMembershipRule = {
11174
+ name: "pattern-membership",
11175
+ 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.",
10662
11176
  codes: [
10663
11177
  { code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
10664
11178
  { code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block using owns" },
10665
11179
  { code: "INVALID_OWNED_MEMBER", defaultSeverity: "error", summary: "owns names a non-existent component" },
10666
11180
  { code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
10667
- { code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" },
10668
- { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
10669
- { code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
10670
- { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
10671
- { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
10672
- { 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" },
10673
- { 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" },
10674
- { code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
11181
+ { code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
10675
11182
  ],
10676
11183
  check(ctx) {
10677
- const ownedBy = /* @__PURE__ */ new Map();
10678
11184
  for (const comp of ctx.components) {
10679
11185
  if (isRetired(comp)) continue;
10680
11186
  const isDraftCtx = ctx.isComponentDraft(comp.id);
@@ -10685,6 +11191,12 @@ var init_pattern_ownership = __esm({
10685
11191
  if (!pattern && comp.owns.length > 0) {
10686
11192
  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);
10687
11193
  }
11194
+ }
11195
+ const ownership = ctx.ownershipIndex();
11196
+ for (const comp of ctx.components) {
11197
+ if (isRetired(comp)) continue;
11198
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11199
+ const pattern = isPattern(comp);
10688
11200
  for (const memberId of comp.owns) {
10689
11201
  const member = ctx.componentMap.get(memberId);
10690
11202
  if (!member) {
@@ -10696,26 +11208,50 @@ var init_pattern_ownership = __esm({
10696
11208
  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);
10697
11209
  continue;
10698
11210
  }
10699
- const firstOwner = ownedBy.get(memberId);
11211
+ const firstOwner = ownership.ownerOf(memberId);
10700
11212
  if (firstOwner && firstOwner !== comp.id) {
10701
11213
  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);
10702
11214
  }
10703
- if (!firstOwner) ownedBy.set(memberId, comp.id);
10704
11215
  }
10705
- if ((comp.componentType === "FeatureComponent" || comp.componentType === "RouterComponent") && comp.owns.some((memberId) => {
11216
+ }
11217
+ }
11218
+ };
11219
+ }
11220
+ });
11221
+
11222
+ // src/core/rules/doctrine/pattern-containment.ts
11223
+ var REPOSITORY_MEMBERS, patternContainmentRule;
11224
+ var init_pattern_containment = __esm({
11225
+ "src/core/rules/doctrine/pattern-containment.ts"() {
11226
+ "use strict";
11227
+ init_models();
11228
+ REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
11229
+ patternContainmentRule = {
11230
+ name: "pattern-containment",
11231
+ 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.",
11232
+ codes: [
11233
+ { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
11234
+ { code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
11235
+ { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" }
11236
+ ],
11237
+ check(ctx) {
11238
+ for (const comp of ctx.components) {
11239
+ if (comp.componentType !== "Repository" || isRetired(comp)) continue;
11240
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11241
+ for (const memberId of comp.owns) {
10706
11242
  const member = ctx.componentMap.get(memberId);
10707
- return member !== void 0 && isRetired(member);
10708
- })) {
10709
- continue;
10710
- }
10711
- if (comp.componentType === "Repository") {
10712
- const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
10713
- for (const memberId of comp.owns) {
10714
- const member = ctx.componentMap.get(memberId);
10715
- if (!member || isRetired(member) || allowed.has(member.componentType)) continue;
10716
- 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);
10717
- }
11243
+ if (!member || isRetired(member) || REPOSITORY_MEMBERS.has(member.componentType)) continue;
11244
+ 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);
10718
11245
  }
11246
+ }
11247
+ for (const comp of ctx.components) {
11248
+ if (comp.componentType !== "FeatureComponent" && comp.componentType !== "RouterComponent") continue;
11249
+ if (isRetired(comp)) continue;
11250
+ if (comp.owns.some((memberId) => {
11251
+ const member = ctx.componentMap.get(memberId);
11252
+ return member !== void 0 && isRetired(member);
11253
+ })) continue;
11254
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10719
11255
  if (comp.componentType === "FeatureComponent") {
10720
11256
  let orchestrators = 0;
10721
11257
  let views = 0;
@@ -10746,45 +11282,83 @@ var init_pattern_ownership = __esm({
10746
11282
  }
10747
11283
  }
10748
11284
  }
11285
+ }
11286
+ };
11287
+ }
11288
+ });
11289
+
11290
+ // src/core/rules/doctrine/unowned-blocks.ts
11291
+ var unownedBlocksRule;
11292
+ var init_unowned_blocks = __esm({
11293
+ "src/core/rules/doctrine/unowned-blocks.ts"() {
11294
+ "use strict";
11295
+ unownedBlocksRule = {
11296
+ name: "unowned-blocks",
11297
+ 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.`,
11298
+ codes: [
11299
+ { 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" },
11300
+ { 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" },
11301
+ { code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
11302
+ ],
11303
+ check(ctx) {
11304
+ const ownership = ctx.ownershipIndex();
10749
11305
  for (const comp of ctx.components) {
10750
- if (comp.componentType !== "Store" || ownedBy.has(comp.id)) continue;
10751
- ctx.addIssue(
10752
- "warning",
10753
- "UNOWNED_STORE",
10754
- `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.`,
10755
- comp.id,
10756
- ctx.isComponentDraft(comp.id)
10757
- );
10758
- }
10759
- for (const comp of ctx.components) {
10760
- if (comp.componentType !== "Registry" || ownedBy.has(comp.id)) continue;
10761
- const hasStoreDep = comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store");
10762
- if (hasStoreDep) continue;
10763
- ctx.addIssue(
10764
- "warning",
10765
- "REGISTRY_WITHOUT_STORE",
10766
- `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.`,
10767
- comp.id,
10768
- ctx.isComponentDraft(comp.id)
10769
- );
10770
- }
10771
- for (const comp of ctx.components) {
10772
- if (comp.componentType !== "Query" || ownedBy.has(comp.id)) continue;
10773
- ctx.addIssue(
10774
- "error",
10775
- "UNOWNED_QUERY",
10776
- `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]).`,
10777
- comp.id,
10778
- ctx.isComponentDraft(comp.id)
10779
- );
11306
+ if (ownership.ownedMembers.has(comp.id)) continue;
11307
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11308
+ if (comp.componentType === "Store") {
11309
+ ctx.addIssue(
11310
+ "warning",
11311
+ "UNOWNED_STORE",
11312
+ `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.`,
11313
+ comp.id,
11314
+ isDraftCtx
11315
+ );
11316
+ }
11317
+ if (comp.componentType === "Registry" && !comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store")) {
11318
+ ctx.addIssue(
11319
+ "warning",
11320
+ "REGISTRY_WITHOUT_STORE",
11321
+ `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.`,
11322
+ comp.id,
11323
+ isDraftCtx
11324
+ );
11325
+ }
11326
+ if (comp.componentType === "Query") {
11327
+ ctx.addIssue(
11328
+ "error",
11329
+ "UNOWNED_QUERY",
11330
+ `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]).`,
11331
+ comp.id,
11332
+ isDraftCtx
11333
+ );
11334
+ }
10780
11335
  }
11336
+ }
11337
+ };
11338
+ }
11339
+ });
11340
+
11341
+ // src/core/rules/doctrine/member-visibility.ts
11342
+ var memberVisibilityRule;
11343
+ var init_member_visibility = __esm({
11344
+ "src/core/rules/doctrine/member-visibility.ts"() {
11345
+ "use strict";
11346
+ init_models();
11347
+ memberVisibilityRule = {
11348
+ name: "member-visibility",
11349
+ 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.",
11350
+ codes: [
11351
+ { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
11352
+ ],
11353
+ check(ctx) {
11354
+ const ownership = ctx.ownershipIndex();
10781
11355
  for (const comp of ctx.components) {
10782
11356
  if (isRetired(comp)) continue;
10783
11357
  for (const depId of comp.dependsOn) {
10784
- const owner = ownedBy.get(depId);
11358
+ const owner = ownership.ownerOf(depId);
10785
11359
  if (!owner) continue;
10786
11360
  if (owner === comp.id) continue;
10787
- if (ownedBy.get(comp.id) === owner) continue;
11361
+ if (ownership.ownerOf(comp.id) === owner) continue;
10788
11362
  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));
10789
11363
  }
10790
11364
  }
@@ -11077,17 +11651,135 @@ var init_component_variants = __esm({
11077
11651
  }
11078
11652
  });
11079
11653
 
11080
- // src/core/rules/extension/declarative-assertions.ts
11654
+ // src/core/rules/extension/declared-assertion.ts
11655
+ function assertionsOfKind(ctx, kind) {
11656
+ return ctx.ext.assertions.filter(
11657
+ (a) => a.kind === kind
11658
+ );
11659
+ }
11081
11660
  function globToRegExp(glob) {
11082
11661
  const escaped = glob.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? ".*" : `\\${ch}`);
11083
11662
  return new RegExp(`^${escaped}$`);
11084
11663
  }
11085
- function matches(sel, comp, ctx) {
11664
+ function matchesSelector(sel, comp, ctx) {
11086
11665
  if (sel.componentType?.length && !sel.componentType.includes(comp.componentType)) return false;
11087
11666
  if (sel.profile?.length && !sel.profile.includes(ctx.getComponentProfile(comp.id))) return false;
11088
11667
  if (sel.id && !globToRegExp(sel.id).test(comp.id)) return false;
11089
11668
  return true;
11090
11669
  }
11670
+ function reportAssertion(ctx, a, message, specId, isDraftCtx) {
11671
+ const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11672
+ ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11673
+ }
11674
+ var init_declared_assertion = __esm({
11675
+ "src/core/rules/extension/declared-assertion.ts"() {
11676
+ "use strict";
11677
+ }
11678
+ });
11679
+
11680
+ // src/core/rules/extension/assertion-forbidden-edges.ts
11681
+ var assertionForbiddenEdgesRule;
11682
+ var init_assertion_forbidden_edges = __esm({
11683
+ "src/core/rules/extension/assertion-forbidden-edges.ts"() {
11684
+ "use strict";
11685
+ init_declared_assertion();
11686
+ assertionForbiddenEdgesRule = {
11687
+ name: "assertion-forbidden-edges",
11688
+ description: "Evaluates the `forbid-edge` assertions loaded packs declare: for every component matching the assertion's from-selector, every dependsOn/owns id whose target matches the to-selector is reported. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11689
+ // Static codes are unknown here — packs bring their own. The validator
11690
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11691
+ codes: [],
11692
+ check(ctx) {
11693
+ for (const a of assertionsOfKind(ctx, "forbid-edge")) {
11694
+ for (const comp of ctx.components) {
11695
+ if (!matchesSelector(a.from, comp, ctx)) continue;
11696
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11697
+ const edges = a.relation.flatMap(
11698
+ (relation) => comp[relation].map((targetId) => ({ relation, targetId }))
11699
+ );
11700
+ for (const { relation, targetId } of edges) {
11701
+ const target = ctx.componentMap.get(targetId);
11702
+ if (!target || !matchesSelector(a.to, target, ctx)) continue;
11703
+ reportAssertion(
11704
+ ctx,
11705
+ a,
11706
+ `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`,
11707
+ comp.id,
11708
+ isDraftCtx
11709
+ );
11710
+ }
11711
+ }
11712
+ }
11713
+ }
11714
+ };
11715
+ }
11716
+ });
11717
+
11718
+ // src/core/rules/extension/assertion-required-fields.ts
11719
+ function fieldValue(spec, field) {
11720
+ const segments = field.split(".");
11721
+ if (segments[0] !== "ext") {
11722
+ return segments.length === 1 ? spec[field] : void 0;
11723
+ }
11724
+ let cur = spec.ext;
11725
+ for (const seg of segments.slice(1)) {
11726
+ if (cur === null || typeof cur !== "object") return void 0;
11727
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11728
+ cur = cur[seg];
11729
+ }
11730
+ return cur;
11731
+ }
11732
+ function holdersAt(ctx, level) {
11733
+ const holders = [];
11734
+ if (level === "component") {
11735
+ for (const c of ctx.components) {
11736
+ holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11737
+ }
11738
+ } else if (level === "interface") {
11739
+ for (const i of ctx.interfaces) {
11740
+ const comp = ctx.componentMap.get(i.component);
11741
+ if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11742
+ }
11743
+ } else {
11744
+ for (const impl of ctx.implementations) {
11745
+ const contract = ctx.interfaceMap.get(impl.contract);
11746
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11747
+ if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11748
+ }
11749
+ }
11750
+ return holders;
11751
+ }
11752
+ var assertionRequiredFieldsRule;
11753
+ var init_assertion_required_fields = __esm({
11754
+ "src/core/rules/extension/assertion-required-fields.ts"() {
11755
+ "use strict";
11756
+ init_declared_assertion();
11757
+ assertionRequiredFieldsRule = {
11758
+ name: "assertion-required-fields",
11759
+ description: "Evaluates the `require-field` assertions loaded packs declare: at the assertion's level (component / interface / implementation), every spec whose owning component matches the on-selector must declare the named field \u2014 a top-level spec field or an `ext.*` path \u2014 and, when the assertion states a closed value set, hold one of those values. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11760
+ // Static codes are unknown here — packs bring their own. The validator
11761
+ // gathers every loaded assertion's fullCode into knownIssueCodes.
11762
+ codes: [],
11763
+ check(ctx) {
11764
+ for (const a of assertionsOfKind(ctx, "require-field")) {
11765
+ for (const h of holdersAt(ctx, a.level)) {
11766
+ if (!matchesSelector(a.on, h.comp, ctx)) continue;
11767
+ const v = fieldValue(h.spec, a.field);
11768
+ if (v === void 0 || v === null) {
11769
+ reportAssertion(ctx, a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11770
+ continue;
11771
+ }
11772
+ if (a.values && !a.values.includes(String(v))) {
11773
+ reportAssertion(ctx, a, `${a.level} spec "${h.specId}" declares "${a.field}" = "${String(v)}", outside the allowed set (${a.values.join(", ")}) of assertion ${a.code}`, h.specId, h.draft);
11774
+ }
11775
+ }
11776
+ }
11777
+ }
11778
+ };
11779
+ }
11780
+ });
11781
+
11782
+ // src/core/rules/extension/assertion-endpoint-shapes.ts
11091
11783
  function endpointAddress(ep) {
11092
11784
  switch (ep.transport) {
11093
11785
  case "HTTP":
@@ -11108,92 +11800,42 @@ function endpointAddress(ep) {
11108
11800
  return ep.address;
11109
11801
  }
11110
11802
  }
11111
- function fieldValue(spec, field) {
11112
- const segments = field.split(".");
11113
- if (segments[0] !== "ext") {
11114
- return segments.length === 1 ? spec[field] : void 0;
11115
- }
11116
- let cur = spec.ext;
11117
- for (const seg of segments.slice(1)) {
11118
- if (cur === null || typeof cur !== "object") return void 0;
11119
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
11120
- cur = cur[seg];
11803
+ function boundEndpoints(ctx, on) {
11804
+ const out = [];
11805
+ for (const intf of ctx.interfaces) {
11806
+ const comp = ctx.componentMap.get(intf.component);
11807
+ if (!comp || !matchesSelector(on, comp, ctx)) continue;
11808
+ const draft = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11809
+ for (const method2 of intf.methods) {
11810
+ if (!method2.endpoint) continue;
11811
+ out.push({ intf, method: method2, endpoint: method2.endpoint, draft });
11812
+ }
11121
11813
  }
11122
- return cur;
11814
+ return out;
11123
11815
  }
11124
- var declarativeAssertionsRule;
11125
- var init_declarative_assertions = __esm({
11126
- "src/core/rules/extension/declarative-assertions.ts"() {
11816
+ var assertionEndpointShapesRule;
11817
+ var init_assertion_endpoint_shapes = __esm({
11818
+ "src/core/rules/extension/assertion-endpoint-shapes.ts"() {
11127
11819
  "use strict";
11128
- declarativeAssertionsRule = {
11129
- name: "declarative-assertions",
11130
- description: "Evaluates the declarative rule assertions loaded packs declare (forbid-edge / require-field / endpoint-shape) \u2014 closed kinds instantiated with pack data, the hosted-safe doctrine channel. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11820
+ init_declared_assertion();
11821
+ assertionEndpointShapesRule = {
11822
+ name: "assertion-endpoint-shapes",
11823
+ description: "Evaluates the `endpoint-shape` assertions loaded packs declare: every endpoint bound on a contract whose component matches the on-selector must use an allowed transport and bind an address (path / topic / command / pipe / channel) matching the assertion's anchored pattern. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
11131
11824
  // Static codes are unknown here — packs bring their own. The validator
11132
11825
  // gathers every loaded assertion's fullCode into knownIssueCodes.
11133
11826
  codes: [],
11134
11827
  check(ctx) {
11135
- const assertions = ctx.ext.assertions;
11136
- if (!assertions.length) return;
11137
- const emit = (a, message, specId, isDraftCtx) => {
11138
- const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
11139
- ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
11140
- };
11141
- for (const a of assertions) {
11142
- if (a.kind === "forbid-edge") {
11143
- for (const comp of ctx.components) {
11144
- if (!matches(a.from, comp, ctx)) continue;
11145
- const isDraftCtx = ctx.isComponentDraft(comp.id);
11146
- for (const relation of a.relation) {
11147
- for (const targetId of comp[relation]) {
11148
- const target = ctx.componentMap.get(targetId);
11149
- if (!target || !matches(a.to, target, ctx)) continue;
11150
- emit(a, `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`, comp.id, isDraftCtx);
11151
- }
11152
- }
11153
- }
11154
- } else if (a.kind === "require-field") {
11155
- const holders = [];
11156
- if (a.level === "component") {
11157
- for (const c of ctx.components) holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
11158
- } else if (a.level === "interface") {
11159
- for (const i of ctx.interfaces) {
11160
- const comp = ctx.componentMap.get(i.component);
11161
- if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
11162
- }
11163
- } else {
11164
- for (const impl of ctx.implementations) {
11165
- const contract = ctx.interfaceMap.get(impl.contract);
11166
- const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
11167
- if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
11168
- }
11169
- }
11170
- for (const h of holders) {
11171
- if (!matches(a.on, h.comp, ctx)) continue;
11172
- const v = fieldValue(h.spec, a.field);
11173
- if (v === void 0 || v === null) {
11174
- emit(a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
11175
- } else if (a.values && !a.values.includes(String(v))) {
11176
- 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);
11177
- }
11828
+ for (const a of assertionsOfKind(ctx, "endpoint-shape")) {
11829
+ const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11830
+ for (const bound2 of boundEndpoints(ctx, a.on)) {
11831
+ const { intf, method: method2, endpoint } = bound2;
11832
+ if (a.transport?.length && !a.transport.includes(endpoint.transport)) {
11833
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method2.name}" uses transport ${endpoint.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, bound2.draft);
11834
+ continue;
11178
11835
  }
11179
- } else if (a.kind === "endpoint-shape") {
11180
- const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
11181
- for (const intf of ctx.interfaces) {
11182
- const comp = ctx.componentMap.get(intf.component);
11183
- if (!comp || !matches(a.on, comp, ctx)) continue;
11184
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11185
- for (const method2 of intf.methods) {
11186
- if (!method2.endpoint) continue;
11187
- const ep = method2.endpoint;
11188
- if (a.transport?.length && !a.transport.includes(ep.transport)) {
11189
- emit(a, `Endpoint of "${intf.id}.${method2.name}" uses transport ${ep.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, isDraftCtx);
11190
- continue;
11191
- }
11192
- const address = endpointAddress(ep);
11193
- if (pattern && !pattern.test(address)) {
11194
- emit(a, `Endpoint of "${intf.id}.${method2.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, isDraftCtx);
11195
- }
11196
- }
11836
+ const address = endpointAddress(endpoint);
11837
+ if (pattern && !pattern.test(address)) {
11838
+ reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method2.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, bound2.draft);
11197
11839
  }
11198
11840
  }
11199
11841
  }
@@ -11633,16 +12275,13 @@ var reachabilityRule;
11633
12275
  var init_unused_detection = __esm({
11634
12276
  "src/core/rules/wiring/unused-detection.ts"() {
11635
12277
  "use strict";
11636
- init_models();
11637
12278
  init_narrative_graph_projector();
11638
12279
  reachabilityRule = {
11639
12280
  name: "unused-detection",
11640
- description: "Walks the narrative execution graph (call steps, register handoffs, dispatch-table routing, lifecycle flows) from every entrypoint (Portal/Observer/published component/lifecycle entrypoint/invokedBy-declared method) and flags components and methods no execution chain reaches, plus types no field or signature references. An interface method declaring invokedBy (a real caller outside the modeled graph) seeds the walk, so its narrative propagates reachability; the declaration itself is audited (thin caller prose, or a method the internal walk already reaches).",
12281
+ description: "Walks the narrative execution graph (call steps, register handoffs, dispatch-table routing, lifecycle flows) from every entrypoint (Portal/Observer/published component/lifecycle entrypoint/invokedBy-declared method) and flags components and methods no execution chain reaches. An interface method declaring invokedBy (a real caller outside the modeled graph) seeds the walk, so its narrative propagates reachability; a declaration the INTERNAL walk (without those seeds) already reaches is stale and is reported.",
11641
12282
  codes: [
11642
12283
  { code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
11643
12284
  { code: "UNUSED_METHOD", defaultSeverity: "warning", summary: "Method never called by any narrative step" },
11644
- { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" },
11645
- { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" },
11646
12285
  { code: "INVOKED_BY_REDUNDANT", defaultSeverity: "warning", summary: "invokedBy declaration on a method the internal narrative walk already reaches \u2014 stale, remove it" }
11647
12286
  ],
11648
12287
  check(ctx) {
@@ -11670,17 +12309,8 @@ var init_unused_detection = __esm({
11670
12309
  if (!m.invokedBy) continue;
11671
12310
  invokedBySeeds.push({ compId: intf.component, methodName: m.name });
11672
12311
  if (!ctx.isSpecInScope(intf.id)) continue;
11673
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
11674
- if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
11675
- ctx.addIssue(
11676
- "warning",
11677
- "INVOKED_BY_UNDESCRIBED",
11678
- `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.`,
11679
- intf.id,
11680
- isDraftCtx
11681
- );
11682
- }
11683
12312
  if (baseWalk.reachesMethod(intf.component, m.name)) {
12313
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
11684
12314
  ctx.addIssue(
11685
12315
  "warning",
11686
12316
  "INVOKED_BY_REDUNDANT",
@@ -11703,23 +12333,75 @@ var init_unused_detection = __esm({
11703
12333
  comp.id,
11704
12334
  isDraftCtx
11705
12335
  );
11706
- } else {
11707
- for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
11708
- for (const m of intf.methods) {
11709
- if (!reach.reachesMethod(comp.id, m.name)) {
11710
- const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
11711
- ctx.addIssue(
11712
- "warning",
11713
- "UNUSED_METHOD",
11714
- `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
11715
- intf.id,
11716
- isDraftCtx
11717
- );
11718
- }
12336
+ continue;
12337
+ }
12338
+ for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
12339
+ for (const m of intf.methods) {
12340
+ if (!reach.reachesMethod(comp.id, m.name)) {
12341
+ const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
12342
+ ctx.addIssue(
12343
+ "warning",
12344
+ "UNUSED_METHOD",
12345
+ `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
12346
+ intf.id,
12347
+ isDraftCtx
12348
+ );
11719
12349
  }
11720
12350
  }
11721
12351
  }
11722
12352
  }
12353
+ }
12354
+ };
12355
+ }
12356
+ });
12357
+
12358
+ // src/core/rules/wiring/invoked-by-description.ts
12359
+ var invokedByDescriptionRule;
12360
+ var init_invoked_by_description = __esm({
12361
+ "src/core/rules/wiring/invoked-by-description.ts"() {
12362
+ "use strict";
12363
+ init_models();
12364
+ invokedByDescriptionRule = {
12365
+ name: "invoked-by-description",
12366
+ description: 'An invokedBy declaration names a caller outside the modeled graph and silences unused-detection for its method, so the claim must stay reviewable: its "caller" prose is held to the intent floor (missing or placeholder-thin prose is reported), stating WHO invokes the method and when.',
12367
+ codes: [
12368
+ { code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
12369
+ ],
12370
+ check(ctx) {
12371
+ for (const intf of ctx.interfaces) {
12372
+ if (!ctx.componentMap.has(intf.component)) continue;
12373
+ if (!ctx.isSpecInScope(intf.id)) continue;
12374
+ for (const m of intf.methods) {
12375
+ if (!m.invokedBy) continue;
12376
+ if (passesIntentFloor(m.invokedBy.caller, m.name)) continue;
12377
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
12378
+ ctx.addIssue(
12379
+ "warning",
12380
+ "INVOKED_BY_UNDESCRIBED",
12381
+ `Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
12382
+ intf.id,
12383
+ isDraftCtx
12384
+ );
12385
+ }
12386
+ }
12387
+ }
12388
+ };
12389
+ }
12390
+ });
12391
+
12392
+ // src/core/rules/wiring/unused-types.ts
12393
+ var unusedTypesRule;
12394
+ var init_unused_types = __esm({
12395
+ "src/core/rules/wiring/unused-types.ts"() {
12396
+ "use strict";
12397
+ init_models();
12398
+ unusedTypesRule = {
12399
+ name: "unused-types",
12400
+ description: "Flags types no field of any type and no interface method signature references. References are matched through the type-reference grammar (generic arguments, collections, qualified ids), and the declaring type's own generic parameters are left out \u2014 a type parameter is not a reference to a type.",
12401
+ codes: [
12402
+ { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" }
12403
+ ],
12404
+ check(ctx) {
11723
12405
  const referencedTypes = /* @__PURE__ */ new Set();
11724
12406
  const markTypeReferenced = (ref) => {
11725
12407
  if (ctx.isBuiltinType(ref)) return;
@@ -11953,7 +12635,44 @@ var init_prose_claims = __esm({
11953
12635
  }
11954
12636
  });
11955
12637
 
11956
- // src/core/rules/wiring/invariant-backing.ts
12638
+ // src/core/rules/wiring/unique-invariant-ids.ts
12639
+ var uniqueInvariantIdsRule;
12640
+ var init_unique_invariant_ids = __esm({
12641
+ "src/core/rules/wiring/unique-invariant-ids.ts"() {
12642
+ "use strict";
12643
+ init_models();
12644
+ uniqueInvariantIdsRule = {
12645
+ name: "unique-invariant-ids",
12646
+ description: `An entity's declared invariant ids must be unique within the entity: a narrative step asserts an invariant by "<type-id>.<invariant-id>", so a repeated id makes one assertion stand for two properties and satisfying either silences both.`,
12647
+ codes: [
12648
+ { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
12649
+ ],
12650
+ check(ctx) {
12651
+ for (const t of ctx.types) {
12652
+ const invariants = t.invariants ?? [];
12653
+ if (invariants.length === 0) continue;
12654
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
12655
+ const entityDraft = sub !== void 0 && isDraftSubsystem(sub);
12656
+ const seen = /* @__PURE__ */ new Set();
12657
+ for (const inv of invariants) {
12658
+ if (seen.has(inv.id)) {
12659
+ ctx.addIssue(
12660
+ "error",
12661
+ "DUPLICATE_INVARIANT_ID",
12662
+ `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
12663
+ t.id,
12664
+ entityDraft
12665
+ );
12666
+ }
12667
+ seen.add(inv.id);
12668
+ }
12669
+ }
12670
+ }
12671
+ };
12672
+ }
12673
+ });
12674
+
12675
+ // src/core/rules/wiring/invariant-ref.ts
11957
12676
  function splitInvariantRef(ref) {
11958
12677
  const at = ref.lastIndexOf(".");
11959
12678
  if (at <= 0 || at === ref.length - 1) return null;
@@ -11977,6 +12696,14 @@ function refMatchesInvariant(ref, type, invariantId) {
11977
12696
  if (!(type.invariants ?? []).some((inv) => inv.id === invariantId)) return false;
11978
12697
  return typeMatchesRef(type, parts.typeRef);
11979
12698
  }
12699
+ var init_invariant_ref = __esm({
12700
+ "src/core/rules/wiring/invariant-ref.ts"() {
12701
+ "use strict";
12702
+ init_models();
12703
+ }
12704
+ });
12705
+
12706
+ // src/core/rules/wiring/invariant-backing.ts
11980
12707
  function stepAsserts(impl, methodName, type, invariantId) {
11981
12708
  const method2 = impl.methods.find((m) => m.name === methodName);
11982
12709
  if (!method2) return false;
@@ -12001,33 +12728,19 @@ var init_invariant_backing = __esm({
12001
12728
  "src/core/rules/wiring/invariant-backing.ts"() {
12002
12729
  "use strict";
12003
12730
  init_models();
12731
+ init_invariant_ref();
12004
12732
  invariantBackingRule = {
12005
12733
  name: "invariant-backing",
12006
- description: 'The invariant registry: entities may declare domain invariants (type.invariants), anchored through their componentClass. Every write-effect contract method of the owning component must carry a narrative step asserting each invariant (step.assertsInvariants: "<type-id>.<invariant-id>") \u2014 the same declared-and-backed shape as semantic guarantees. This is an HONEST lint over declarations: a green run means every write path visibly claims the invariant, never that the narrative or code actually enforces it. An invariant with no resolvable owner or no declared write path is unanchored; dangling assertion references are errors.',
12734
+ description: 'The invariant registry: entities may declare domain invariants (type.invariants), anchored through their componentClass. Every write-effect contract method of the owning component must carry a narrative step asserting each invariant (step.assertsInvariants: "<type-id>.<invariant-id>") \u2014 the same declared-and-backed shape as semantic guarantees. This is an HONEST lint over declarations: a green run means every write path visibly claims the invariant, never that the narrative or code actually enforces it. An invariant with no resolvable owner or no declared write path is unanchored, and nothing further is claimed about it.',
12007
12735
  codes: [
12008
- { code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" },
12009
12736
  { code: "INVARIANT_UNANCHORED", defaultSeverity: "warning", summary: "An entity declares invariants but has no componentClass, its componentClass does not resolve, or the owning component declares no write-effect contract methods" },
12010
- { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" },
12011
- { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12737
+ { code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
12012
12738
  ],
12013
12739
  check(ctx) {
12014
12740
  for (const t of ctx.types) {
12015
12741
  const invariants = t.invariants ?? [];
12016
12742
  if (invariants.length === 0) continue;
12017
12743
  const entityDraft = isEntityDraft(t, ctx);
12018
- const seen = /* @__PURE__ */ new Set();
12019
- for (const inv of invariants) {
12020
- if (seen.has(inv.id)) {
12021
- ctx.addIssue(
12022
- "error",
12023
- "DUPLICATE_INVARIANT_ID",
12024
- `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
12025
- t.id,
12026
- entityDraft
12027
- );
12028
- }
12029
- seen.add(inv.id);
12030
- }
12031
12744
  const comp = resolveComponentClass(t, ctx);
12032
12745
  if (!comp) {
12033
12746
  ctx.addIssue(
@@ -12069,6 +12782,24 @@ var init_invariant_backing = __esm({
12069
12782
  }
12070
12783
  }
12071
12784
  }
12785
+ }
12786
+ };
12787
+ }
12788
+ });
12789
+
12790
+ // src/core/rules/wiring/invariant-references.ts
12791
+ var invariantReferencesRule;
12792
+ var init_invariant_references = __esm({
12793
+ "src/core/rules/wiring/invariant-references.ts"() {
12794
+ "use strict";
12795
+ init_invariant_ref();
12796
+ invariantReferencesRule = {
12797
+ name: "invariant-references",
12798
+ description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere.",
12799
+ codes: [
12800
+ { code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
12801
+ ],
12802
+ check(ctx) {
12072
12803
  for (const impl of ctx.implementations) {
12073
12804
  const isDraftCtx = ctx.isImplementationDraft(impl);
12074
12805
  for (const method2 of impl.methods) {
@@ -12182,8 +12913,7 @@ var init_source_file_linkage = __esm({
12182
12913
  { code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
12183
12914
  ],
12184
12915
  check(ctx) {
12185
- const factsByPath = /* @__PURE__ */ new Map();
12186
- for (const facts of ctx.codeModel.files) factsByPath.set(pathKey(facts.path), facts);
12916
+ const code = ctx.codeIndex();
12187
12917
  const degradedTsFiles = ctx.codeModel.files.filter(
12188
12918
  (f) => f.status === "analyzed" && (f.language === "typescript" || f.language === "javascript") && f.analysisGrade !== "exact"
12189
12919
  );
@@ -12231,7 +12961,7 @@ var init_source_file_linkage = __esm({
12231
12961
  const key = pathKey(file);
12232
12962
  if (reported.has(key)) continue;
12233
12963
  reported.add(key);
12234
- const facts = factsByPath.get(key);
12964
+ const facts = code.factsAt(key);
12235
12965
  if (!facts || facts.status === "analyzed") continue;
12236
12966
  const isImplementationFile = !!impl.sourcePath && pathKey(impl.sourcePath) === key;
12237
12967
  const users = isImplementationFile ? [] : impl.methods.filter((m) => m.sourcePath && pathKey(m.sourcePath) === key).map((m) => m.name);
@@ -12281,14 +13011,7 @@ var init_method_realization = __esm({
12281
13011
  { 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" }
12282
13012
  ],
12283
13013
  check(ctx) {
12284
- const lookups = /* @__PURE__ */ new Map();
12285
- for (const facts of ctx.codeModel.files) {
12286
- lookups.set(pathKey(facts.path), {
12287
- declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
12288
- anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
12289
- facts
12290
- });
12291
- }
13014
+ const code = ctx.codeIndex();
12292
13015
  for (const impl of ctx.implementations) {
12293
13016
  const contract = ctx.interfaceMap.get(impl.contract);
12294
13017
  if (!contract) continue;
@@ -12303,11 +13026,11 @@ var init_method_realization = __esm({
12303
13026
  if (tier === "off") continue;
12304
13027
  const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
12305
13028
  if (!file) continue;
12306
- const lookup = lookups.get(pathKey(file));
12307
- if (!lookup || lookup.facts.status !== "analyzed") continue;
13029
+ const facts = code.factsAt(file);
13030
+ if (!facts || facts.status !== "analyzed") continue;
12308
13031
  const symbol = methodImpl?.symbol ?? method2.name;
12309
- const inDeclared = lookup.declared.has(symbol);
12310
- const inAnchored = inDeclared || lookup.anchored.has(symbol);
13032
+ const inDeclared = code.declarationsAt(file).has(symbol);
13033
+ const inAnchored = inDeclared || code.anchorsAt(file).has(symbol);
12311
13034
  const realized = tier === "declared" ? inDeclared : inAnchored;
12312
13035
  if (realized) continue;
12313
13036
  const label = methodImpl?.symbol ? `"${method2.name}" (symbol "${symbol}")` : `"${method2.name}"`;
@@ -12315,7 +13038,7 @@ var init_method_realization = __esm({
12315
13038
  ctx.addIssue(
12316
13039
  "warning",
12317
13040
  "UNREALIZED_METHOD",
12318
- `Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${lookup.facts.analysisGrade}).${weakHint}`,
13041
+ `Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${facts.analysisGrade}).${weakHint}`,
12319
13042
  impl.id,
12320
13043
  draft
12321
13044
  );
@@ -12339,10 +13062,7 @@ var init_finding_realization = __esm({
12339
13062
  { 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)" }
12340
13063
  ],
12341
13064
  check(ctx) {
12342
- const lookups = /* @__PURE__ */ new Map();
12343
- for (const facts of ctx.codeModel.files) {
12344
- lookups.set(pathKey(facts.path), { findingAnchors: new Set(facts.anchoredNames), facts });
12345
- }
13065
+ const code = ctx.codeIndex();
12346
13066
  for (const impl of ctx.implementations) {
12347
13067
  const contract = ctx.interfaceMap.get(impl.contract);
12348
13068
  if (!contract) continue;
@@ -12358,14 +13078,15 @@ var init_finding_realization = __esm({
12358
13078
  if (tier === "off") continue;
12359
13079
  const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
12360
13080
  if (!file) continue;
12361
- const lookup = lookups.get(pathKey(file));
12362
- if (!lookup || lookup.facts.status !== "analyzed") continue;
13081
+ const facts = code.factsAt(file);
13082
+ if (!facts || facts.status !== "analyzed") continue;
13083
+ const findingAnchors = code.findingAnchorsAt(file);
12363
13084
  for (const finding of method2.findings) {
12364
- if (lookup.findingAnchors.has(finding.code)) continue;
13085
+ if (findingAnchors.has(finding.code)) continue;
12365
13086
  ctx.addIssue(
12366
13087
  "warning",
12367
13088
  "UNREALIZED_FINDING",
12368
- `Method "${method2.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.`,
13089
+ `Method "${method2.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.`,
12369
13090
  impl.id,
12370
13091
  draft
12371
13092
  );
@@ -12397,68 +13118,58 @@ function closedCallees(facts, fn) {
12397
13118
  }
12398
13119
  return closed;
12399
13120
  }
12400
- var callConformanceRule;
12401
- var init_call_conformance = __esm({
12402
- "src/core/rules/conformance/call-conformance.ts"() {
12403
- "use strict";
12404
- init_models();
12405
- callConformanceRule = {
12406
- name: "call-conformance",
12407
- 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.",
12408
- codes: [
12409
- { 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)" }
12410
- ],
12411
- check(ctx) {
12412
- const factsByPath = /* @__PURE__ */ new Map();
12413
- for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
12414
- for (const impl of ctx.implementations) {
12415
- const contract = ctx.interfaceMap.get(impl.contract);
12416
- if (!contract) continue;
12417
- const component = ctx.componentMap.get(contract.component);
12418
- if (!component) continue;
12419
- if (ctx.isInChainedSubproject(component.subsystem)) continue;
12420
- const specTier = impl.conformance ?? defaultConformanceTier(component);
12421
- const isDraftCtx = ctx.isImplementationDraft(impl);
12422
- for (const implMethod of impl.methods) {
12423
- const tier = implMethod.conformance ?? specTier;
12424
- if (tier === "off") continue;
12425
- if (!implMethod.narrative.length) continue;
12426
- const file = methodSourceFile(implMethod, impl.sourcePath);
12427
- if (!file) continue;
12428
- const facts = factsByPath.get(pathKey(file));
12429
- if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12430
- const fnSymbol = implMethod.symbol ?? implMethod.name;
12431
- const callees = closedCallees(facts, fnSymbol);
12432
- if (!callees) continue;
12433
- const missing = [];
12434
- for (const step of implMethod.narrative) {
12435
- if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
12436
- if (!ctx.componentMap.has(step.targetComponent)) continue;
12437
- const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
12438
- for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
12439
- for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
12440
- const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
12441
- if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12442
- }
13121
+ var callConformanceRule;
13122
+ var init_call_conformance = __esm({
13123
+ "src/core/rules/conformance/call-conformance.ts"() {
13124
+ "use strict";
13125
+ init_models();
13126
+ callConformanceRule = {
13127
+ name: "call-conformance",
13128
+ 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.",
13129
+ codes: [
13130
+ { 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)" }
13131
+ ],
13132
+ check(ctx) {
13133
+ const code = ctx.codeIndex();
13134
+ for (const entry of ctx.implementationMethods()) {
13135
+ const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
13136
+ const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
13137
+ if (tier === "off") continue;
13138
+ if (!implMethod.narrative.length) continue;
13139
+ if (!file) continue;
13140
+ const facts = code.factsAt(file);
13141
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
13142
+ const fnSymbol = implMethod.symbol ?? implMethod.name;
13143
+ const callees = closedCallees(facts, fnSymbol);
13144
+ if (!callees) continue;
13145
+ const missing = [];
13146
+ for (const step of implMethod.narrative) {
13147
+ if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
13148
+ if (!ctx.componentMap.has(step.targetComponent)) continue;
13149
+ const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
13150
+ for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
13151
+ for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
13152
+ const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
13153
+ if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
12443
13154
  }
12444
- if (accepted.has(fnSymbol)) continue;
12445
- if ([...accepted].some((name) => callees.has(name))) continue;
12446
- missing.push({
12447
- step: step.stepNumber,
12448
- target: `${step.targetComponent}.${step.targetMethod}`,
12449
- accepted: [...accepted]
12450
- });
12451
13155
  }
12452
- if (missing.length === 0) continue;
12453
- const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
12454
- ctx.addIssue(
12455
- "warning",
12456
- "CALL_STEP_UNREALIZED",
12457
- `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.`,
12458
- impl.id,
12459
- isDraftCtx
12460
- );
13156
+ if (accepted.has(fnSymbol)) continue;
13157
+ if ([...accepted].some((name) => callees.has(name))) continue;
13158
+ missing.push({
13159
+ step: step.stepNumber,
13160
+ target: `${step.targetComponent}.${step.targetMethod}`,
13161
+ accepted: [...accepted]
13162
+ });
12461
13163
  }
13164
+ if (missing.length === 0) continue;
13165
+ const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
13166
+ ctx.addIssue(
13167
+ "warning",
13168
+ "CALL_STEP_UNREALIZED",
13169
+ `Method "${implMethod.name}" in implementation "${impl.id}": ${missing.length} narrative call step(s) are not realized as calls of the function "${fnSymbol}" in "${file}" \u2014 ${detail}. Callees are matched by name, closed over same-file helpers (exact grade, set membership \u2014 order and arguments are not checked). Realize the calls, fix the narrative, or map code names via per-method symbols on the targets.`,
13170
+ impl.id,
13171
+ entry.draftContext
13172
+ );
12462
13173
  }
12463
13174
  }
12464
13175
  };
@@ -12479,6 +13190,7 @@ var init_hidden_state = __esm({
12479
13190
  { 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" }
12480
13191
  ],
12481
13192
  check(ctx) {
13193
+ const code = ctx.codeIndex();
12482
13194
  const byPath = /* @__PURE__ */ new Map();
12483
13195
  for (const impl of ctx.implementations) {
12484
13196
  const contract = ctx.interfaceMap.get(impl.contract);
@@ -12500,11 +13212,11 @@ var init_hidden_state = __esm({
12500
13212
  byPath.set(key, list2);
12501
13213
  }
12502
13214
  }
12503
- for (const facts of ctx.codeModel.files) {
12504
- if (facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
13215
+ for (const path70 of code.exactPaths) {
13216
+ const facts = code.factsAt(path70);
12505
13217
  const bindings = facts.topLevelMutableBindings ?? [];
12506
13218
  if (bindings.length === 0) continue;
12507
- const mapped = byPath.get(pathKey(facts.path)) ?? [];
13219
+ const mapped = byPath.get(path70) ?? [];
12508
13220
  if (mapped.length === 0) continue;
12509
13221
  if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
12510
13222
  const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
@@ -12536,54 +13248,21 @@ var init_dependency_conformance = __esm({
12536
13248
  { 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" }
12537
13249
  ],
12538
13250
  check(ctx) {
12539
- const factsByPath = new Map(ctx.codeModel.files.map((f) => [pathKey(f.path), f]));
12540
- const nodes = /* @__PURE__ */ new Map();
12541
- const filesByComponent = /* @__PURE__ */ new Map();
12542
- const implsByComponent = /* @__PURE__ */ new Map();
12543
- for (const impl of ctx.implementations) {
12544
- const contract = ctx.interfaceMap.get(impl.contract);
12545
- if (!contract) continue;
12546
- const component = ctx.componentMap.get(contract.component);
12547
- if (!component) continue;
12548
- if (ctx.isInChainedSubproject(component.subsystem)) continue;
12549
- let mapped = false;
12550
- for (const file of implementationSourceFiles(impl)) {
12551
- const p = pathKey(file);
12552
- const facts = factsByPath.get(p);
12553
- if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12554
- let node = nodes.get(p);
12555
- if (!node) {
12556
- node = { path: p, components: [], anchorImplId: impl.id, imports: facts.imports, reexports: facts.reexports, draft: false };
12557
- nodes.set(p, node);
12558
- }
12559
- if (!node.components.some((c) => c.id === component.id)) node.components.push(component);
12560
- node.draft = node.draft || ctx.isImplementationDraft(impl);
12561
- if (!filesByComponent.has(component.id)) filesByComponent.set(component.id, /* @__PURE__ */ new Set());
12562
- filesByComponent.get(component.id).add(p);
12563
- mapped = true;
12564
- }
12565
- if (mapped) {
12566
- if (!implsByComponent.has(component.id)) implsByComponent.set(component.id, []);
12567
- implsByComponent.get(component.id).push(impl);
12568
- }
12569
- }
12570
- const mappedPaths = new Set(nodes.keys());
12571
- const edges = /* @__PURE__ */ new Map();
12572
- const realizationEdges = /* @__PURE__ */ new Map();
12573
- for (const node of nodes.values()) {
12574
- const targets = /* @__PURE__ */ new Set();
12575
- for (const spec of node.imports) {
12576
- const resolved = resolveImport(node.path, spec, mappedPaths);
12577
- if (resolved && resolved !== node.path) targets.add(resolved);
12578
- }
12579
- edges.set(node.path, targets);
12580
- const realized = new Set(targets);
12581
- for (const spec of node.reexports) {
12582
- const resolved = resolveImport(node.path, spec, mappedPaths);
12583
- if (resolved && resolved !== node.path) realized.add(resolved);
12584
- }
12585
- realizationEdges.set(node.path, realized);
12586
- }
13251
+ const code = ctx.codeIndex();
13252
+ const realization = ctx.realizationIndex();
13253
+ const isExact = (p) => code.exactPaths.has(p);
13254
+ const mappedPaths = new Set(realization.paths.filter(isExact));
13255
+ const graph = ctx.importGraph(mappedPaths);
13256
+ const exactFiles = /* @__PURE__ */ new Map();
13257
+ const filesOf = (compId) => {
13258
+ let files = exactFiles.get(compId);
13259
+ if (!files) {
13260
+ files = realization.filesOf(compId).filter(isExact);
13261
+ exactFiles.set(compId, files);
13262
+ }
13263
+ return files;
13264
+ };
13265
+ const mappedImplsOf = (compId) => realization.implementationsOf(compId).filter((impl) => implementationSourceFiles(impl).some((f) => isExact(pathKey(f))));
12587
13266
  const dependsOrOwns = (from, toId) => from.dependsOn.includes(toId) || (from.owns ?? []).includes(toId);
12588
13267
  const ownerOf = /* @__PURE__ */ new Map();
12589
13268
  for (const c of ctx.components) {
@@ -12594,9 +13273,9 @@ var init_dependency_conformance = __esm({
12594
13273
  if (!published || published.size === 0) return false;
12595
13274
  return from.dependsOn.some((d) => published.has(d));
12596
13275
  };
12597
- const edgeJustified = (fromNode, toNode) => {
12598
- for (const cf of fromNode.components) {
12599
- for (const cg of toNode.components) {
13276
+ const edgeJustified = (from, to) => {
13277
+ for (const cf of from) {
13278
+ for (const cg of to) {
12600
13279
  if (cf.id === cg.id) return true;
12601
13280
  if (dependsOrOwns(cf, cg.id)) return true;
12602
13281
  if (cf.subsystem === cg.subsystem && dependsOrOwns(cg, cf.id) && (cg.componentType === "Portal" || cg.componentType === "Observer")) return true;
@@ -12610,60 +13289,35 @@ var init_dependency_conformance = __esm({
12610
13289
  }
12611
13290
  return false;
12612
13291
  };
12613
- for (const [fromPath, targets] of edges) {
12614
- const fromNode = nodes.get(fromPath);
12615
- for (const toPath of targets) {
12616
- const toNode = nodes.get(toPath);
12617
- if (edgeJustified(fromNode, toNode)) continue;
13292
+ const draftAt = (path70) => realization.implementationsAt(path70).some((impl) => ctx.isImplementationDraft(impl));
13293
+ for (const fromPath of mappedPaths) {
13294
+ const fromComponents = realization.componentsAt(fromPath);
13295
+ for (const toPath of graph.importsOf(fromPath)) {
13296
+ const toComponents = realization.componentsAt(toPath);
13297
+ if (edgeJustified(fromComponents, toComponents)) continue;
12618
13298
  ctx.addIssue(
12619
13299
  "warning",
12620
13300
  "UNDECLARED_DEPENDENCY",
12621
- `"${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.`,
12622
- fromNode.anchorImplId,
12623
- fromNode.draft || toNode.draft
13301
+ `"${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.`,
13302
+ realization.implementationsAt(fromPath)[0]?.id,
13303
+ draftAt(fromPath) || draftAt(toPath)
12624
13304
  );
12625
13305
  }
12626
13306
  }
12627
- const hasImportBetween = (fromFiles, toFiles, allowReverse) => {
12628
- for (const f of fromFiles) {
12629
- const targets = realizationEdges.get(f);
12630
- if (!targets) continue;
12631
- for (const t of toFiles) if (targets.has(t)) return true;
12632
- }
12633
- if (allowReverse) {
12634
- for (const t of toFiles) {
12635
- const reverse = realizationEdges.get(t);
12636
- if (!reverse) continue;
12637
- for (const f of fromFiles) if (reverse.has(f)) return true;
12638
- }
12639
- }
12640
- return false;
12641
- };
12642
13307
  for (const component of ctx.components) {
12643
- const fromFiles = filesByComponent.get(component.id);
12644
- if (!fromFiles) continue;
13308
+ const fromFiles = filesOf(component.id);
13309
+ if (fromFiles.length === 0) continue;
12645
13310
  const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
12646
13311
  const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
12647
13312
  for (const targetId of declaredTargets) {
12648
13313
  const target = ctx.componentMap.get(targetId);
12649
13314
  if (!target) continue;
12650
- if (component.subsystem !== target.subsystem) {
12651
- const subsystemFiles = /* @__PURE__ */ new Set();
12652
- for (const node of nodes.values()) {
12653
- if (node.components.some((c) => c.subsystem === target.subsystem)) subsystemFiles.add(node.path);
12654
- }
12655
- if (subsystemFiles.size === 0) continue;
12656
- for (const f of fromFiles) subsystemFiles.delete(f);
12657
- if (subsystemFiles.size === 0) continue;
12658
- if (hasImportBetween(fromFiles, subsystemFiles, isMountingDeclarer)) continue;
12659
- } else {
12660
- const toFiles = filesByComponent.get(targetId);
12661
- if (!toFiles) continue;
12662
- if ([...fromFiles].some((f) => toFiles.has(f))) continue;
12663
- if (hasImportBetween(fromFiles, toFiles, isMountingDeclarer)) continue;
12664
- }
12665
- const impls = implsByComponent.get(component.id) ?? [];
12666
- const anchor = impls[0];
13315
+ const crossSubsystem = component.subsystem !== target.subsystem;
13316
+ const toFiles = crossSubsystem ? realization.filesIn(target.subsystem).filter((p) => isExact(p) && !fromFiles.includes(p)) : filesOf(targetId);
13317
+ if (toFiles.length === 0) continue;
13318
+ if (fromFiles.some((f) => toFiles.includes(f))) continue;
13319
+ if (graph.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
13320
+ const impls = mappedImplsOf(component.id);
12667
13321
  const declaredDependsOn = component.dependsOn.includes(targetId);
12668
13322
  const declaredOwns = (component.owns ?? []).includes(targetId);
12669
13323
  const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
@@ -12671,8 +13325,8 @@ var init_dependency_conformance = __esm({
12671
13325
  ctx.addIssue(
12672
13326
  "warning",
12673
13327
  "UNREALIZED_DEPENDENCY",
12674
- `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.`,
12675
- anchor?.id ?? component.id,
13328
+ `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.`,
13329
+ impls[0]?.id ?? component.id,
12676
13330
  draft
12677
13331
  );
12678
13332
  }
@@ -12682,124 +13336,171 @@ var init_dependency_conformance = __esm({
12682
13336
  }
12683
13337
  });
12684
13338
 
12685
- // src/core/rules/conformance/integration-conformance.ts
12686
- var integrationConformanceRule;
12687
- var init_integration_conformance = __esm({
12688
- "src/core/rules/conformance/integration-conformance.ts"() {
13339
+ // src/core/rules/conformance/integration-sim-declaration.ts
13340
+ var integrationSimDeclarationRule;
13341
+ var init_integration_sim_declaration = __esm({
13342
+ "src/core/rules/conformance/integration-sim-declaration.ts"() {
12689
13343
  "use strict";
12690
- init_models();
12691
- integrationConformanceRule = {
12692
- name: "integration-conformance",
12693
- 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.",
13344
+ integrationSimDeclarationRule = {
13345
+ name: "integration-sim-declaration",
13346
+ description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim, and a subsystem that has adopted nothing is never flooded.",
12694
13347
  codes: [
12695
- { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" },
12696
- { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" },
12697
- { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" },
12698
- { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13348
+ { code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
12699
13349
  ],
12700
13350
  check(ctx) {
12701
- const factsByPath = new Map(ctx.codeModel.files.map((f) => [pathKey(f.path), f]));
12702
- const allPaths = new Set(factsByPath.keys());
12703
- const filesByComponent = /* @__PURE__ */ new Map();
12704
- const filesBySubsystem = /* @__PURE__ */ new Map();
12705
- const implsByComponent = /* @__PURE__ */ new Map();
12706
13351
  const adopted = /* @__PURE__ */ new Set();
12707
- const exists = (p) => {
12708
- const status2 = factsByPath.get(p)?.status;
12709
- return status2 === "analyzed" || status2 === "unreadable";
12710
- };
12711
13352
  for (const impl of ctx.implementations) {
13353
+ if (!impl.simPath) continue;
12712
13354
  const contract = ctx.interfaceMap.get(impl.contract);
12713
13355
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12714
13356
  if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
12715
- if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
12716
- implsByComponent.get(comp.id).push(impl);
12717
- for (const file of implementationSourceFiles(impl)) {
12718
- const p = pathKey(file);
12719
- if (!exists(p)) continue;
12720
- if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
12721
- filesByComponent.get(comp.id).add(p);
12722
- if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
12723
- filesBySubsystem.get(comp.subsystem).add(p);
12724
- }
12725
- if (impl.simPath) adopted.add(comp.subsystem);
12726
- }
12727
- const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
12728
- const reachFrom = (start2) => {
12729
- const seen = /* @__PURE__ */ new Set([start2]);
12730
- const stack = [start2];
12731
- while (stack.length) {
12732
- const from = stack.pop();
12733
- const facts = factsByPath.get(from);
12734
- if (!facts || facts.status !== "analyzed") continue;
12735
- for (const spec of [...facts.imports, ...facts.reexports]) {
12736
- const to = resolveImport(from, spec, allPaths);
12737
- if (to && !seen.has(to)) {
12738
- seen.add(to);
12739
- stack.push(to);
12740
- }
12741
- }
12742
- }
12743
- return seen;
12744
- };
13357
+ adopted.add(comp.subsystem);
13358
+ }
12745
13359
  for (const impl of ctx.implementations) {
13360
+ if (impl.simPath) continue;
12746
13361
  const contract = ctx.interfaceMap.get(impl.contract);
12747
13362
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12748
13363
  if (!comp) continue;
12749
13364
  if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13365
+ if (!adopted.has(comp.subsystem)) continue;
13366
+ if (ctx.isImplementationDraft(impl)) continue;
12750
13367
  const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
12751
- if (!impl.simPath) {
12752
- if (!adopted.has(comp.subsystem)) continue;
12753
- if (ctx.isImplementationDraft(impl)) continue;
12754
- if (deps.length === 0) continue;
12755
- ctx.addIssue(
12756
- "warning",
12757
- "MISSING_INTEGRATION_SIM",
12758
- `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.`,
12759
- impl.id
12760
- );
12761
- continue;
12762
- }
13368
+ if (deps.length === 0) continue;
13369
+ ctx.addIssue(
13370
+ "warning",
13371
+ "MISSING_INTEGRATION_SIM",
13372
+ `Implementation "${impl.id}" (component "${comp.id}") is complete with ${deps.length} declared dependenc${deps.length === 1 ? "y" : "ies"}, its subsystem "${comp.subsystem}" has adopted integration sims, but it declares no simPath \u2014 its unit suite proves contract shape against mocks, not that the wired components run together. Declare the committed harness that constructs it with its REAL dependencies (N:1 sharing allowed), or lint.allow with the reason.`,
13373
+ impl.id
13374
+ );
13375
+ }
13376
+ }
13377
+ };
13378
+ }
13379
+ });
13380
+
13381
+ // src/core/rules/conformance/integration-sim-file.ts
13382
+ var integrationSimFileRule;
13383
+ var init_integration_sim_file = __esm({
13384
+ "src/core/rules/conformance/integration-sim-file.ts"() {
13385
+ "use strict";
13386
+ init_models();
13387
+ integrationSimFileRule = {
13388
+ name: "integration-sim-file",
13389
+ description: "A declared L4 simPath must resolve to a readable, committed file inside the project root \u2014 the integration harness is a re-runnable file, not a claim. Execution stays CI's job.",
13390
+ codes: [
13391
+ { code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
13392
+ ],
13393
+ check(ctx) {
13394
+ const code = ctx.codeIndex();
13395
+ for (const impl of ctx.implementations) {
13396
+ if (!impl.simPath) continue;
13397
+ const contract = ctx.interfaceMap.get(impl.contract);
13398
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13399
+ if (!comp) continue;
13400
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13401
+ const facts = code.factsAt(pathKey(impl.simPath));
13402
+ if (facts && facts.status === "analyzed") continue;
13403
+ const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
13404
+ ctx.addIssue(
13405
+ "warning",
13406
+ "SIM_FILE_MISSING",
13407
+ `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
13408
+ impl.id,
13409
+ ctx.isImplementationDraft(impl)
13410
+ );
13411
+ }
13412
+ }
13413
+ };
13414
+ }
13415
+ });
13416
+
13417
+ // src/core/rules/conformance/integration-sim-wiring.ts
13418
+ var integrationSimWiringRule;
13419
+ var init_integration_sim_wiring = __esm({
13420
+ "src/core/rules/conformance/integration-sim-wiring.ts"() {
13421
+ "use strict";
13422
+ init_models();
13423
+ integrationSimWiringRule = {
13424
+ name: "integration-sim-wiring",
13425
+ description: "A committed integration harness's import graph \u2014 closed transitively over the analyzed module set, exact grade only \u2014 must reach at least one of the component's own source modules (its implementation's or a method's) and at least one source module of each direct dependsOn/owns component (technology-boundary dependencies exempt: their contract-faithful fakes are sanctioned). Execution is CI's job \u2014 this proves wiring, never that the sim passes.",
13426
+ codes: [
13427
+ { code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
13428
+ ],
13429
+ check(ctx) {
13430
+ const code = ctx.codeIndex();
13431
+ const realization = ctx.realizationIndex();
13432
+ const graph = ctx.importGraph();
13433
+ const exists = (p) => {
13434
+ const status2 = code.factsAt(p)?.status;
13435
+ return status2 === "analyzed" || status2 === "unreadable";
13436
+ };
13437
+ const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
13438
+ const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
13439
+ const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
13440
+ for (const impl of ctx.implementations) {
13441
+ if (!impl.simPath) continue;
13442
+ const contract = ctx.interfaceMap.get(impl.contract);
13443
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13444
+ if (!comp) continue;
13445
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
12763
13446
  const simPath = pathKey(impl.simPath);
12764
- const facts = factsByPath.get(simPath);
12765
- const isDraftCtx = ctx.isImplementationDraft(impl);
12766
- if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
12767
- 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";
12768
- ctx.addIssue(
12769
- "warning",
12770
- "SIM_FILE_MISSING",
12771
- `Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
12772
- impl.id,
12773
- isDraftCtx
12774
- );
12775
- continue;
12776
- }
13447
+ const facts = code.factsAt(simPath);
13448
+ if (!facts || facts.status !== "analyzed") continue;
12777
13449
  if (facts.analysisGrade !== "exact") continue;
12778
- const reach = reachFrom(simPath);
12779
- const ownFiles = filesByComponent.get(comp.id) ?? /* @__PURE__ */ new Set();
13450
+ const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
13451
+ const reach = graph.reachFrom(simPath);
13452
+ const ownFiles = modulesOf(comp.id);
12780
13453
  const missing = [];
12781
- if (ownFiles.size > 0 && ![...ownFiles].some((f) => reach.has(f))) {
12782
- missing.push(`the component's own module (${[...ownFiles].join(" | ")})`);
13454
+ if (ownFiles.length > 0 && !ownFiles.some((f) => reach.has(f))) {
13455
+ missing.push(`the component's own module (${ownFiles.join(" | ")})`);
12783
13456
  }
12784
13457
  for (const dep of deps) {
12785
13458
  if (isTechBoundary(dep)) continue;
12786
13459
  const depComp = ctx.componentMap.get(dep);
12787
- const depFiles = depComp.subsystem !== comp.subsystem ? filesBySubsystem.get(depComp.subsystem) : filesByComponent.get(dep);
12788
- if (!depFiles || depFiles.size === 0) continue;
12789
- if (![...depFiles].some((f) => reach.has(f))) {
12790
- const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${[...depFiles].join(" | ")})`;
13460
+ const depFiles = depComp.subsystem !== comp.subsystem ? modulesIn(depComp.subsystem) : modulesOf(dep);
13461
+ if (depFiles.length === 0) continue;
13462
+ if (!depFiles.some((f) => reach.has(f))) {
13463
+ const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${depFiles.join(" | ")})`;
12791
13464
  missing.push(label);
12792
13465
  }
12793
13466
  }
12794
- if (missing.length) {
12795
- ctx.addIssue(
12796
- "warning",
12797
- "UNWIRED_INTEGRATION_SIM",
12798
- `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.`,
12799
- impl.id,
12800
- isDraftCtx
12801
- );
12802
- }
13467
+ if (missing.length === 0) continue;
13468
+ ctx.addIssue(
13469
+ "warning",
13470
+ "UNWIRED_INTEGRATION_SIM",
13471
+ `Sim "${impl.simPath}" of implementation "${impl.id}" does not reach ${missing.join(", nor ")} through its import graph (closed over the analyzed modules, exact grade) \u2014 the harness is not wiring the real implementations it claims to exercise. Import the real modules (technology-boundary adapters may stay contract-faithfully faked), or fix the simPath.`,
13472
+ impl.id,
13473
+ ctx.isImplementationDraft(impl)
13474
+ );
13475
+ }
13476
+ }
13477
+ };
13478
+ }
13479
+ });
13480
+
13481
+ // src/core/rules/conformance/integration-sim-coverage.ts
13482
+ var integrationSimCoverageRule;
13483
+ var init_integration_sim_coverage = __esm({
13484
+ "src/core/rules/conformance/integration-sim-coverage.ts"() {
13485
+ "use strict";
13486
+ init_models();
13487
+ integrationSimCoverageRule = {
13488
+ name: "integration-sim-coverage",
13489
+ description: "Once a committed integration harness carries sim:<component-id>. anchors, that component claims path coverage: every narrated method's happy path and every labeled throw step needs its exact sim:<component>.<method>[:<label>] string-literal anchor. Anchors prove the path is NAMED \u2014 assertion quality and execution stay CI's job.",
13490
+ codes: [
13491
+ { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
13492
+ ],
13493
+ check(ctx) {
13494
+ const code = ctx.codeIndex();
13495
+ for (const impl of ctx.implementations) {
13496
+ if (!impl.simPath) continue;
13497
+ const contract = ctx.interfaceMap.get(impl.contract);
13498
+ const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
13499
+ if (!comp) continue;
13500
+ if (ctx.isInChainedSubproject(comp.subsystem)) continue;
13501
+ const facts = code.factsAt(pathKey(impl.simPath));
13502
+ if (!facts || facts.status !== "analyzed") continue;
13503
+ if (facts.analysisGrade !== "exact") continue;
12803
13504
  const simAnchors = new Set(facts.anchoredNames.filter((a) => a.startsWith("sim:")));
12804
13505
  if (simAnchors.size === 0) continue;
12805
13506
  const compPrefix = `sim:${comp.id}.`;
@@ -12820,15 +13521,14 @@ var init_integration_conformance = __esm({
12820
13521
  }
12821
13522
  }
12822
13523
  }
12823
- if (uncovered.length) {
12824
- ctx.addIssue(
12825
- "warning",
12826
- "SIM_PATH_UNCOVERED",
12827
- `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.`,
12828
- impl.id,
12829
- isDraftCtx
12830
- );
12831
- }
13524
+ if (uncovered.length === 0) continue;
13525
+ ctx.addIssue(
13526
+ "warning",
13527
+ "SIM_PATH_UNCOVERED",
13528
+ `Sim "${impl.simPath}" declares path coverage for component "${comp.id}" (it carries sim: anchors) but does not name ${uncovered.join(", nor ")}. Drive the path and anchor it with the exact string literal, or drop the component's sim: anchors to withdraw the coverage claim. Unlabeled throw steps are not expected \u2014 a step's label is the path's identity. Anchors prove the path is NAMED; assertion quality and execution stay CI's job.`,
13529
+ impl.id,
13530
+ ctx.isImplementationDraft(impl)
13531
+ );
12832
13532
  }
12833
13533
  }
12834
13534
  };
@@ -12938,10 +13638,10 @@ var init_coupling_health = __esm({
12938
13638
  }
12939
13639
  });
12940
13640
 
12941
- // src/core/rules/heuristic/target-language.ts
12942
- var LANGUAGE_MARKERS, UNSUPPORTED_FLOW, languageRule;
12943
- var init_target_language = __esm({
12944
- "src/core/rules/heuristic/target-language.ts"() {
13641
+ // src/core/rules/heuristic/signature-language-builtins.ts
13642
+ var LANGUAGE_MARKERS, signatureLanguageBuiltinsRule;
13643
+ var init_signature_language_builtins = __esm({
13644
+ "src/core/rules/heuristic/signature-language-builtins.ts"() {
12945
13645
  "use strict";
12946
13646
  init_models();
12947
13647
  LANGUAGE_MARKERS = {
@@ -12976,6 +13676,68 @@ var init_target_language = __esm({
12976
13676
  csharp: /* @__PURE__ */ new Set(["task"]),
12977
13677
  go: /* @__PURE__ */ new Set(["chan", "rune"])
12978
13678
  };
13679
+ signatureLanguageBuiltinsRule = {
13680
+ name: "signature-language-builtins",
13681
+ description: "Contracts must speak the declared target language: builtin types that unambiguously belong to another language family are flagged in method signatures (e.g. `usize` in a TypeScript system, `Promise` in a Rust one). Set targetLanguage on the system (L0) or override per subsystem (L1).",
13682
+ codes: [
13683
+ { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
13684
+ ],
13685
+ check(ctx) {
13686
+ const markersFor = (family) => {
13687
+ const base = LANGUAGE_MARKERS[family];
13688
+ const extra = ctx.ext.languages[family]?.foreignBuiltins;
13689
+ if (!extra?.length) return base;
13690
+ return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
13691
+ };
13692
+ const families = [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)])];
13693
+ const scans = [];
13694
+ for (const intf of ctx.interfaces) {
13695
+ const comp = ctx.componentMap.get(intf.component);
13696
+ const lang = ctx.targetLanguageFor(comp?.subsystem);
13697
+ if (!lang) continue;
13698
+ const ownMarkers = markersFor(lang);
13699
+ if (!ownMarkers || ownMarkers.size === 0) continue;
13700
+ const draft = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
13701
+ for (const method2 of intf.methods) {
13702
+ for (const ref of methodTypeRefs(method2)) scans.push({ intf, method: method2, ref, lang, ownMarkers, draft });
13703
+ }
13704
+ }
13705
+ for (const scan of scans) {
13706
+ const refLower = scan.ref.toLowerCase();
13707
+ if (scan.ownMarkers.has(refLower)) continue;
13708
+ const foreign = families.find((family) => family !== scan.lang && markersFor(family)?.has(refLower));
13709
+ if (!foreign) continue;
13710
+ ctx.addIssue(
13711
+ "warning",
13712
+ "LANGUAGE_FOREIGN_BUILTIN",
13713
+ `Method "${scan.method.name}" on interface "${scan.intf.id}" uses "${scan.ref}", a ${foreign} builtin, but the target language here is ${scan.lang}. Use the ${scan.lang} equivalent so implementers generate idiomatic code.`,
13714
+ scan.intf.id,
13715
+ scan.draft
13716
+ );
13717
+ }
13718
+ }
13719
+ };
13720
+ }
13721
+ });
13722
+
13723
+ // src/core/rules/heuristic/narrative-language-constructs.ts
13724
+ function constructsOf(step) {
13725
+ const constructs = [
13726
+ step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13727
+ ];
13728
+ if (step.detach) constructs.push("detach");
13729
+ return constructs;
13730
+ }
13731
+ function constructLabel(construct) {
13732
+ if (construct === "doWhile") return "a do-while loop";
13733
+ if (construct === "forEach" || construct === "for" || construct === "while") return `a ${construct} loop`;
13734
+ if (construct === "detach") return "a detached (fire-and-forget) call";
13735
+ return `a ${construct} step`;
13736
+ }
13737
+ var UNSUPPORTED_FLOW, narrativeLanguageConstructsRule;
13738
+ var init_narrative_language_constructs = __esm({
13739
+ "src/core/rules/heuristic/narrative-language-constructs.ts"() {
13740
+ "use strict";
12979
13741
  UNSUPPORTED_FLOW = {
12980
13742
  rust: {
12981
13743
  try: "Rust models errors as values (Result + ?) \u2014 specify the guarded logic as explicit error branches, or note the Result mapping in the step description",
@@ -12995,53 +13757,18 @@ var init_target_language = __esm({
12995
13757
  doWhile: "Python has no do-while \u2014 prefer loopKind: while, or state the emulation (while True + break)"
12996
13758
  }
12997
13759
  };
12998
- languageRule = {
12999
- name: "target-language",
13000
- description: "Contracts must speak the declared target language: builtin types that unambiguously belong to another language family are flagged in method signatures, and narrative flow steps using constructs the language lacks (e.g. exceptions in Rust/Go, do-while in Python) are flagged too. Set targetLanguage on the system (L0) or override per subsystem (L1).",
13760
+ narrativeLanguageConstructsRule = {
13761
+ name: "narrative-language-constructs",
13762
+ description: "Narratives must describe flows the declared target language can express: steps using constructs the language lacks (e.g. exceptions in Rust/Go, do-while in Python) are flagged with the idiomatic alternative. Set targetLanguage on the system (L0) or override per subsystem (L1).",
13001
13763
  codes: [
13002
- { code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" },
13003
13764
  { code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
13004
13765
  ],
13005
13766
  check(ctx) {
13006
- const markersFor = (family) => {
13007
- const base = LANGUAGE_MARKERS[family];
13008
- const extra = ctx.ext.languages[family]?.foreignBuiltins;
13009
- if (!extra?.length) return base;
13010
- return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
13011
- };
13012
- const families = /* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)]);
13013
13767
  const gapsFor = (lang) => ({
13014
13768
  ...UNSUPPORTED_FLOW[lang] ?? {},
13015
13769
  ...ctx.ext.languages[lang]?.unsupportedFlow ?? {}
13016
13770
  });
13017
- for (const intf of ctx.interfaces) {
13018
- const comp = ctx.componentMap.get(intf.component);
13019
- const lang = ctx.targetLanguageFor(comp?.subsystem);
13020
- if (!lang) continue;
13021
- const ownMarkers = markersFor(lang);
13022
- if (!ownMarkers || ownMarkers.size === 0) continue;
13023
- const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
13024
- for (const m of intf.methods) {
13025
- const refs = methodTypeRefs(m);
13026
- for (const ref of refs) {
13027
- const refLower = ref.toLowerCase();
13028
- if (ownMarkers.has(refLower)) continue;
13029
- for (const family of families) {
13030
- if (family === lang) continue;
13031
- if (markersFor(family)?.has(refLower)) {
13032
- ctx.addIssue(
13033
- "warning",
13034
- "LANGUAGE_FOREIGN_BUILTIN",
13035
- `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.`,
13036
- intf.id,
13037
- isDraftCtx
13038
- );
13039
- break;
13040
- }
13041
- }
13042
- }
13043
- }
13044
- }
13771
+ const scans = [];
13045
13772
  for (const impl of ctx.implementations) {
13046
13773
  const contract = ctx.interfaceMap.get(impl.contract);
13047
13774
  if (!contract) continue;
@@ -13050,26 +13777,22 @@ var init_target_language = __esm({
13050
13777
  if (!lang) continue;
13051
13778
  const gaps = gapsFor(lang);
13052
13779
  if (Object.keys(gaps).length === 0) continue;
13053
- const isDraftCtx = ctx.isImplementationDraft(impl);
13054
- for (const implMethod of impl.methods) {
13055
- for (const step of implMethod.narrative) {
13056
- const constructs = [
13057
- step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
13058
- ];
13059
- if (step.detach) constructs.push("detach");
13060
- for (const construct of constructs) {
13061
- const guidance = gaps[construct];
13062
- if (!guidance) continue;
13063
- 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`;
13064
- ctx.addIssue(
13065
- "warning",
13066
- "LANGUAGE_FOREIGN_FLOW",
13067
- `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" uses ${label}, but the target language is ${lang}: ${guidance}.`,
13068
- impl.id,
13069
- isDraftCtx
13070
- );
13071
- }
13072
- }
13780
+ const draft = ctx.isImplementationDraft(impl);
13781
+ for (const method2 of impl.methods) {
13782
+ for (const step of method2.narrative) scans.push({ implementation: impl, method: method2, step, lang, gaps, draft });
13783
+ }
13784
+ }
13785
+ for (const scan of scans) {
13786
+ for (const construct of constructsOf(scan.step)) {
13787
+ const guidance = scan.gaps[construct];
13788
+ if (!guidance) continue;
13789
+ ctx.addIssue(
13790
+ "warning",
13791
+ "LANGUAGE_FOREIGN_FLOW",
13792
+ `Step ${scan.step.stepNumber} of "${scan.method.name}" in implementation "${scan.implementation.id}" uses ${constructLabel(construct)}, but the target language is ${scan.lang}: ${guidance}.`,
13793
+ scan.implementation.id,
13794
+ scan.draft
13795
+ );
13073
13796
  }
13074
13797
  }
13075
13798
  }
@@ -13077,6 +13800,36 @@ var init_target_language = __esm({
13077
13800
  }
13078
13801
  });
13079
13802
 
13803
+ // src/core/rules/heuristic/technology-binding.ts
13804
+ var DATA_LAYER, technologyBindingRule;
13805
+ var init_technology_binding = __esm({
13806
+ "src/core/rules/heuristic/technology-binding.ts"() {
13807
+ "use strict";
13808
+ DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13809
+ technologyBindingRule = {
13810
+ name: "technology-binding",
13811
+ description: "Only data-layer stereotypes (Adapter/Store/Registry/Index) should bind a technology directly: an L4 that declares `technologies` on logic has no swap seam. No hardcoded vendor lists \u2014 only declared tokens are policed, so the rule never fires on a tree that doesn't opt in.",
13812
+ codes: [
13813
+ { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13814
+ ],
13815
+ check(ctx) {
13816
+ for (const impl of ctx.implementations) {
13817
+ const intf = ctx.interfaceMap.get(impl.contract);
13818
+ const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13819
+ if (!impl.technologies?.length || !comp || DATA_LAYER.has(comp.componentType)) continue;
13820
+ ctx.addIssue(
13821
+ "warning",
13822
+ "TECH_ON_LOGIC_COMPONENT",
13823
+ `Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
13824
+ impl.id,
13825
+ ctx.isImplementationDraft(impl)
13826
+ );
13827
+ }
13828
+ }
13829
+ };
13830
+ }
13831
+ });
13832
+
13080
13833
  // src/core/rules/heuristic/technology-boundaries.ts
13081
13834
  function makeMatcher(tech) {
13082
13835
  const parts = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
@@ -13100,19 +13853,29 @@ function contractIdentifiers(m) {
13100
13853
  if (m.endpoint) parts.push(Object.values(m.endpoint).map(String).join(" "));
13101
13854
  return parts.join(" ");
13102
13855
  }
13103
- var DATA_LAYER, technologyRule;
13856
+ function implementationText(impl) {
13857
+ const parts = [impl.description, ...implementationSourceFiles(impl)];
13858
+ for (const m of impl.methods) {
13859
+ parts.push(m.intent);
13860
+ for (const s of m.narrative ?? []) {
13861
+ parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13862
+ for (const c of s.cases ?? []) parts.push(c.value);
13863
+ for (const c of s.catches ?? []) parts.push(c.error);
13864
+ }
13865
+ }
13866
+ return parts.filter(Boolean).join(" ");
13867
+ }
13868
+ var technologyRule;
13104
13869
  var init_technology_boundaries = __esm({
13105
13870
  "src/core/rules/heuristic/technology-boundaries.ts"() {
13106
13871
  "use strict";
13107
13872
  init_models();
13108
- DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
13109
13873
  technologyRule = {
13110
13874
  name: "technology-boundaries",
13111
- description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, L3 contract identifiers must stay intent-language (the contract is the swap seam), and only data-layer stereotypes (Adapter/Store/Registry/Index) should bind a technology directly.",
13875
+ description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface.",
13112
13876
  codes: [
13113
13877
  { code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
13114
- { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" },
13115
- { code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
13878
+ { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
13116
13879
  ],
13117
13880
  check(ctx) {
13118
13881
  const ownerOf = /* @__PURE__ */ new Map();
@@ -13140,21 +13903,18 @@ var init_technology_boundaries = __esm({
13140
13903
  }
13141
13904
  return out;
13142
13905
  };
13143
- const homes = /* @__PURE__ */ new Map();
13906
+ const declarations = [];
13144
13907
  for (const impl of ctx.implementations) {
13145
13908
  if (!impl.technologies?.length) continue;
13146
13909
  const intf = ctx.interfaceMap.get(impl.contract);
13147
13910
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13148
13911
  if (!comp) continue;
13149
- if (!DATA_LAYER.has(comp.componentType)) {
13150
- ctx.addIssue(
13151
- "warning",
13152
- "TECH_ON_LOGIC_COMPONENT",
13153
- `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.`,
13154
- impl.id,
13155
- ctx.isImplementationDraft(impl)
13156
- );
13157
- }
13912
+ for (const tech of impl.technologies) declarations.push({ comp, tech });
13913
+ }
13914
+ const scopes = /* @__PURE__ */ new Map();
13915
+ const scopeOf2 = (comp) => {
13916
+ const cached = scopes.get(comp.id);
13917
+ if (cached) return cached;
13158
13918
  const compSet = ownsClosure(ownershipRoot(comp.id));
13159
13919
  const scope = new Set(compSet);
13160
13920
  for (const i of ctx.interfaces) if (compSet.has(i.component)) scope.add(i.id);
@@ -13169,15 +13929,18 @@ var init_technology_boundaries = __esm({
13169
13929
  if (subId === s.id || subId.startsWith(`${s.id}::`)) scope.add(s.id);
13170
13930
  }
13171
13931
  }
13172
- for (const tech of impl.technologies) {
13173
- const match = makeMatcher(tech);
13174
- if (!match) continue;
13175
- const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13176
- const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13177
- home.ownerComponents.add(comp.id);
13178
- for (const id of scope) home.scope.add(id);
13179
- homes.set(key, home);
13180
- }
13932
+ scopes.set(comp.id, scope);
13933
+ return scope;
13934
+ };
13935
+ const homes = /* @__PURE__ */ new Map();
13936
+ for (const { comp, tech } of declarations) {
13937
+ const match = makeMatcher(tech);
13938
+ if (!match) continue;
13939
+ const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
13940
+ const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
13941
+ home.ownerComponents.add(comp.id);
13942
+ for (const id of scopeOf2(comp)) home.scope.add(id);
13943
+ homes.set(key, home);
13181
13944
  }
13182
13945
  if (homes.size === 0) return;
13183
13946
  const ownersDesc = (h) => [...h.ownerComponents].map((c) => `"${c}"`).join(", ");
@@ -13241,16 +14004,7 @@ var init_technology_boundaries = __esm({
13241
14004
  }
13242
14005
  for (const impl of ctx.implementations) {
13243
14006
  if (home.scope.has(impl.id)) continue;
13244
- const parts = [impl.description, ...implementationSourceFiles(impl)];
13245
- for (const m of impl.methods) {
13246
- parts.push(m.intent);
13247
- for (const s of m.narrative ?? []) {
13248
- parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
13249
- for (const c of s.cases ?? []) parts.push(c.value);
13250
- for (const c of s.catches ?? []) parts.push(c.error);
13251
- }
13252
- }
13253
- if (home.match(parts.filter(Boolean).join(" "))) {
14007
+ if (home.match(implementationText(impl))) {
13254
14008
  ctx.addIssue(
13255
14009
  "warning",
13256
14010
  "TECH_LEAKAGE",
@@ -13939,12 +14693,15 @@ var init_repository = __esm({
13939
14693
  "src/core/rules/repository.ts"() {
13940
14694
  "use strict";
13941
14695
  init_hierarchy_integrity();
13942
- init_namespace_hygiene();
14696
+ init_reserved_id_segments();
14697
+ init_namespace_shadowing();
13943
14698
  init_roundtrip_serialization();
13944
14699
  init_type_declarations();
13945
14700
  init_field_type_references();
13946
14701
  init_signature_type_references();
13947
- init_public_surface();
14702
+ init_public_surface_binding();
14703
+ init_public_surface_declared_type();
14704
+ init_public_surface_bound_contract();
13948
14705
  init_lint_allows();
13949
14706
  init_contract_symmetry();
13950
14707
  init_narrative_target_references();
@@ -13954,23 +14711,37 @@ var init_repository = __esm({
13954
14711
  init_narrative_step_config();
13955
14712
  init_narrative_reachability();
13956
14713
  init_narrative_jump_edges();
13957
- init_narrative_antipatterns();
14714
+ init_meaningless_branches();
14715
+ init_inescapable_cycles();
14716
+ init_unconditional_call_cycles();
13958
14717
  init_narrative_detail();
14718
+ init_detail_sufficiency();
13959
14719
  init_portal_fields();
13960
14720
  init_durability_declaration();
13961
14721
  init_logic_declaration();
13962
14722
  init_retired_stereotypes();
13963
14723
  init_portal_endpoints();
14724
+ init_non_portal_endpoints();
13964
14725
  init_portal_call_auth();
13965
- init_stereotype_dependencies();
13966
- init_pattern_ownership();
14726
+ init_auth_source_wiring();
14727
+ init_subsystem_boundary_dependencies();
14728
+ init_logic_dependency_class();
14729
+ init_data_block_dependencies();
14730
+ init_entrypoint_dependencies();
14731
+ init_portal_write_shortcut();
14732
+ init_pattern_membership();
14733
+ init_pattern_containment();
14734
+ init_unowned_blocks();
14735
+ init_member_visibility();
13967
14736
  init_facade_forwarding();
13968
14737
  init_profile_registration();
13969
14738
  init_profile_stereotype_fencing();
13970
14739
  init_pack_profile_stereotypes();
13971
14740
  init_pattern_references();
13972
14741
  init_component_variants();
13973
- init_declarative_assertions();
14742
+ init_assertion_forbidden_edges();
14743
+ init_assertion_required_fields();
14744
+ init_assertion_endpoint_shapes();
13974
14745
  init_pack_resolution();
13975
14746
  init_pack_reproducibility();
13976
14747
  init_dependency_cycles();
@@ -13978,10 +14749,14 @@ var init_repository = __esm({
13978
14749
  init_dispatch_step_routing();
13979
14750
  init_lifecycle_entrypoints();
13980
14751
  init_unused_detection();
14752
+ init_invoked_by_description();
14753
+ init_unused_types();
13981
14754
  init_durability_round_trip();
13982
14755
  init_untyped_seams();
13983
14756
  init_prose_claims();
14757
+ init_unique_invariant_ids();
13984
14758
  init_invariant_backing();
14759
+ init_invariant_references();
13985
14760
  init_event_topology();
13986
14761
  init_source_file_linkage();
13987
14762
  init_method_realization();
@@ -13989,9 +14764,14 @@ var init_repository = __esm({
13989
14764
  init_call_conformance();
13990
14765
  init_hidden_state();
13991
14766
  init_dependency_conformance();
13992
- init_integration_conformance();
14767
+ init_integration_sim_declaration();
14768
+ init_integration_sim_file();
14769
+ init_integration_sim_wiring();
14770
+ init_integration_sim_coverage();
13993
14771
  init_coupling_health();
13994
- init_target_language();
14772
+ init_signature_language_builtins();
14773
+ init_narrative_language_constructs();
14774
+ init_technology_binding();
13995
14775
  init_technology_boundaries();
13996
14776
  init_naming_conventions();
13997
14777
  init_complexity_and_metadata();
@@ -14001,8 +14781,11 @@ var init_repository = __esm({
14001
14781
  SDD_RULES = [
14002
14782
  hierarchyRule,
14003
14783
  // Namespace integrity right after hierarchy: unresolvable/unwritable ids
14004
- // explain many downstream findings, so surface them early in the list.
14005
- namespaceHygieneRule,
14784
+ // explain many downstream findings, so surface them early in the list. Two
14785
+ // questions of the same ids: the segment no id may spend, and the local name
14786
+ // that would anchor a bare reference to the root instead.
14787
+ reservedIdSegmentsRule,
14788
+ namespaceShadowingRule,
14006
14789
  roundtripRule,
14007
14790
  // The type vocabulary in three questions: what a type declares about
14008
14791
  // itself, then the identifiers its fields name, then the ones its
@@ -14028,20 +14811,48 @@ var init_repository = __esm({
14028
14811
  narrativeStepConfigRule,
14029
14812
  narrativeReachabilityRule,
14030
14813
  narrativeJumpEdgesRule,
14031
- // Antipatterns right after flow soundness: they analyze the same step
14032
- // graphs and only make sense once the graphs are structurally valid.
14033
- narrativeAntipatternsRule,
14814
+ // Antipatterns right after flow soundness: they read the same step graphs
14815
+ // and only make sense once the graphs are structurally valid. Three things
14816
+ // structure alone can prove, in widening scope: a decision that decides
14817
+ // nothing, a step cycle nothing leaves, and a call cycle with no guard.
14818
+ meaninglessBranchesRule,
14819
+ inescapableCyclesRule,
14820
+ unconditionalCallCyclesRule,
14821
+ // The detail dial in two questions: does a method carry the detail its level
14822
+ // promises, and is that level low enough to be hiding something.
14034
14823
  narrativeDetailRule,
14824
+ detailSufficiencyRule,
14035
14825
  // Field shape before endpoint bindings: a Portal-only field on the wrong
14036
14826
  // stereotype explains the endpoint findings around it, and this half is
14037
14827
  // spec-scoped so the write boundary refuses it first.
14038
14828
  portalFieldsRule,
14829
+ // Endpoints from both sides: a Portal binds every method to its transport,
14830
+ // and nothing that is not a Portal may bind one at all.
14039
14831
  portalsRule,
14040
- // Cross-call auth: a narrative call into an authed Portal must name its
14041
- // credential source (rides with the portal family).
14832
+ nonPortalEndpointsRule,
14833
+ // Cross-call auth rides with the portal family, in two questions: does a
14834
+ // call into an authed Portal present a credential properly, and does the
14835
+ // source it names resolve to a provider it is wired to.
14042
14836
  portalCallAuthRule,
14043
- stereotypeDepsRule,
14044
- patternsRule,
14837
+ authSourceWiringRule,
14838
+ // Dependencies in five questions, one owner each: where an edge is
14839
+ // allowed to LAND (the boundary, and what an unresolved id means), then
14840
+ // the intra-subsystem matrix by the layer that answers for it — an
14841
+ // Orchestrator's declared class, the data blocks, the entry points and
14842
+ // the process layer — and finally the Portal read-face guard, the one
14843
+ // that reads narratives and dispatch tables rather than dependsOn.
14844
+ subsystemBoundaryDepsRule,
14845
+ logicDependencyClassRule,
14846
+ dataBlockDepsRule,
14847
+ entrypointDepsRule,
14848
+ portalWriteShortcutRule,
14849
+ // Patterns in four questions: who may own and what a claim must name,
14850
+ // what each pattern must contain, which data blocks are left standing
14851
+ // alone, and who may see a private member.
14852
+ patternMembershipRule,
14853
+ patternContainmentRule,
14854
+ unownedBlocksRule,
14855
+ memberVisibilityRule,
14045
14856
  // Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
14046
14857
  facadeForwardingRule,
14047
14858
  // Profiles in three questions, three owners: is the name real (the
@@ -14053,9 +14864,18 @@ var init_repository = __esm({
14053
14864
  patternReferencesRule,
14054
14865
  variantReferencesRule,
14055
14866
  // Pack-instantiated declarative doctrine rides with the pack-reference
14056
- // family: same data source, same provenance-bearing findings.
14057
- declarativeAssertionsRule,
14058
- publicSurfaceRule,
14867
+ // family: same data source, same provenance-bearing findings. One rule per
14868
+ // assertion kind — the kind is what a pack author writes, and each kind
14869
+ // asks its own question of its own collection.
14870
+ assertionForbiddenEdgesRule,
14871
+ assertionRequiredFieldsRule,
14872
+ assertionEndpointShapesRule,
14873
+ // The published surface in three questions: what backs the entry, whether
14874
+ // that component's stereotype can realize the type it declares, and whether
14875
+ // the contract it binds is that component's own.
14876
+ publicSurfaceBindingRule,
14877
+ publicSurfaceDeclaredTypeRule,
14878
+ publicSurfaceBoundContractRule,
14059
14879
  cyclesRule,
14060
14880
  // Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
14061
14881
  // reader sees the broken edge finding next to the unused-detection fallout
@@ -14063,7 +14883,11 @@ var init_repository = __esm({
14063
14883
  dispatchTableBindingsRule,
14064
14884
  dispatchStepRoutingRule,
14065
14885
  lifecycleRule,
14886
+ // The declared entrypoint's own prose before the walk that its declaration
14887
+ // silences, then the walk, then the types no walk can reach.
14888
+ invokedByDescriptionRule,
14066
14889
  reachabilityRule,
14890
+ unusedTypesRule,
14067
14891
  // The declaration (spec-scoped, refused at the write boundary) before the
14068
14892
  // round-trip consequences it enables.
14069
14893
  durabilityDeclarationRule,
@@ -14075,8 +14899,13 @@ var init_repository = __esm({
14075
14899
  untypedSeamRule,
14076
14900
  proseClaimRule,
14077
14901
  // Invariant registry rides with the semantic-edge family: declared entity
14078
- // invariants must be asserted on every write path (declarations, not proofs).
14902
+ // invariants must be asserted on every write path (declarations, not
14903
+ // proofs). Three questions of one registry: are the entity's ids unique,
14904
+ // does each invariant reach every write path of its owner, and does every
14905
+ // asserted reference name something declared.
14906
+ uniqueInvariantIdsRule,
14079
14907
  invariantBackingRule,
14908
+ invariantReferencesRule,
14080
14909
  // Pub/sub completeness: emitted topics need subscribers and vice versa.
14081
14910
  eventTopologyRule,
14082
14911
  // Code↔spec: structural conformance consumes the injected CodeModel (built
@@ -14095,10 +14924,22 @@ var init_repository = __esm({
14095
14924
  hiddenStateRule,
14096
14925
  dependencyConformanceRule,
14097
14926
  // Integration wiring proof rides after the code↔spec family: it consumes
14098
- // the same code model and speaks about the same sourcePath modules.
14099
- integrationConformanceRule,
14927
+ // the same code model and speaks about the same sourcePath modules. Four
14928
+ // questions about one harness, in the order a reader meets them: is one
14929
+ // expected here, does the declared one exist, does it wire the real
14930
+ // modules, does it name every narrated path.
14931
+ integrationSimDeclarationRule,
14932
+ integrationSimFileRule,
14933
+ integrationSimWiringRule,
14934
+ integrationSimCoverageRule,
14100
14935
  couplingRule,
14101
- languageRule,
14936
+ // Target-language fit in two questions: what a CONTRACT may name, and what
14937
+ // a NARRATIVE may describe.
14938
+ signatureLanguageBuiltinsRule,
14939
+ narrativeLanguageConstructsRule,
14940
+ // The declaration before the consequences: which stereotype may bind a
14941
+ // technology at all, then where its name may appear.
14942
+ technologyBindingRule,
14102
14943
  technologyRule,
14103
14944
  namingRule,
14104
14945
  complexityRule,
@@ -14808,11 +15649,15 @@ function buildRuleContext(opts) {
14808
15649
  }
14809
15650
  return { kind: "unresolved" };
14810
15651
  };
15652
+ const contractMethods = /* @__PURE__ */ new Map();
14811
15653
  const interfaceMethodsOf = (compId) => {
15654
+ const cached = contractMethods.get(compId);
15655
+ if (cached) return cached;
14812
15656
  const out = [];
14813
15657
  for (const intf of interfacesByComponent.get(compId) ?? []) {
14814
15658
  out.push(...intf.methods);
14815
15659
  }
15660
+ contractMethods.set(compId, out);
14816
15661
  return out;
14817
15662
  };
14818
15663
  const profileDefFor = (subsystemId) => {
@@ -14908,7 +15753,30 @@ function buildRuleContext(opts) {
14908
15753
  ...surfaceResolved ? { surfaceResolved: true } : {}
14909
15754
  });
14910
15755
  };
14911
- return {
15756
+ let codeIndexMemo;
15757
+ const codeIndex = () => codeIndexMemo ??= buildCodeIndex(ctx.codeModel);
15758
+ let realizationMemo;
15759
+ const realizationIndex = () => realizationMemo ??= buildRealizationIndex(ctx);
15760
+ const importGraphs = /* @__PURE__ */ new Map();
15761
+ const importGraph = (paths) => {
15762
+ const universe = paths ?? codeIndex().paths;
15763
+ const key = `${universe.size}\0${[...universe].join("\0")}`;
15764
+ let graph = importGraphs.get(key);
15765
+ if (!graph) {
15766
+ graph = buildImportGraph(codeIndex(), universe);
15767
+ importGraphs.set(key, graph);
15768
+ }
15769
+ return graph;
15770
+ };
15771
+ let ownershipMemo;
15772
+ const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
15773
+ let dependencyEdgesMemo;
15774
+ const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
15775
+ let specIdsMemo;
15776
+ const specIds = () => specIdsMemo ??= buildSpecIds(ctx);
15777
+ let implementationMethodsMemo;
15778
+ const implementationMethods = () => implementationMethodsMemo ??= buildImplementationMethods(ctx);
15779
+ const ctx = {
14912
15780
  system,
14913
15781
  subsystems,
14914
15782
  components,
@@ -14937,6 +15805,13 @@ function buildRuleContext(opts) {
14937
15805
  isCollapsedCrossTreeRef,
14938
15806
  resolveSurfaceRef,
14939
15807
  interfaceMethodsOf,
15808
+ codeIndex,
15809
+ realizationIndex,
15810
+ importGraph,
15811
+ ownershipIndex,
15812
+ dependencyEdges,
15813
+ specIds,
15814
+ implementationMethods,
14940
15815
  complexityConfigFor,
14941
15816
  documentationConfigFor,
14942
15817
  namingConfigFor,
@@ -14951,6 +15826,7 @@ function buildRuleContext(opts) {
14951
15826
  knownIssueCodes: opts.knownIssueCodes,
14952
15827
  addIssue
14953
15828
  };
15829
+ return ctx;
14954
15830
  }
14955
15831
  var DEPTH_RANK, DEPTH_GATED_CODES, COMPLETENESS_RULES;
14956
15832
  var init_rules = __esm({
@@ -14959,6 +15835,7 @@ var init_rules = __esm({
14959
15835
  init_models();
14960
15836
  init_extensions();
14961
15837
  init_types();
15838
+ init_read_model();
14962
15839
  init_repository();
14963
15840
  init_lint_allows();
14964
15841
  init_source_analysis();
@@ -21889,8 +22766,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
21889
22766
  const componentById = new Map(graph.components.map((c) => [c.id, c]));
21890
22767
  const resolveComponent = (id) => {
21891
22768
  if (componentById.has(id)) return componentById.get(id);
21892
- const matches2 = graph.components.filter((c) => c.id.endsWith(`::${id}`));
21893
- return matches2.length === 1 ? matches2[0] : void 0;
22769
+ const matches = graph.components.filter((c) => c.id.endsWith(`::${id}`));
22770
+ return matches.length === 1 ? matches[0] : void 0;
21894
22771
  };
21895
22772
  const entry = resolveComponent(componentId);
21896
22773
  if (!entry) {
@@ -23222,9 +24099,9 @@ var init_specs2 = __esm({
23222
24099
  }
23223
24100
  }
23224
24101
  const suffix = `::${id}`;
23225
- const matches2 = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
23226
- if (matches2.length === 1) {
23227
- return index.paths.subsystem[matches2[0]];
24102
+ const matches = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
24103
+ if (matches.length === 1) {
24104
+ return index.paths.subsystem[matches[0]];
23228
24105
  }
23229
24106
  if (pathExists(this.paths.specsSubsystemsDir()) && listFiles(this.paths.specsSubsystemsDir(), ".yaml").length > 0) {
23230
24107
  return path23.join(this.paths.specsSubsystemsDir(), `${id}.yaml`);
@@ -28511,8 +29388,8 @@ function materializeAgentLayer(agents, projectConfig, options) {
28511
29388
  } else if (options.domains) {
28512
29389
  filterDomainIds = options.domains.split(",").map((s) => s.trim()).filter(Boolean);
28513
29390
  } else if (options.domain) {
28514
- const matches2 = agents.map((a) => a.domainRoot).filter((d) => !!d && (d === options.domain || d.startsWith(`${options.domain}::`)));
28515
- filterDomainIds = Array.from(/* @__PURE__ */ new Set([options.domain, ...matches2]));
29391
+ const matches = agents.map((a) => a.domainRoot).filter((d) => !!d && (d === options.domain || d.startsWith(`${options.domain}::`)));
29392
+ filterDomainIds = Array.from(/* @__PURE__ */ new Set([options.domain, ...matches]));
28516
29393
  }
28517
29394
  const agentPool = filterDomainIds ? agents.filter((a) => filterDomainIds.includes(a.domainRoot ?? "root")) : agents;
28518
29395
  if (options.dryRun) {