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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1113,7 +1113,7 @@ var init_defaults = __esm({
1113
1113
  copilot: ".github/prompts",
1114
1114
  codex: ".codex/agents"
1115
1115
  };
1116
- WAIRON_VERSION = "5.1.1-dev.25";
1116
+ WAIRON_VERSION = "5.1.1-dev.26";
1117
1117
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1118
1118
  ARCHITECT_AGENT_ID = "agent-architect";
1119
1119
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -8694,6 +8694,209 @@ var init_types = __esm({
8694
8694
  }
8695
8695
  });
8696
8696
 
8697
+ // src/core/rules/read-model.ts
8698
+ function appendUnique(map, key, value, same) {
8699
+ const list = map.get(key);
8700
+ if (!list) {
8701
+ map.set(key, [value]);
8702
+ return;
8703
+ }
8704
+ if (same ? list.some((v) => same(v, value)) : list.includes(value)) return;
8705
+ list.push(value);
8706
+ }
8707
+ function buildCodeIndex(model) {
8708
+ const facts = /* @__PURE__ */ new Map();
8709
+ const paths = /* @__PURE__ */ new Set();
8710
+ const exactPaths = /* @__PURE__ */ new Set();
8711
+ for (const f of model.files) {
8712
+ const key = pathKey(f.path);
8713
+ facts.set(key, f);
8714
+ paths.add(key);
8715
+ if (f.status === "analyzed" && f.analysisGrade === "exact") exactPaths.add(key);
8716
+ }
8717
+ const declarations = /* @__PURE__ */ new Map();
8718
+ const anchors = /* @__PURE__ */ new Map();
8719
+ const findingAnchors = /* @__PURE__ */ new Map();
8720
+ const namesAt = (cache, path27, make) => {
8721
+ const cached = cache.get(path27);
8722
+ if (cached) return cached;
8723
+ const f = facts.get(path27);
8724
+ if (!f) return NO_NAMES;
8725
+ const built = make(f);
8726
+ cache.set(path27, built);
8727
+ return built;
8728
+ };
8729
+ return {
8730
+ paths,
8731
+ exactPaths,
8732
+ factsAt: (path27) => facts.get(pathKey(path27)),
8733
+ declarationsAt: (path27) => namesAt(declarations, pathKey(path27), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames])),
8734
+ anchorsAt: (path27) => namesAt(anchors, pathKey(path27), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
8735
+ findingAnchorsAt: (path27) => namesAt(findingAnchors, pathKey(path27), (f) => new Set(f.anchoredNames))
8736
+ };
8737
+ }
8738
+ function buildRealizationIndex(ctx) {
8739
+ const implsByComponent = /* @__PURE__ */ new Map();
8740
+ const implsByPath = /* @__PURE__ */ new Map();
8741
+ const filesByComponent = /* @__PURE__ */ new Map();
8742
+ const filesBySubsystem = /* @__PURE__ */ new Map();
8743
+ const componentsByPath = /* @__PURE__ */ new Map();
8744
+ const paths = [];
8745
+ const seenPaths = /* @__PURE__ */ new Set();
8746
+ for (const impl of ctx.implementations) {
8747
+ const contract = ctx.interfaceMap.get(impl.contract);
8748
+ if (!contract) continue;
8749
+ const component = ctx.componentMap.get(contract.component);
8750
+ if (!component) continue;
8751
+ if (ctx.isInChainedSubproject(component.subsystem)) continue;
8752
+ appendUnique(implsByComponent, component.id, impl);
8753
+ for (const file of implementationSourceFiles(impl)) {
8754
+ const p = pathKey(file);
8755
+ if (!seenPaths.has(p)) {
8756
+ seenPaths.add(p);
8757
+ paths.push(p);
8758
+ }
8759
+ appendUnique(implsByPath, p, impl);
8760
+ appendUnique(filesByComponent, component.id, p);
8761
+ appendUnique(filesBySubsystem, component.subsystem, p);
8762
+ appendUnique(componentsByPath, p, component, (a, b) => a.id === b.id);
8763
+ }
8764
+ }
8765
+ const NONE_IMPL = [];
8766
+ const NONE_COMP = [];
8767
+ const NONE_PATH = [];
8768
+ return {
8769
+ paths,
8770
+ implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
8771
+ implementationsAt: (path27) => implsByPath.get(pathKey(path27)) ?? NONE_IMPL,
8772
+ filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
8773
+ filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
8774
+ componentsAt: (path27) => componentsByPath.get(pathKey(path27)) ?? NONE_COMP
8775
+ };
8776
+ }
8777
+ function buildImportGraph(index, universe) {
8778
+ const imports = /* @__PURE__ */ new Map();
8779
+ const traces = /* @__PURE__ */ new Map();
8780
+ const resolveAll = (from, specifiers, into) => {
8781
+ for (const specifier of specifiers) {
8782
+ const to = resolveImport(from, specifier, universe);
8783
+ if (to && to !== from) into.add(to);
8784
+ }
8785
+ };
8786
+ const importsOf = (path27) => {
8787
+ const cached = imports.get(path27);
8788
+ if (cached) return cached;
8789
+ const facts = index.factsAt(path27);
8790
+ const out = /* @__PURE__ */ new Set();
8791
+ if (facts?.status === "analyzed") resolveAll(path27, facts.imports, out);
8792
+ imports.set(path27, out);
8793
+ return out;
8794
+ };
8795
+ const tracesOf = (path27) => {
8796
+ const cached = traces.get(path27);
8797
+ if (cached) return cached;
8798
+ const facts = index.factsAt(path27);
8799
+ const out = new Set(importsOf(path27));
8800
+ if (facts?.status === "analyzed") resolveAll(path27, facts.reexports, out);
8801
+ traces.set(path27, out);
8802
+ return out;
8803
+ };
8804
+ return {
8805
+ paths: [...universe],
8806
+ importsOf,
8807
+ tracesOf,
8808
+ connects: (from, to, allowReverse) => {
8809
+ for (const f of from) {
8810
+ const forward = tracesOf(f);
8811
+ for (const t of to) if (forward.has(t)) return true;
8812
+ }
8813
+ if (allowReverse) {
8814
+ for (const t of to) {
8815
+ const reverse = tracesOf(t);
8816
+ for (const f of from) if (reverse.has(f)) return true;
8817
+ }
8818
+ }
8819
+ return false;
8820
+ },
8821
+ reachFrom: (start) => {
8822
+ const seen = /* @__PURE__ */ new Set([start]);
8823
+ const stack = [start];
8824
+ while (stack.length) {
8825
+ const from = stack.pop();
8826
+ for (const to of tracesOf(from)) {
8827
+ if (seen.has(to)) continue;
8828
+ seen.add(to);
8829
+ stack.push(to);
8830
+ }
8831
+ }
8832
+ return seen;
8833
+ }
8834
+ };
8835
+ }
8836
+ function buildOwnershipIndex(ctx) {
8837
+ const ownedBy = /* @__PURE__ */ new Map();
8838
+ for (const comp of ctx.components) {
8839
+ if (isRetired(comp) || !isPattern(comp)) continue;
8840
+ for (const memberId of comp.owns) {
8841
+ const member = ctx.componentMap.get(memberId);
8842
+ if (!member || isPattern(member)) continue;
8843
+ if (!ownedBy.has(memberId)) ownedBy.set(memberId, comp.id);
8844
+ }
8845
+ }
8846
+ return {
8847
+ ownedMembers: new Set(ownedBy.keys()),
8848
+ ownerOf: (memberId) => ownedBy.get(memberId)
8849
+ };
8850
+ }
8851
+ function buildDependencyEdges(ctx) {
8852
+ const all = [];
8853
+ const matrix = [];
8854
+ for (const from of ctx.components) {
8855
+ const fromDraft = ctx.isComponentDraft(from.id);
8856
+ const profile = ctx.ext.profiles[ctx.getComponentProfile(from.id)];
8857
+ for (const ref of from.dependsOn) {
8858
+ const to = ctx.componentMap.get(ref);
8859
+ if (!to) {
8860
+ all.push(offTreeEdge(ctx, from, ref, fromDraft));
8861
+ continue;
8862
+ }
8863
+ const edge = {
8864
+ from,
8865
+ ref,
8866
+ to,
8867
+ reach: to.subsystem === from.subsystem ? "internal" : "cross-subsystem",
8868
+ retired: isRetired(from) || isRetired(to),
8869
+ draftContext: fromDraft || ctx.isComponentDraft(to.id),
8870
+ licensed: profile?.allowedEdges?.some(
8871
+ (e) => e.from.includes(from.componentType) && e.to.includes(to.componentType)
8872
+ ) ?? false
8873
+ };
8874
+ all.push(edge);
8875
+ if (edge.reach === "internal" && !edge.retired && !edge.licensed) matrix.push(edge);
8876
+ }
8877
+ }
8878
+ return { all, matrix };
8879
+ }
8880
+ function offTreeEdge(ctx, from, ref, fromDraft) {
8881
+ const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
8882
+ const external = ctx.isExternalNamespaceRef(ref);
8883
+ if (external || ctx.isCollapsedCrossTreeRef(ref, from.subsystem)) {
8884
+ const surface = ctx.resolveSurfaceRef(ref, from.subsystem);
8885
+ if (surface.kind === "ambiguous") return { ...base, reach: "ambiguous", surface };
8886
+ if (surface.kind === "resolved") return { ...base, reach: "surface", surface };
8887
+ if (external) return { ...base, reach: "unpinned" };
8888
+ }
8889
+ return { ...base, reach: "missing" };
8890
+ }
8891
+ var NO_NAMES;
8892
+ var init_read_model = __esm({
8893
+ "src/core/rules/read-model.ts"() {
8894
+ "use strict";
8895
+ init_models();
8896
+ NO_NAMES = /* @__PURE__ */ new Set();
8897
+ }
8898
+ });
8899
+
8697
8900
  // src/core/rules/integrity/hierarchy-integrity.ts
8698
8901
  var hierarchyRule;
8699
8902
  var init_hierarchy_integrity = __esm({
@@ -9902,8 +10105,7 @@ var init_narrative_detail = __esm({
9902
10105
  { code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
9903
10106
  ],
9904
10107
  check(ctx) {
9905
- const factsByPath = /* @__PURE__ */ new Map();
9906
- for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
10108
+ const code = ctx.codeIndex();
9907
10109
  for (const impl of ctx.implementations) {
9908
10110
  const contract = ctx.interfaceMap.get(impl.contract);
9909
10111
  if (!contract) continue;
@@ -9929,7 +10131,7 @@ var init_narrative_detail = __esm({
9929
10131
  const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
9930
10132
  const file = methodSourceFile(implMethod, impl.sourcePath);
9931
10133
  if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
9932
- const facts = factsByPath.get(pathKey(file));
10134
+ const facts = code.factsAt(file);
9933
10135
  const symbol = implMethod.symbol ?? implMethod.name;
9934
10136
  const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
9935
10137
  const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
@@ -10075,7 +10277,7 @@ var init_logic_declaration = __esm({
10075
10277
  logicDeclarationRule = {
10076
10278
  name: "logic-declaration",
10077
10279
  scope: "spec",
10078
- description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by stereotype-dependencies. Intrinsic to one component: no tree required.",
10280
+ description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by logic-dependency-class. Intrinsic to one component: no tree required.",
10079
10281
  codes: [
10080
10282
  { code: "DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR", defaultSeverity: "error", summary: "dependencyClass declared on a component that is not an Orchestrator" }
10081
10283
  ],
@@ -10301,257 +10503,341 @@ var init_portal_call_auth = __esm({
10301
10503
  }
10302
10504
  });
10303
10505
 
10304
- // src/core/rules/doctrine/stereotype-dependencies.ts
10305
- var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf, stereotypeDepsRule;
10306
- var init_stereotype_dependencies = __esm({
10307
- "src/core/rules/doctrine/stereotype-dependencies.ts"() {
10506
+ // src/core/rules/doctrine/subsystem-boundary-dependencies.ts
10507
+ var subsystemBoundaryDepsRule;
10508
+ var init_subsystem_boundary_dependencies = __esm({
10509
+ "src/core/rules/doctrine/subsystem-boundary-dependencies.ts"() {
10308
10510
  "use strict";
10309
10511
  init_models();
10310
- NEVER_INLINE_STATE = " Never resolve this by merging the store's state into the consuming component \u2014 state hidden inside a logic block is invisible to the spec and unrecoverable.";
10311
- storeResolutionHint = (consumerType, storeId) => {
10312
- if (consumerType === "Orchestrator") {
10313
- return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
10314
- }
10315
- return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
10316
- };
10317
- isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
10318
- isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
10319
- stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
10320
- stereotypeDepsRule = {
10321
- name: "stereotype-dependencies",
10322
- description: "Enforces the component-stereotype interaction matrix and the cross-subsystem shape. The matrix: a Portal reaches the data layer only through Repository/Index READ faces, and writes route through Orchestrators (PORTAL_WRITE_SHORTCUT); Stores are depended upon; Registries write their Store; Adapters are sinks; Views stay passive; an Orchestrator's dependencyClass bounds what it may depend on; a Supervisor reaches data only through workflows; a live Actor is reached through a Supervisor that supervises it; a Query reads its Store; any block may use pure logic. Across subsystems: client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A governing pack profile may license intra-subsystem edges the matrix refuses via allowedEdges (its platform idiom, with the stated reason); boundary rules stay unrelaxable.",
10512
+ subsystemBoundaryDepsRule = {
10513
+ name: "subsystem-boundary-dependencies",
10514
+ description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A reference that names nothing in this tree is resolved against the stored surface snapshots: a hit is a DECLARED remote portal and the same Adapter requirement applies to it, snapshots of several providers that disagree make the reference ambiguous, and a reference authored to leave this root that no snapshot covers warns instead of erroring. Boundary rules are never relaxed by a pack profile.",
10323
10515
  codes: [
10324
10516
  { code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
10325
10517
  { code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
10326
10518
  { code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
10327
10519
  { code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
10328
10520
  { code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
10329
- { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" },
10330
- { code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
10331
- { code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
10332
- { code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
10333
- { code: "ARCHITECTURE_VIOLATION_REGISTRY_DEP", defaultSeverity: "warning", summary: "Registry depending on anything but its Store, a backend Adapter or pure logic (warning while new; aligned to the standard \xA77 validate\u2192write path)" },
10334
- { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
10335
- { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
10336
- { code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
10337
- { code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" },
10338
- { code: "DEPENDENCY_CLASS_VIOLATION", defaultSeverity: "error", summary: "Orchestrator depending on a component its dependencyClass does not allow: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters" },
10339
- { code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on anything but Actors, Orchestrators, Adapters or other Supervisors \u2014 it reaches data only through workflows" },
10340
- { code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise, without also depending on a Supervisor that supervises it \u2014 a live Actor is reached by id through its Supervisor" },
10341
- { code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
10521
+ { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
10342
10522
  ],
10343
10523
  check(ctx) {
10344
- for (const comp of ctx.components) {
10345
- const isDraftCtx = ctx.isComponentDraft(comp.id);
10346
- const dependencies = comp.dependsOn;
10347
- for (const depId of dependencies) {
10348
- const depComp = ctx.componentMap.get(depId);
10349
- if (!depComp) {
10350
- const external = ctx.isExternalNamespaceRef(depId);
10351
- if (external || ctx.isCollapsedCrossTreeRef(depId, comp.subsystem)) {
10352
- const resolved = ctx.resolveSurfaceRef(depId, comp.subsystem);
10353
- if (resolved.kind === "ambiguous") {
10354
- ctx.addIssue("error", "SURFACE_REF_AMBIGUOUS", ambiguityMessage(resolved, `Component "${comp.id}" depends on`, depId), comp.id, isDraftCtx);
10355
- continue;
10356
- }
10357
- if (resolved.kind === "resolved") {
10358
- if (comp.componentType !== "Adapter") {
10359
- ctx.addIssue(
10360
- "error",
10361
- "CROSS_SUBSYSTEM_NON_ADAPTER",
10362
- `Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${depId}", a surface of project "${resolved.snapshot.projectName}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
10363
- comp.id,
10364
- isDraftCtx,
10365
- true
10366
- );
10367
- }
10368
- continue;
10369
- }
10370
- if (external) {
10371
- ctx.addIssue(
10372
- "warning",
10373
- "CROSS_TREE_REF_UNRESOLVED",
10374
- `Component "${comp.id}" depends on cross-tree component "${depId}", and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
10375
- comp.id,
10376
- isDraftCtx
10377
- );
10378
- continue;
10379
- }
10380
- }
10524
+ for (const edge of ctx.dependencyEdges().all) {
10525
+ if (edge.retired) continue;
10526
+ const comp = edge.from;
10527
+ switch (edge.reach) {
10528
+ case "missing":
10381
10529
  ctx.addIssue(
10382
10530
  "error",
10383
10531
  "INVALID_DEPENDENCY_REFERENCE",
10384
- `Component "${comp.id}" lists dependency "${depId}" which does not exist.`,
10532
+ `Component "${comp.id}" lists dependency "${edge.ref}" which does not exist.`,
10385
10533
  comp.id,
10386
- isDraftCtx
10534
+ edge.draftContext
10387
10535
  );
10388
10536
  continue;
10389
- }
10390
- if (isRetired(comp) || isRetired(depComp)) continue;
10391
- if (depComp.subsystem !== comp.subsystem) {
10392
- const crossDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
10393
- const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
10394
- const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
10395
- if (comp.componentType !== "Adapter" && !licensed) {
10396
- ctx.addIssue(
10397
- "error",
10398
- "CROSS_SUBSYSTEM_NON_ADAPTER",
10399
- `Boundary violation: ${comp.componentType} "${comp.id}" (subsystem "${comp.subsystem}") depends directly on "${depComp.id}" in subsystem "${depComp.subsystem}". Only a local client Adapter may cross a subsystem boundary \u2014 route this hop through an Adapter that calls "${depComp.subsystem}"'s public interface, or declare a trustedLink on "${comp.subsystem}" (with the reason) to license a direct in-process edge to that peer's published Portal.`,
10400
- comp.id,
10401
- crossDraft
10402
- );
10403
- }
10404
- const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
10405
- if (!targetIsPublic) {
10406
- ctx.addIssue(
10407
- "error",
10408
- "CROSS_SUBSYSTEM_PRIVATE_ACCESS",
10409
- `Boundary violation: "${comp.id}" depends on "${depComp.id}", which is not part of subsystem "${depComp.subsystem}"'s published public surface. Depend on one of its publicInterfaces components instead.`,
10410
- comp.id,
10411
- crossDraft
10412
- );
10413
- } else if (depComp.componentType !== "Portal") {
10414
- const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
10415
- ctx.addIssue(
10416
- "error",
10417
- "CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
10418
- `Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
10419
- comp.id,
10420
- crossDraft
10421
- );
10422
- }
10423
- continue;
10424
- }
10425
- const governingProfile2 = ctx.ext.profiles[ctx.getComponentProfile(comp.id)];
10426
- if (governingProfile2?.allowedEdges?.some((e) => e.from.includes(comp.componentType) && e.to.includes(depComp.componentType))) {
10537
+ case "unpinned":
10538
+ ctx.addIssue(
10539
+ "warning",
10540
+ "CROSS_TREE_REF_UNRESOLVED",
10541
+ `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.`,
10542
+ comp.id,
10543
+ edge.draftContext
10544
+ );
10427
10545
  continue;
10428
- }
10429
- const edgeDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
10430
- const depPure = isPureLogic(depComp);
10431
- const depRead = isReadLogic(depComp);
10432
- const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10433
- if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
10546
+ case "ambiguous":
10434
10547
  ctx.addIssue(
10435
10548
  "error",
10436
- "ARCHITECTURE_VIOLATION_PORTAL_DEP",
10437
- `Architectural violation: Component "${comp.id}" cannot depend on ${depComp.componentType} component "${depComp.id}". ${depComp.componentType}s are top-level entry points/subscribers and cannot be dependencies.`,
10549
+ "SURFACE_REF_AMBIGUOUS",
10550
+ ambiguityMessage(edge.surface, `Component "${comp.id}" depends on`, edge.ref),
10438
10551
  comp.id,
10439
- edgeDraft
10552
+ edge.draftContext
10440
10553
  );
10441
10554
  continue;
10442
- }
10443
- if (comp.componentType === "Portal" || comp.componentType === "Observer") {
10444
- const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
10445
- if (forbiddenTypes.includes(depComp.componentType)) {
10555
+ case "surface":
10556
+ if (comp.componentType !== "Adapter") {
10557
+ const provider = edge.surface.kind === "resolved" ? edge.surface.snapshot.projectName : "";
10446
10558
  ctx.addIssue(
10447
10559
  "error",
10448
- "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
10449
- `Architectural violation: ${comp.componentType} component "${comp.id}" cannot depend directly on "${depComp.componentType}" component "${depComp.id}". ${comp.componentType}s coordinate through Orchestrators (and Supervisors); they do not reach the data layer directly.` + storeHint,
10560
+ "CROSS_SUBSYSTEM_NON_ADAPTER",
10561
+ `Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${edge.ref}", a surface of project "${provider}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
10450
10562
  comp.id,
10451
- edgeDraft
10563
+ edge.draftContext,
10564
+ true
10452
10565
  );
10453
10566
  }
10454
- }
10455
- if (isPureLogic(comp) && !depPure) {
10456
- ctx.addIssue(
10457
- "error",
10458
- "DEPENDENCY_CLASS_VIOLATION",
10459
- `Architectural violation: Orchestrator "${comp.id}" (dependencyClass pure) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Pure logic depends only on pure Orchestrators \u2014 move that dependency to the workflow that calls "${comp.id}", or declare "${comp.id}" read when it reads through read logic, a Repository, an Index or an Adapter.` + storeHint,
10460
- comp.id,
10461
- edgeDraft
10462
- );
10463
- }
10464
- if (isReadLogic(comp) && !depPure && !depRead && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
10465
- ctx.addIssue(
10466
- "error",
10467
- "DEPENDENCY_CLASS_VIOLATION",
10468
- `Architectural violation: Orchestrator "${comp.id}" (dependencyClass read) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Read logic depends only on pure or read Orchestrators, Repositories, Indexes and Adapters \u2014 move that dependency to the workflow that calls "${comp.id}", or unset the dependencyClass of "${comp.id}" when it is itself a workflow.` + storeHint,
10469
- comp.id,
10470
- edgeDraft
10471
- );
10472
- }
10473
- if (comp.componentType === "Store" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10474
- ctx.addIssue(
10475
- "error",
10476
- "ARCHITECTURE_VIOLATION_STORE_DEP",
10477
- `Architectural violation: Store component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". Stores may only depend on other Stores, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse.`,
10478
- comp.id,
10479
- edgeDraft
10480
- );
10481
- }
10482
- if (comp.componentType === "Registry" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10483
- ctx.addIssue(
10484
- "warning",
10485
- "ARCHITECTURE_VIOLATION_REGISTRY_DEP",
10486
- `Architectural violation: Registry component "${comp.id}" should not depend on "${depComp.componentType}" component "${depComp.id}". A Registry is the write path to its Store and may depend only on that Store, a backend Adapter, or pure logic that validates the write \u2014 the Registry never updates Indexes and never drives workflow.`,
10487
- comp.id,
10488
- edgeDraft
10489
- );
10490
- }
10491
- if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
10492
- ctx.addIssue(
10493
- "error",
10494
- "ARCHITECTURE_VIOLATION_ADAPTER_DEP",
10495
- `Architectural violation: Adapter component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". An Adapter may use pure logic, but never a Store or any other Orchestrator.`,
10496
- comp.id,
10497
- edgeDraft
10498
- );
10499
- }
10500
- if (comp.componentType === "Index" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10501
- ctx.addIssue(
10502
- "error",
10503
- "ARCHITECTURE_VIOLATION_INDEX_DEP",
10504
- `Architectural violation: Index component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". An Index is a read projection and may depend only on its Store, a backend Adapter or pure logic.`,
10505
- comp.id,
10506
- edgeDraft
10507
- );
10508
- }
10509
- if (comp.componentType === "Query" && depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure) {
10510
- ctx.addIssue(
10511
- "error",
10512
- "ARCHITECTURE_VIOLATION_QUERY_DEP",
10513
- `Architectural violation: Query "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Query computes reads over its Store, through a backend Adapter where one serves it, with pure logic \u2014 move any other dependency to the workflow that calls the Repository facade.`,
10514
- comp.id,
10515
- edgeDraft
10516
- );
10517
- }
10518
- if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !depPure)) {
10567
+ continue;
10568
+ case "cross-subsystem":
10569
+ break;
10570
+ default:
10571
+ continue;
10572
+ }
10573
+ const depComp = edge.to;
10574
+ const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
10575
+ const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
10576
+ if (comp.componentType !== "Adapter" && !licensed) {
10577
+ ctx.addIssue(
10578
+ "error",
10579
+ "CROSS_SUBSYSTEM_NON_ADAPTER",
10580
+ `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.`,
10581
+ comp.id,
10582
+ edge.draftContext
10583
+ );
10584
+ }
10585
+ const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
10586
+ if (!targetIsPublic) {
10587
+ ctx.addIssue(
10588
+ "error",
10589
+ "CROSS_SUBSYSTEM_PRIVATE_ACCESS",
10590
+ `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.`,
10591
+ comp.id,
10592
+ edge.draftContext
10593
+ );
10594
+ } else if (depComp.componentType !== "Portal") {
10595
+ const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
10596
+ ctx.addIssue(
10597
+ "error",
10598
+ "CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
10599
+ `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.`,
10600
+ comp.id,
10601
+ edge.draftContext
10602
+ );
10603
+ }
10604
+ }
10605
+ }
10606
+ };
10607
+ }
10608
+ });
10609
+
10610
+ // src/core/rules/doctrine/stereotype-terms.ts
10611
+ var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf;
10612
+ var init_stereotype_terms = __esm({
10613
+ "src/core/rules/doctrine/stereotype-terms.ts"() {
10614
+ "use strict";
10615
+ 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.";
10616
+ storeResolutionHint = (consumerType, storeId) => {
10617
+ if (consumerType === "Orchestrator") {
10618
+ return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
10619
+ }
10620
+ return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
10621
+ };
10622
+ isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
10623
+ isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
10624
+ stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
10625
+ }
10626
+ });
10627
+
10628
+ // src/core/rules/doctrine/logic-dependency-class.ts
10629
+ var logicDependencyClassRule;
10630
+ var init_logic_dependency_class = __esm({
10631
+ "src/core/rules/doctrine/logic-dependency-class.ts"() {
10632
+ "use strict";
10633
+ init_stereotype_terms();
10634
+ logicDependencyClassRule = {
10635
+ name: "logic-dependency-class",
10636
+ 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.",
10637
+ codes: [
10638
+ { 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" }
10639
+ ],
10640
+ check(ctx) {
10641
+ for (const edge of ctx.dependencyEdges().matrix) {
10642
+ const comp = edge.from;
10643
+ const depComp = edge.to;
10644
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
10645
+ const depPure = isPureLogic(depComp);
10646
+ const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10647
+ if (isPureLogic(comp) && !depPure) {
10648
+ ctx.addIssue(
10649
+ "error",
10650
+ "DEPENDENCY_CLASS_VIOLATION",
10651
+ `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,
10652
+ comp.id,
10653
+ edge.draftContext
10654
+ );
10655
+ }
10656
+ if (isReadLogic(comp) && !depPure && !isReadLogic(depComp) && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
10657
+ ctx.addIssue(
10658
+ "error",
10659
+ "DEPENDENCY_CLASS_VIOLATION",
10660
+ `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,
10661
+ comp.id,
10662
+ edge.draftContext
10663
+ );
10664
+ }
10665
+ }
10666
+ }
10667
+ };
10668
+ }
10669
+ });
10670
+
10671
+ // src/core/rules/doctrine/data-block-dependencies.ts
10672
+ var dataBlockDepsRule;
10673
+ var init_data_block_dependencies = __esm({
10674
+ "src/core/rules/doctrine/data-block-dependencies.ts"() {
10675
+ "use strict";
10676
+ init_stereotype_terms();
10677
+ dataBlockDepsRule = {
10678
+ name: "data-block-dependencies",
10679
+ 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.",
10680
+ codes: [
10681
+ { code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
10682
+ { 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)" },
10683
+ { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
10684
+ { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
10685
+ { code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
10686
+ ],
10687
+ check(ctx) {
10688
+ for (const edge of ctx.dependencyEdges().matrix) {
10689
+ const comp = edge.from;
10690
+ const depComp = edge.to;
10691
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
10692
+ const depPure = isPureLogic(depComp);
10693
+ const ownStoreOnly = depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure;
10694
+ if (comp.componentType === "Store" && ownStoreOnly) {
10695
+ ctx.addIssue(
10696
+ "error",
10697
+ "ARCHITECTURE_VIOLATION_STORE_DEP",
10698
+ `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.`,
10699
+ comp.id,
10700
+ edge.draftContext
10701
+ );
10702
+ }
10703
+ if (comp.componentType === "Registry" && ownStoreOnly) {
10704
+ ctx.addIssue(
10705
+ "warning",
10706
+ "ARCHITECTURE_VIOLATION_REGISTRY_DEP",
10707
+ `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.`,
10708
+ comp.id,
10709
+ edge.draftContext
10710
+ );
10711
+ }
10712
+ if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
10713
+ ctx.addIssue(
10714
+ "error",
10715
+ "ARCHITECTURE_VIOLATION_ADAPTER_DEP",
10716
+ `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.`,
10717
+ comp.id,
10718
+ edge.draftContext
10719
+ );
10720
+ }
10721
+ if (comp.componentType === "Index" && ownStoreOnly) {
10722
+ ctx.addIssue(
10723
+ "error",
10724
+ "ARCHITECTURE_VIOLATION_INDEX_DEP",
10725
+ `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.`,
10726
+ comp.id,
10727
+ edge.draftContext
10728
+ );
10729
+ }
10730
+ if (comp.componentType === "Query" && ownStoreOnly) {
10731
+ ctx.addIssue(
10732
+ "error",
10733
+ "ARCHITECTURE_VIOLATION_QUERY_DEP",
10734
+ `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.`,
10735
+ comp.id,
10736
+ edge.draftContext
10737
+ );
10738
+ }
10739
+ }
10740
+ }
10741
+ };
10742
+ }
10743
+ });
10744
+
10745
+ // src/core/rules/doctrine/entrypoint-dependencies.ts
10746
+ var entrypointDepsRule;
10747
+ var init_entrypoint_dependencies = __esm({
10748
+ "src/core/rules/doctrine/entrypoint-dependencies.ts"() {
10749
+ "use strict";
10750
+ init_stereotype_terms();
10751
+ entrypointDepsRule = {
10752
+ name: "entrypoint-dependencies",
10753
+ 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.",
10754
+ codes: [
10755
+ { code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
10756
+ { code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
10757
+ { code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
10758
+ { 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" },
10759
+ { 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" }
10760
+ ],
10761
+ check(ctx) {
10762
+ for (const edge of ctx.dependencyEdges().matrix) {
10763
+ const comp = edge.from;
10764
+ const depComp = edge.to;
10765
+ const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
10766
+ if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
10767
+ ctx.addIssue(
10768
+ "error",
10769
+ "ARCHITECTURE_VIOLATION_PORTAL_DEP",
10770
+ `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.`,
10771
+ comp.id,
10772
+ edge.draftContext
10773
+ );
10774
+ continue;
10775
+ }
10776
+ if (comp.componentType === "Portal" || comp.componentType === "Observer") {
10777
+ const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
10778
+ if (forbiddenTypes.includes(depComp.componentType)) {
10519
10779
  ctx.addIssue(
10520
10780
  "error",
10521
- "ARCHITECTURE_VIOLATION_VIEW_DEP",
10522
- `Architectural violation: View component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". Views must remain passive UI blocks that use pure logic at most.` + storeHint,
10781
+ "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
10782
+ `Architectural violation: ${comp.componentType} component "${comp.id}" cannot depend directly on "${depComp.componentType}" component "${depComp.id}". ${comp.componentType}s coordinate through Orchestrators (and Supervisors); they do not reach the data layer directly.` + storeHint,
10523
10783
  comp.id,
10524
- edgeDraft
10784
+ edge.draftContext
10525
10785
  );
10526
10786
  }
10527
- if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
10787
+ }
10788
+ if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !isPureLogic(depComp))) {
10789
+ ctx.addIssue(
10790
+ "error",
10791
+ "ARCHITECTURE_VIOLATION_VIEW_DEP",
10792
+ `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,
10793
+ comp.id,
10794
+ edge.draftContext
10795
+ );
10796
+ }
10797
+ if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
10798
+ ctx.addIssue(
10799
+ "error",
10800
+ "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
10801
+ `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,
10802
+ comp.id,
10803
+ edge.draftContext
10804
+ );
10805
+ }
10806
+ if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
10807
+ const reachedThroughSupervisor = comp.dependsOn.some((id) => {
10808
+ const supervisor = ctx.componentMap.get(id);
10809
+ return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
10810
+ });
10811
+ if (!reachedThroughSupervisor) {
10812
+ const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
10813
+ const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
10528
10814
  ctx.addIssue(
10529
10815
  "error",
10530
- "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
10531
- `Architectural violation: Supervisor "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Supervisor reaches data only through workflows \u2014 depend on the Orchestrator, Actor or Adapter that does that work instead.` + storeHint,
10816
+ "ACTOR_REACHED_WITHOUT_SUPERVISOR",
10817
+ `Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
10532
10818
  comp.id,
10533
- edgeDraft
10819
+ edge.draftContext
10534
10820
  );
10535
10821
  }
10536
- if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
10537
- const reachedThroughSupervisor = comp.dependsOn.some((id) => {
10538
- const supervisor = ctx.componentMap.get(id);
10539
- return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
10540
- });
10541
- if (!reachedThroughSupervisor) {
10542
- const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
10543
- const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
10544
- ctx.addIssue(
10545
- "error",
10546
- "ACTOR_REACHED_WITHOUT_SUPERVISOR",
10547
- `Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
10548
- comp.id,
10549
- edgeDraft
10550
- );
10551
- }
10552
- }
10553
10822
  }
10554
10823
  }
10824
+ }
10825
+ };
10826
+ }
10827
+ });
10828
+
10829
+ // src/core/rules/doctrine/portal-write-shortcut.ts
10830
+ var portalWriteShortcutRule;
10831
+ var init_portal_write_shortcut = __esm({
10832
+ "src/core/rules/doctrine/portal-write-shortcut.ts"() {
10833
+ "use strict";
10834
+ portalWriteShortcutRule = {
10835
+ name: "portal-write-shortcut",
10836
+ 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.",
10837
+ codes: [
10838
+ { 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)" }
10839
+ ],
10840
+ check(ctx) {
10555
10841
  for (const impl of ctx.implementations) {
10556
10842
  const contract = ctx.interfaceMap.get(impl.contract);
10557
10843
  const component = contract ? ctx.componentMap.get(contract.component) : void 0;
@@ -10596,31 +10882,23 @@ var init_stereotype_dependencies = __esm({
10596
10882
  }
10597
10883
  });
10598
10884
 
10599
- // src/core/rules/doctrine/pattern-ownership.ts
10600
- var patternsRule;
10601
- var init_pattern_ownership = __esm({
10602
- "src/core/rules/doctrine/pattern-ownership.ts"() {
10885
+ // src/core/rules/doctrine/pattern-membership.ts
10886
+ var patternMembershipRule;
10887
+ var init_pattern_membership = __esm({
10888
+ "src/core/rules/doctrine/pattern-membership.ts"() {
10603
10889
  "use strict";
10604
10890
  init_models();
10605
- patternsRule = {
10606
- name: "pattern-ownership",
10607
- description: "Patterns (Repository/FeatureComponent/RouterComponent) own member blocks with the containment their definition prescribes; blocks own nothing; a member has exactly one owner; and private members are reachable only via their facade or siblings.",
10891
+ patternMembershipRule = {
10892
+ name: "pattern-membership",
10893
+ description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern to claim it.",
10608
10894
  codes: [
10609
10895
  { code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
10610
10896
  { code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block using owns" },
10611
10897
  { code: "INVALID_OWNED_MEMBER", defaultSeverity: "error", summary: "owns names a non-existent component" },
10612
10898
  { code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
10613
- { code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" },
10614
- { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
10615
- { code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
10616
- { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
10617
- { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
10618
- { code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
10619
- { code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" },
10620
- { code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
10899
+ { code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
10621
10900
  ],
10622
10901
  check(ctx) {
10623
- const ownedBy = /* @__PURE__ */ new Map();
10624
10902
  for (const comp of ctx.components) {
10625
10903
  if (isRetired(comp)) continue;
10626
10904
  const isDraftCtx = ctx.isComponentDraft(comp.id);
@@ -10631,6 +10909,12 @@ var init_pattern_ownership = __esm({
10631
10909
  if (!pattern && comp.owns.length > 0) {
10632
10910
  ctx.addIssue("error", "BLOCK_OWNS_MEMBERS", `Building block "${comp.id}" (${comp.componentType}) cannot own members; only patterns (${Array.from(PATTERN_TYPES).join("/")}) use "owns".`, comp.id, isDraftCtx);
10633
10911
  }
10912
+ }
10913
+ const ownership = ctx.ownershipIndex();
10914
+ for (const comp of ctx.components) {
10915
+ if (isRetired(comp)) continue;
10916
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10917
+ const pattern = isPattern(comp);
10634
10918
  for (const memberId of comp.owns) {
10635
10919
  const member = ctx.componentMap.get(memberId);
10636
10920
  if (!member) {
@@ -10642,26 +10926,50 @@ var init_pattern_ownership = __esm({
10642
10926
  ctx.addIssue("error", "PATTERN_OWNS_PATTERN", `Pattern "${comp.id}" owns "${memberId}", which is itself a pattern. Patterns own only building blocks \u2014 compose patterns at the subsystem (L1) level.`, comp.id, isDraftCtx);
10643
10927
  continue;
10644
10928
  }
10645
- const firstOwner = ownedBy.get(memberId);
10929
+ const firstOwner = ownership.ownerOf(memberId);
10646
10930
  if (firstOwner && firstOwner !== comp.id) {
10647
10931
  ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${firstOwner}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
10648
10932
  }
10649
- if (!firstOwner) ownedBy.set(memberId, comp.id);
10650
10933
  }
10651
- if ((comp.componentType === "FeatureComponent" || comp.componentType === "RouterComponent") && comp.owns.some((memberId) => {
10934
+ }
10935
+ }
10936
+ };
10937
+ }
10938
+ });
10939
+
10940
+ // src/core/rules/doctrine/pattern-containment.ts
10941
+ var REPOSITORY_MEMBERS, patternContainmentRule;
10942
+ var init_pattern_containment = __esm({
10943
+ "src/core/rules/doctrine/pattern-containment.ts"() {
10944
+ "use strict";
10945
+ init_models();
10946
+ REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
10947
+ patternContainmentRule = {
10948
+ name: "pattern-containment",
10949
+ 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.",
10950
+ codes: [
10951
+ { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
10952
+ { code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
10953
+ { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" }
10954
+ ],
10955
+ check(ctx) {
10956
+ for (const comp of ctx.components) {
10957
+ if (comp.componentType !== "Repository" || isRetired(comp)) continue;
10958
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10959
+ for (const memberId of comp.owns) {
10652
10960
  const member = ctx.componentMap.get(memberId);
10653
- return member !== void 0 && isRetired(member);
10654
- })) {
10655
- continue;
10656
- }
10657
- if (comp.componentType === "Repository") {
10658
- const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
10659
- for (const memberId of comp.owns) {
10660
- const member = ctx.componentMap.get(memberId);
10661
- if (!member || isRetired(member) || allowed.has(member.componentType)) continue;
10662
- ctx.addIssue("error", "REPOSITORY_CONTAINMENT", `Repository "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Repository may own only Store, Registry, Index, Query, and (optionally) Adapter.`, comp.id, isDraftCtx);
10663
- }
10961
+ if (!member || isRetired(member) || REPOSITORY_MEMBERS.has(member.componentType)) continue;
10962
+ ctx.addIssue("error", "REPOSITORY_CONTAINMENT", `Repository "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Repository may own only Store, Registry, Index, Query, and (optionally) Adapter.`, comp.id, isDraftCtx);
10664
10963
  }
10964
+ }
10965
+ for (const comp of ctx.components) {
10966
+ if (comp.componentType !== "FeatureComponent" && comp.componentType !== "RouterComponent") continue;
10967
+ if (isRetired(comp)) continue;
10968
+ if (comp.owns.some((memberId) => {
10969
+ const member = ctx.componentMap.get(memberId);
10970
+ return member !== void 0 && isRetired(member);
10971
+ })) continue;
10972
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
10665
10973
  if (comp.componentType === "FeatureComponent") {
10666
10974
  let orchestrators = 0;
10667
10975
  let views = 0;
@@ -10692,45 +11000,83 @@ var init_pattern_ownership = __esm({
10692
11000
  }
10693
11001
  }
10694
11002
  }
11003
+ }
11004
+ };
11005
+ }
11006
+ });
11007
+
11008
+ // src/core/rules/doctrine/unowned-blocks.ts
11009
+ var unownedBlocksRule;
11010
+ var init_unowned_blocks = __esm({
11011
+ "src/core/rules/doctrine/unowned-blocks.ts"() {
11012
+ "use strict";
11013
+ unownedBlocksRule = {
11014
+ name: "unowned-blocks",
11015
+ 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.`,
11016
+ codes: [
11017
+ { 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" },
11018
+ { 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" },
11019
+ { code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
11020
+ ],
11021
+ check(ctx) {
11022
+ const ownership = ctx.ownershipIndex();
10695
11023
  for (const comp of ctx.components) {
10696
- if (comp.componentType !== "Store" || ownedBy.has(comp.id)) continue;
10697
- ctx.addIssue(
10698
- "warning",
10699
- "UNOWNED_STORE",
10700
- `Store "${comp.id}" is not owned by any pattern. The recommended shape for held state is a Repository (owns: ["${comp.id}", its Registry, its Index]) with consumers on the facade. For genuinely simple state a deliberately standalone Store is the sanctioned lightweight form \u2014 keep it visible as this Store, reachable from the workflow layer (Orchestrator/Supervisor/Actor), and acknowledge it with a lint.allow reason. Never take the third path of merging the state into a consuming component: state hidden inside a logic block disappears from the architecture permanently.`,
10701
- comp.id,
10702
- ctx.isComponentDraft(comp.id)
10703
- );
10704
- }
10705
- for (const comp of ctx.components) {
10706
- if (comp.componentType !== "Registry" || ownedBy.has(comp.id)) continue;
10707
- const hasStoreDep = comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store");
10708
- if (hasStoreDep) continue;
10709
- ctx.addIssue(
10710
- "warning",
10711
- "REGISTRY_WITHOUT_STORE",
10712
- `Registry "${comp.id}" stands alone with no Store to write to. A Registry is the write path to a Store \u2014 if this component itself holds the persisted state (a file-backed record/config store), retype it as a Store with the honest durability (read-through for no-RAM-copy file I/O) so the durability machinery can see it; otherwise wire its Store, or lint.allow with a reason.`,
10713
- comp.id,
10714
- ctx.isComponentDraft(comp.id)
10715
- );
10716
- }
10717
- for (const comp of ctx.components) {
10718
- if (comp.componentType !== "Query" || ownedBy.has(comp.id)) continue;
10719
- ctx.addIssue(
10720
- "error",
10721
- "UNOWNED_QUERY",
10722
- `Query "${comp.id}" is not owned by any pattern. A Query computes reads over its own Repository's Store \u2014 make it a member of the Repository whose Store it reads (owns: ["${comp.id}", \u2026]).`,
10723
- comp.id,
10724
- ctx.isComponentDraft(comp.id)
10725
- );
11024
+ if (ownership.ownedMembers.has(comp.id)) continue;
11025
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
11026
+ if (comp.componentType === "Store") {
11027
+ ctx.addIssue(
11028
+ "warning",
11029
+ "UNOWNED_STORE",
11030
+ `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.`,
11031
+ comp.id,
11032
+ isDraftCtx
11033
+ );
11034
+ }
11035
+ if (comp.componentType === "Registry" && !comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store")) {
11036
+ ctx.addIssue(
11037
+ "warning",
11038
+ "REGISTRY_WITHOUT_STORE",
11039
+ `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.`,
11040
+ comp.id,
11041
+ isDraftCtx
11042
+ );
11043
+ }
11044
+ if (comp.componentType === "Query") {
11045
+ ctx.addIssue(
11046
+ "error",
11047
+ "UNOWNED_QUERY",
11048
+ `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]).`,
11049
+ comp.id,
11050
+ isDraftCtx
11051
+ );
11052
+ }
10726
11053
  }
11054
+ }
11055
+ };
11056
+ }
11057
+ });
11058
+
11059
+ // src/core/rules/doctrine/member-visibility.ts
11060
+ var memberVisibilityRule;
11061
+ var init_member_visibility = __esm({
11062
+ "src/core/rules/doctrine/member-visibility.ts"() {
11063
+ "use strict";
11064
+ init_models();
11065
+ memberVisibilityRule = {
11066
+ name: "member-visibility",
11067
+ 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.",
11068
+ codes: [
11069
+ { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
11070
+ ],
11071
+ check(ctx) {
11072
+ const ownership = ctx.ownershipIndex();
10727
11073
  for (const comp of ctx.components) {
10728
11074
  if (isRetired(comp)) continue;
10729
11075
  for (const depId of comp.dependsOn) {
10730
- const owner = ownedBy.get(depId);
11076
+ const owner = ownership.ownerOf(depId);
10731
11077
  if (!owner) continue;
10732
11078
  if (owner === comp.id) continue;
10733
- if (ownedBy.get(comp.id) === owner) continue;
11079
+ if (ownership.ownerOf(comp.id) === owner) continue;
10734
11080
  ctx.addIssue("error", "VISIBILITY_VIOLATION", `Component "${comp.id}" depends on "${depId}", which is privately owned by pattern "${owner}". Depend on the facade "${owner}" instead.`, comp.id, ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(depId));
10735
11081
  }
10736
11082
  }
@@ -12128,8 +12474,7 @@ var init_source_file_linkage = __esm({
12128
12474
  { code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
12129
12475
  ],
12130
12476
  check(ctx) {
12131
- const factsByPath = /* @__PURE__ */ new Map();
12132
- for (const facts of ctx.codeModel.files) factsByPath.set(pathKey(facts.path), facts);
12477
+ const code = ctx.codeIndex();
12133
12478
  const degradedTsFiles = ctx.codeModel.files.filter(
12134
12479
  (f) => f.status === "analyzed" && (f.language === "typescript" || f.language === "javascript") && f.analysisGrade !== "exact"
12135
12480
  );
@@ -12177,7 +12522,7 @@ var init_source_file_linkage = __esm({
12177
12522
  const key = pathKey(file);
12178
12523
  if (reported.has(key)) continue;
12179
12524
  reported.add(key);
12180
- const facts = factsByPath.get(key);
12525
+ const facts = code.factsAt(key);
12181
12526
  if (!facts || facts.status === "analyzed") continue;
12182
12527
  const isImplementationFile = !!impl.sourcePath && pathKey(impl.sourcePath) === key;
12183
12528
  const users = isImplementationFile ? [] : impl.methods.filter((m) => m.sourcePath && pathKey(m.sourcePath) === key).map((m) => m.name);
@@ -12227,14 +12572,7 @@ var init_method_realization = __esm({
12227
12572
  { code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" }
12228
12573
  ],
12229
12574
  check(ctx) {
12230
- const lookups = /* @__PURE__ */ new Map();
12231
- for (const facts of ctx.codeModel.files) {
12232
- lookups.set(pathKey(facts.path), {
12233
- declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
12234
- anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
12235
- facts
12236
- });
12237
- }
12575
+ const code = ctx.codeIndex();
12238
12576
  for (const impl of ctx.implementations) {
12239
12577
  const contract = ctx.interfaceMap.get(impl.contract);
12240
12578
  if (!contract) continue;
@@ -12249,11 +12587,11 @@ var init_method_realization = __esm({
12249
12587
  if (tier === "off") continue;
12250
12588
  const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
12251
12589
  if (!file) continue;
12252
- const lookup = lookups.get(pathKey(file));
12253
- if (!lookup || lookup.facts.status !== "analyzed") continue;
12590
+ const facts = code.factsAt(file);
12591
+ if (!facts || facts.status !== "analyzed") continue;
12254
12592
  const symbol = methodImpl?.symbol ?? method.name;
12255
- const inDeclared = lookup.declared.has(symbol);
12256
- const inAnchored = inDeclared || lookup.anchored.has(symbol);
12593
+ const inDeclared = code.declarationsAt(file).has(symbol);
12594
+ const inAnchored = inDeclared || code.anchorsAt(file).has(symbol);
12257
12595
  const realized = tier === "declared" ? inDeclared : inAnchored;
12258
12596
  if (realized) continue;
12259
12597
  const label = methodImpl?.symbol ? `"${method.name}" (symbol "${symbol}")` : `"${method.name}"`;
@@ -12261,7 +12599,7 @@ var init_method_realization = __esm({
12261
12599
  ctx.addIssue(
12262
12600
  "warning",
12263
12601
  "UNREALIZED_METHOD",
12264
- `Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${lookup.facts.analysisGrade}).${weakHint}`,
12602
+ `Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${facts.analysisGrade}).${weakHint}`,
12265
12603
  impl.id,
12266
12604
  draft
12267
12605
  );
@@ -12285,10 +12623,7 @@ var init_finding_realization = __esm({
12285
12623
  { code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" }
12286
12624
  ],
12287
12625
  check(ctx) {
12288
- const lookups = /* @__PURE__ */ new Map();
12289
- for (const facts of ctx.codeModel.files) {
12290
- lookups.set(pathKey(facts.path), { findingAnchors: new Set(facts.anchoredNames), facts });
12291
- }
12626
+ const code = ctx.codeIndex();
12292
12627
  for (const impl of ctx.implementations) {
12293
12628
  const contract = ctx.interfaceMap.get(impl.contract);
12294
12629
  if (!contract) continue;
@@ -12304,14 +12639,15 @@ var init_finding_realization = __esm({
12304
12639
  if (tier === "off") continue;
12305
12640
  const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
12306
12641
  if (!file) continue;
12307
- const lookup = lookups.get(pathKey(file));
12308
- if (!lookup || lookup.facts.status !== "analyzed") continue;
12642
+ const facts = code.factsAt(file);
12643
+ if (!facts || facts.status !== "analyzed") continue;
12644
+ const findingAnchors = code.findingAnchorsAt(file);
12309
12645
  for (const finding of method.findings) {
12310
- if (lookup.findingAnchors.has(finding.code)) continue;
12646
+ if (findingAnchors.has(finding.code)) continue;
12311
12647
  ctx.addIssue(
12312
12648
  "warning",
12313
12649
  "UNREALIZED_FINDING",
12314
- `Method "${method.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
12650
+ `Method "${method.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
12315
12651
  impl.id,
12316
12652
  draft
12317
12653
  );
@@ -12355,8 +12691,7 @@ var init_call_conformance = __esm({
12355
12691
  { code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step whose target method name (or symbol) never appears among the realized function's callees in the method's source file (exact grade, set membership)" }
12356
12692
  ],
12357
12693
  check(ctx) {
12358
- const factsByPath = /* @__PURE__ */ new Map();
12359
- for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
12694
+ const code = ctx.codeIndex();
12360
12695
  for (const impl of ctx.implementations) {
12361
12696
  const contract = ctx.interfaceMap.get(impl.contract);
12362
12697
  if (!contract) continue;
@@ -12371,7 +12706,7 @@ var init_call_conformance = __esm({
12371
12706
  if (!implMethod.narrative.length) continue;
12372
12707
  const file = methodSourceFile(implMethod, impl.sourcePath);
12373
12708
  if (!file) continue;
12374
- const facts = factsByPath.get(pathKey(file));
12709
+ const facts = code.factsAt(file);
12375
12710
  if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12376
12711
  const fnSymbol = implMethod.symbol ?? implMethod.name;
12377
12712
  const callees = closedCallees(facts, fnSymbol);
@@ -12425,6 +12760,7 @@ var init_hidden_state = __esm({
12425
12760
  { code: "HIDDEN_STATE", defaultSeverity: "warning", summary: "Module-scope mutable binding in a file mapped only to stateless logic (Orchestrators) \u2014 held state hiding outside a Store, Supervisor or Actor" }
12426
12761
  ],
12427
12762
  check(ctx) {
12763
+ const code = ctx.codeIndex();
12428
12764
  const byPath = /* @__PURE__ */ new Map();
12429
12765
  for (const impl of ctx.implementations) {
12430
12766
  const contract = ctx.interfaceMap.get(impl.contract);
@@ -12446,11 +12782,11 @@ var init_hidden_state = __esm({
12446
12782
  byPath.set(key, list);
12447
12783
  }
12448
12784
  }
12449
- for (const facts of ctx.codeModel.files) {
12450
- if (facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12785
+ for (const path27 of code.exactPaths) {
12786
+ const facts = code.factsAt(path27);
12451
12787
  const bindings = facts.topLevelMutableBindings ?? [];
12452
12788
  if (bindings.length === 0) continue;
12453
- const mapped = byPath.get(pathKey(facts.path)) ?? [];
12789
+ const mapped = byPath.get(path27) ?? [];
12454
12790
  if (mapped.length === 0) continue;
12455
12791
  if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
12456
12792
  const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
@@ -12482,54 +12818,21 @@ var init_dependency_conformance = __esm({
12482
12818
  { code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
12483
12819
  ],
12484
12820
  check(ctx) {
12485
- const factsByPath = new Map(ctx.codeModel.files.map((f) => [pathKey(f.path), f]));
12486
- const nodes = /* @__PURE__ */ new Map();
12487
- const filesByComponent = /* @__PURE__ */ new Map();
12488
- const implsByComponent = /* @__PURE__ */ new Map();
12489
- for (const impl of ctx.implementations) {
12490
- const contract = ctx.interfaceMap.get(impl.contract);
12491
- if (!contract) continue;
12492
- const component = ctx.componentMap.get(contract.component);
12493
- if (!component) continue;
12494
- if (ctx.isInChainedSubproject(component.subsystem)) continue;
12495
- let mapped = false;
12496
- for (const file of implementationSourceFiles(impl)) {
12497
- const p = pathKey(file);
12498
- const facts = factsByPath.get(p);
12499
- if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
12500
- let node = nodes.get(p);
12501
- if (!node) {
12502
- node = { path: p, components: [], anchorImplId: impl.id, imports: facts.imports, reexports: facts.reexports, draft: false };
12503
- nodes.set(p, node);
12504
- }
12505
- if (!node.components.some((c) => c.id === component.id)) node.components.push(component);
12506
- node.draft = node.draft || ctx.isImplementationDraft(impl);
12507
- if (!filesByComponent.has(component.id)) filesByComponent.set(component.id, /* @__PURE__ */ new Set());
12508
- filesByComponent.get(component.id).add(p);
12509
- mapped = true;
12510
- }
12511
- if (mapped) {
12512
- if (!implsByComponent.has(component.id)) implsByComponent.set(component.id, []);
12513
- implsByComponent.get(component.id).push(impl);
12514
- }
12515
- }
12516
- const mappedPaths = new Set(nodes.keys());
12517
- const edges = /* @__PURE__ */ new Map();
12518
- const realizationEdges = /* @__PURE__ */ new Map();
12519
- for (const node of nodes.values()) {
12520
- const targets = /* @__PURE__ */ new Set();
12521
- for (const spec of node.imports) {
12522
- const resolved = resolveImport(node.path, spec, mappedPaths);
12523
- if (resolved && resolved !== node.path) targets.add(resolved);
12524
- }
12525
- edges.set(node.path, targets);
12526
- const realized = new Set(targets);
12527
- for (const spec of node.reexports) {
12528
- const resolved = resolveImport(node.path, spec, mappedPaths);
12529
- if (resolved && resolved !== node.path) realized.add(resolved);
12530
- }
12531
- realizationEdges.set(node.path, realized);
12532
- }
12821
+ const code = ctx.codeIndex();
12822
+ const realization = ctx.realizationIndex();
12823
+ const isExact = (p) => code.exactPaths.has(p);
12824
+ const mappedPaths = new Set(realization.paths.filter(isExact));
12825
+ const graph = ctx.importGraph(mappedPaths);
12826
+ const exactFiles = /* @__PURE__ */ new Map();
12827
+ const filesOf = (compId) => {
12828
+ let files = exactFiles.get(compId);
12829
+ if (!files) {
12830
+ files = realization.filesOf(compId).filter(isExact);
12831
+ exactFiles.set(compId, files);
12832
+ }
12833
+ return files;
12834
+ };
12835
+ const mappedImplsOf = (compId) => realization.implementationsOf(compId).filter((impl) => implementationSourceFiles(impl).some((f) => isExact(pathKey(f))));
12533
12836
  const dependsOrOwns = (from, toId) => from.dependsOn.includes(toId) || (from.owns ?? []).includes(toId);
12534
12837
  const ownerOf = /* @__PURE__ */ new Map();
12535
12838
  for (const c of ctx.components) {
@@ -12540,9 +12843,9 @@ var init_dependency_conformance = __esm({
12540
12843
  if (!published || published.size === 0) return false;
12541
12844
  return from.dependsOn.some((d) => published.has(d));
12542
12845
  };
12543
- const edgeJustified = (fromNode, toNode) => {
12544
- for (const cf of fromNode.components) {
12545
- for (const cg of toNode.components) {
12846
+ const edgeJustified = (from, to) => {
12847
+ for (const cf of from) {
12848
+ for (const cg of to) {
12546
12849
  if (cf.id === cg.id) return true;
12547
12850
  if (dependsOrOwns(cf, cg.id)) return true;
12548
12851
  if (cf.subsystem === cg.subsystem && dependsOrOwns(cg, cf.id) && (cg.componentType === "Portal" || cg.componentType === "Observer")) return true;
@@ -12556,60 +12859,35 @@ var init_dependency_conformance = __esm({
12556
12859
  }
12557
12860
  return false;
12558
12861
  };
12559
- for (const [fromPath, targets] of edges) {
12560
- const fromNode = nodes.get(fromPath);
12561
- for (const toPath of targets) {
12562
- const toNode = nodes.get(toPath);
12563
- if (edgeJustified(fromNode, toNode)) continue;
12862
+ const draftAt = (path27) => realization.implementationsAt(path27).some((impl) => ctx.isImplementationDraft(impl));
12863
+ for (const fromPath of mappedPaths) {
12864
+ const fromComponents = realization.componentsAt(fromPath);
12865
+ for (const toPath of graph.importsOf(fromPath)) {
12866
+ const toComponents = realization.componentsAt(toPath);
12867
+ if (edgeJustified(fromComponents, toComponents)) continue;
12564
12868
  ctx.addIssue(
12565
12869
  "warning",
12566
12870
  "UNDECLARED_DEPENDENCY",
12567
- `"${fromPath}" (realizing ${fromNode.components.map((c) => c.id).join(", ")}) imports "${toPath}" (realizing ${toNode.components.map((c) => c.id).join(", ")}) but no declared dependsOn/owns edge justifies it \u2014 declare the collaboration on the component that actually uses it, or route the cross-subsystem hop through the target's published surface.`,
12568
- fromNode.anchorImplId,
12569
- fromNode.draft || toNode.draft
12871
+ `"${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.`,
12872
+ realization.implementationsAt(fromPath)[0]?.id,
12873
+ draftAt(fromPath) || draftAt(toPath)
12570
12874
  );
12571
12875
  }
12572
12876
  }
12573
- const hasImportBetween = (fromFiles, toFiles, allowReverse) => {
12574
- for (const f of fromFiles) {
12575
- const targets = realizationEdges.get(f);
12576
- if (!targets) continue;
12577
- for (const t of toFiles) if (targets.has(t)) return true;
12578
- }
12579
- if (allowReverse) {
12580
- for (const t of toFiles) {
12581
- const reverse = realizationEdges.get(t);
12582
- if (!reverse) continue;
12583
- for (const f of fromFiles) if (reverse.has(f)) return true;
12584
- }
12585
- }
12586
- return false;
12587
- };
12588
12877
  for (const component of ctx.components) {
12589
- const fromFiles = filesByComponent.get(component.id);
12590
- if (!fromFiles) continue;
12878
+ const fromFiles = filesOf(component.id);
12879
+ if (fromFiles.length === 0) continue;
12591
12880
  const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
12592
12881
  const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
12593
12882
  for (const targetId of declaredTargets) {
12594
12883
  const target = ctx.componentMap.get(targetId);
12595
12884
  if (!target) continue;
12596
- if (component.subsystem !== target.subsystem) {
12597
- const subsystemFiles = /* @__PURE__ */ new Set();
12598
- for (const node of nodes.values()) {
12599
- if (node.components.some((c) => c.subsystem === target.subsystem)) subsystemFiles.add(node.path);
12600
- }
12601
- if (subsystemFiles.size === 0) continue;
12602
- for (const f of fromFiles) subsystemFiles.delete(f);
12603
- if (subsystemFiles.size === 0) continue;
12604
- if (hasImportBetween(fromFiles, subsystemFiles, isMountingDeclarer)) continue;
12605
- } else {
12606
- const toFiles = filesByComponent.get(targetId);
12607
- if (!toFiles) continue;
12608
- if ([...fromFiles].some((f) => toFiles.has(f))) continue;
12609
- if (hasImportBetween(fromFiles, toFiles, isMountingDeclarer)) continue;
12610
- }
12611
- const impls = implsByComponent.get(component.id) ?? [];
12612
- const anchor = impls[0];
12885
+ const crossSubsystem = component.subsystem !== target.subsystem;
12886
+ const toFiles = crossSubsystem ? realization.filesIn(target.subsystem).filter((p) => isExact(p) && !fromFiles.includes(p)) : filesOf(targetId);
12887
+ if (toFiles.length === 0) continue;
12888
+ if (fromFiles.some((f) => toFiles.includes(f))) continue;
12889
+ if (graph.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
12890
+ const impls = mappedImplsOf(component.id);
12613
12891
  const declaredDependsOn = component.dependsOn.includes(targetId);
12614
12892
  const declaredOwns = (component.owns ?? []).includes(targetId);
12615
12893
  const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
@@ -12617,8 +12895,8 @@ var init_dependency_conformance = __esm({
12617
12895
  ctx.addIssue(
12618
12896
  "warning",
12619
12897
  "UNREALIZED_DEPENDENCY",
12620
- `Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${[...fromFiles].join(", ")} \u219B ${target.subsystem !== component.subsystem ? `subsystem ${target.subsystem}` : [...filesByComponent.get(targetId) ?? []].join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
12621
- anchor?.id ?? component.id,
12898
+ `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.`,
12899
+ impls[0]?.id ?? component.id,
12622
12900
  draft
12623
12901
  );
12624
12902
  }
@@ -12644,50 +12922,24 @@ var init_integration_conformance = __esm({
12644
12922
  { code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
12645
12923
  ],
12646
12924
  check(ctx) {
12647
- const factsByPath = new Map(ctx.codeModel.files.map((f) => [pathKey(f.path), f]));
12648
- const allPaths = new Set(factsByPath.keys());
12649
- const filesByComponent = /* @__PURE__ */ new Map();
12650
- const filesBySubsystem = /* @__PURE__ */ new Map();
12651
- const implsByComponent = /* @__PURE__ */ new Map();
12652
- const adopted = /* @__PURE__ */ new Set();
12925
+ const code = ctx.codeIndex();
12926
+ const realization = ctx.realizationIndex();
12927
+ const graph = ctx.importGraph();
12653
12928
  const exists = (p) => {
12654
- const status = factsByPath.get(p)?.status;
12929
+ const status = code.factsAt(p)?.status;
12655
12930
  return status === "analyzed" || status === "unreadable";
12656
12931
  };
12932
+ const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
12933
+ const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
12934
+ const adopted = /* @__PURE__ */ new Set();
12657
12935
  for (const impl of ctx.implementations) {
12936
+ if (!impl.simPath) continue;
12658
12937
  const contract = ctx.interfaceMap.get(impl.contract);
12659
12938
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
12660
12939
  if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
12661
- if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
12662
- implsByComponent.get(comp.id).push(impl);
12663
- for (const file of implementationSourceFiles(impl)) {
12664
- const p = pathKey(file);
12665
- if (!exists(p)) continue;
12666
- if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
12667
- filesByComponent.get(comp.id).add(p);
12668
- if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
12669
- filesBySubsystem.get(comp.subsystem).add(p);
12670
- }
12671
- if (impl.simPath) adopted.add(comp.subsystem);
12672
- }
12673
- const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
12674
- const reachFrom = (start) => {
12675
- const seen = /* @__PURE__ */ new Set([start]);
12676
- const stack = [start];
12677
- while (stack.length) {
12678
- const from = stack.pop();
12679
- const facts = factsByPath.get(from);
12680
- if (!facts || facts.status !== "analyzed") continue;
12681
- for (const spec of [...facts.imports, ...facts.reexports]) {
12682
- const to = resolveImport(from, spec, allPaths);
12683
- if (to && !seen.has(to)) {
12684
- seen.add(to);
12685
- stack.push(to);
12686
- }
12687
- }
12688
- }
12689
- return seen;
12690
- };
12940
+ adopted.add(comp.subsystem);
12941
+ }
12942
+ const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
12691
12943
  for (const impl of ctx.implementations) {
12692
12944
  const contract = ctx.interfaceMap.get(impl.contract);
12693
12945
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
@@ -12707,7 +12959,7 @@ var init_integration_conformance = __esm({
12707
12959
  continue;
12708
12960
  }
12709
12961
  const simPath = pathKey(impl.simPath);
12710
- const facts = factsByPath.get(simPath);
12962
+ const facts = code.factsAt(simPath);
12711
12963
  const isDraftCtx = ctx.isImplementationDraft(impl);
12712
12964
  if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
12713
12965
  const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
@@ -12721,19 +12973,19 @@ var init_integration_conformance = __esm({
12721
12973
  continue;
12722
12974
  }
12723
12975
  if (facts.analysisGrade !== "exact") continue;
12724
- const reach = reachFrom(simPath);
12725
- const ownFiles = filesByComponent.get(comp.id) ?? /* @__PURE__ */ new Set();
12976
+ const reach = graph.reachFrom(simPath);
12977
+ const ownFiles = modulesOf(comp.id);
12726
12978
  const missing = [];
12727
- if (ownFiles.size > 0 && ![...ownFiles].some((f) => reach.has(f))) {
12728
- missing.push(`the component's own module (${[...ownFiles].join(" | ")})`);
12979
+ if (ownFiles.length > 0 && !ownFiles.some((f) => reach.has(f))) {
12980
+ missing.push(`the component's own module (${ownFiles.join(" | ")})`);
12729
12981
  }
12730
12982
  for (const dep of deps) {
12731
12983
  if (isTechBoundary(dep)) continue;
12732
12984
  const depComp = ctx.componentMap.get(dep);
12733
- const depFiles = depComp.subsystem !== comp.subsystem ? filesBySubsystem.get(depComp.subsystem) : filesByComponent.get(dep);
12734
- if (!depFiles || depFiles.size === 0) continue;
12735
- if (![...depFiles].some((f) => reach.has(f))) {
12736
- const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${[...depFiles].join(" | ")})`;
12985
+ const depFiles = depComp.subsystem !== comp.subsystem ? modulesIn(depComp.subsystem) : modulesOf(dep);
12986
+ if (depFiles.length === 0) continue;
12987
+ if (!depFiles.some((f) => reach.has(f))) {
12988
+ const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${depFiles.join(" | ")})`;
12737
12989
  missing.push(label);
12738
12990
  }
12739
12991
  }
@@ -13908,8 +14160,15 @@ var init_repository = __esm({
13908
14160
  init_retired_stereotypes();
13909
14161
  init_portal_endpoints();
13910
14162
  init_portal_call_auth();
13911
- init_stereotype_dependencies();
13912
- init_pattern_ownership();
14163
+ init_subsystem_boundary_dependencies();
14164
+ init_logic_dependency_class();
14165
+ init_data_block_dependencies();
14166
+ init_entrypoint_dependencies();
14167
+ init_portal_write_shortcut();
14168
+ init_pattern_membership();
14169
+ init_pattern_containment();
14170
+ init_unowned_blocks();
14171
+ init_member_visibility();
13913
14172
  init_facade_forwarding();
13914
14173
  init_profile_registration();
13915
14174
  init_profile_stereotype_fencing();
@@ -13986,8 +14245,24 @@ var init_repository = __esm({
13986
14245
  // Cross-call auth: a narrative call into an authed Portal must name its
13987
14246
  // credential source (rides with the portal family).
13988
14247
  portalCallAuthRule,
13989
- stereotypeDepsRule,
13990
- patternsRule,
14248
+ // Dependencies in five questions, one owner each: where an edge is
14249
+ // allowed to LAND (the boundary, and what an unresolved id means), then
14250
+ // the intra-subsystem matrix by the layer that answers for it — an
14251
+ // Orchestrator's declared class, the data blocks, the entry points and
14252
+ // the process layer — and finally the Portal read-face guard, the one
14253
+ // that reads narratives and dispatch tables rather than dependsOn.
14254
+ subsystemBoundaryDepsRule,
14255
+ logicDependencyClassRule,
14256
+ dataBlockDepsRule,
14257
+ entrypointDepsRule,
14258
+ portalWriteShortcutRule,
14259
+ // Patterns in four questions: who may own and what a claim must name,
14260
+ // what each pattern must contain, which data blocks are left standing
14261
+ // alone, and who may see a private member.
14262
+ patternMembershipRule,
14263
+ patternContainmentRule,
14264
+ unownedBlocksRule,
14265
+ memberVisibilityRule,
13991
14266
  // Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
13992
14267
  facadeForwardingRule,
13993
14268
  // Profiles in three questions, three owners: is the name real (the
@@ -14758,11 +15033,15 @@ function buildRuleContext(opts) {
14758
15033
  }
14759
15034
  return { kind: "unresolved" };
14760
15035
  };
15036
+ const contractMethods = /* @__PURE__ */ new Map();
14761
15037
  const interfaceMethodsOf = (compId) => {
15038
+ const cached = contractMethods.get(compId);
15039
+ if (cached) return cached;
14762
15040
  const out = [];
14763
15041
  for (const intf of interfacesByComponent.get(compId) ?? []) {
14764
15042
  out.push(...intf.methods);
14765
15043
  }
15044
+ contractMethods.set(compId, out);
14766
15045
  return out;
14767
15046
  };
14768
15047
  const profileDefFor = (subsystemId) => {
@@ -14858,7 +15137,26 @@ function buildRuleContext(opts) {
14858
15137
  ...surfaceResolved ? { surfaceResolved: true } : {}
14859
15138
  });
14860
15139
  };
14861
- return {
15140
+ let codeIndexMemo;
15141
+ const codeIndex = () => codeIndexMemo ??= buildCodeIndex(ctx.codeModel);
15142
+ let realizationMemo;
15143
+ const realizationIndex = () => realizationMemo ??= buildRealizationIndex(ctx);
15144
+ const importGraphs = /* @__PURE__ */ new Map();
15145
+ const importGraph = (paths) => {
15146
+ const universe = paths ?? codeIndex().paths;
15147
+ const key = `${universe.size}\0${[...universe].join("\0")}`;
15148
+ let graph = importGraphs.get(key);
15149
+ if (!graph) {
15150
+ graph = buildImportGraph(codeIndex(), universe);
15151
+ importGraphs.set(key, graph);
15152
+ }
15153
+ return graph;
15154
+ };
15155
+ let ownershipMemo;
15156
+ const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
15157
+ let dependencyEdgesMemo;
15158
+ const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
15159
+ const ctx = {
14862
15160
  system,
14863
15161
  subsystems,
14864
15162
  components,
@@ -14887,6 +15185,11 @@ function buildRuleContext(opts) {
14887
15185
  isCollapsedCrossTreeRef,
14888
15186
  resolveSurfaceRef,
14889
15187
  interfaceMethodsOf,
15188
+ codeIndex,
15189
+ realizationIndex,
15190
+ importGraph,
15191
+ ownershipIndex,
15192
+ dependencyEdges,
14890
15193
  complexityConfigFor,
14891
15194
  documentationConfigFor,
14892
15195
  namingConfigFor,
@@ -14901,6 +15204,7 @@ function buildRuleContext(opts) {
14901
15204
  knownIssueCodes: opts.knownIssueCodes,
14902
15205
  addIssue
14903
15206
  };
15207
+ return ctx;
14904
15208
  }
14905
15209
  var DEPTH_RANK, DEPTH_GATED_CODES, COMPLETENESS_RULES;
14906
15210
  var init_rules = __esm({
@@ -14909,6 +15213,7 @@ var init_rules = __esm({
14909
15213
  init_models();
14910
15214
  init_extensions();
14911
15215
  init_types();
15216
+ init_read_model();
14912
15217
  init_repository();
14913
15218
  init_lint_allows();
14914
15219
  init_source_analysis();