@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/cli/index.js +760 -455
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +760 -455
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.26";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -8748,6 +8748,209 @@ var init_types = __esm({
|
|
|
8748
8748
|
}
|
|
8749
8749
|
});
|
|
8750
8750
|
|
|
8751
|
+
// src/core/rules/read-model.ts
|
|
8752
|
+
function appendUnique(map, key, value, same) {
|
|
8753
|
+
const list2 = map.get(key);
|
|
8754
|
+
if (!list2) {
|
|
8755
|
+
map.set(key, [value]);
|
|
8756
|
+
return;
|
|
8757
|
+
}
|
|
8758
|
+
if (same ? list2.some((v) => same(v, value)) : list2.includes(value)) return;
|
|
8759
|
+
list2.push(value);
|
|
8760
|
+
}
|
|
8761
|
+
function buildCodeIndex(model) {
|
|
8762
|
+
const facts = /* @__PURE__ */ new Map();
|
|
8763
|
+
const paths = /* @__PURE__ */ new Set();
|
|
8764
|
+
const exactPaths = /* @__PURE__ */ new Set();
|
|
8765
|
+
for (const f of model.files) {
|
|
8766
|
+
const key = pathKey(f.path);
|
|
8767
|
+
facts.set(key, f);
|
|
8768
|
+
paths.add(key);
|
|
8769
|
+
if (f.status === "analyzed" && f.analysisGrade === "exact") exactPaths.add(key);
|
|
8770
|
+
}
|
|
8771
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
8772
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
8773
|
+
const findingAnchors = /* @__PURE__ */ new Map();
|
|
8774
|
+
const namesAt = (cache2, path70, make) => {
|
|
8775
|
+
const cached = cache2.get(path70);
|
|
8776
|
+
if (cached) return cached;
|
|
8777
|
+
const f = facts.get(path70);
|
|
8778
|
+
if (!f) return NO_NAMES;
|
|
8779
|
+
const built = make(f);
|
|
8780
|
+
cache2.set(path70, built);
|
|
8781
|
+
return built;
|
|
8782
|
+
};
|
|
8783
|
+
return {
|
|
8784
|
+
paths,
|
|
8785
|
+
exactPaths,
|
|
8786
|
+
factsAt: (path70) => facts.get(pathKey(path70)),
|
|
8787
|
+
declarationsAt: (path70) => namesAt(declarations, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames])),
|
|
8788
|
+
anchorsAt: (path70) => namesAt(anchors, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
8789
|
+
findingAnchorsAt: (path70) => namesAt(findingAnchors, pathKey(path70), (f) => new Set(f.anchoredNames))
|
|
8790
|
+
};
|
|
8791
|
+
}
|
|
8792
|
+
function buildRealizationIndex(ctx) {
|
|
8793
|
+
const implsByComponent = /* @__PURE__ */ new Map();
|
|
8794
|
+
const implsByPath = /* @__PURE__ */ new Map();
|
|
8795
|
+
const filesByComponent = /* @__PURE__ */ new Map();
|
|
8796
|
+
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
8797
|
+
const componentsByPath = /* @__PURE__ */ new Map();
|
|
8798
|
+
const paths = [];
|
|
8799
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
8800
|
+
for (const impl of ctx.implementations) {
|
|
8801
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
8802
|
+
if (!contract) continue;
|
|
8803
|
+
const component = ctx.componentMap.get(contract.component);
|
|
8804
|
+
if (!component) continue;
|
|
8805
|
+
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
8806
|
+
appendUnique(implsByComponent, component.id, impl);
|
|
8807
|
+
for (const file of implementationSourceFiles(impl)) {
|
|
8808
|
+
const p = pathKey(file);
|
|
8809
|
+
if (!seenPaths.has(p)) {
|
|
8810
|
+
seenPaths.add(p);
|
|
8811
|
+
paths.push(p);
|
|
8812
|
+
}
|
|
8813
|
+
appendUnique(implsByPath, p, impl);
|
|
8814
|
+
appendUnique(filesByComponent, component.id, p);
|
|
8815
|
+
appendUnique(filesBySubsystem, component.subsystem, p);
|
|
8816
|
+
appendUnique(componentsByPath, p, component, (a, b) => a.id === b.id);
|
|
8817
|
+
}
|
|
8818
|
+
}
|
|
8819
|
+
const NONE_IMPL = [];
|
|
8820
|
+
const NONE_COMP = [];
|
|
8821
|
+
const NONE_PATH = [];
|
|
8822
|
+
return {
|
|
8823
|
+
paths,
|
|
8824
|
+
implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
|
|
8825
|
+
implementationsAt: (path70) => implsByPath.get(pathKey(path70)) ?? NONE_IMPL,
|
|
8826
|
+
filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
|
|
8827
|
+
filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
|
|
8828
|
+
componentsAt: (path70) => componentsByPath.get(pathKey(path70)) ?? NONE_COMP
|
|
8829
|
+
};
|
|
8830
|
+
}
|
|
8831
|
+
function buildImportGraph(index, universe) {
|
|
8832
|
+
const imports = /* @__PURE__ */ new Map();
|
|
8833
|
+
const traces = /* @__PURE__ */ new Map();
|
|
8834
|
+
const resolveAll = (from, specifiers, into) => {
|
|
8835
|
+
for (const specifier of specifiers) {
|
|
8836
|
+
const to = resolveImport(from, specifier, universe);
|
|
8837
|
+
if (to && to !== from) into.add(to);
|
|
8838
|
+
}
|
|
8839
|
+
};
|
|
8840
|
+
const importsOf = (path70) => {
|
|
8841
|
+
const cached = imports.get(path70);
|
|
8842
|
+
if (cached) return cached;
|
|
8843
|
+
const facts = index.factsAt(path70);
|
|
8844
|
+
const out = /* @__PURE__ */ new Set();
|
|
8845
|
+
if (facts?.status === "analyzed") resolveAll(path70, facts.imports, out);
|
|
8846
|
+
imports.set(path70, out);
|
|
8847
|
+
return out;
|
|
8848
|
+
};
|
|
8849
|
+
const tracesOf = (path70) => {
|
|
8850
|
+
const cached = traces.get(path70);
|
|
8851
|
+
if (cached) return cached;
|
|
8852
|
+
const facts = index.factsAt(path70);
|
|
8853
|
+
const out = new Set(importsOf(path70));
|
|
8854
|
+
if (facts?.status === "analyzed") resolveAll(path70, facts.reexports, out);
|
|
8855
|
+
traces.set(path70, out);
|
|
8856
|
+
return out;
|
|
8857
|
+
};
|
|
8858
|
+
return {
|
|
8859
|
+
paths: [...universe],
|
|
8860
|
+
importsOf,
|
|
8861
|
+
tracesOf,
|
|
8862
|
+
connects: (from, to, allowReverse) => {
|
|
8863
|
+
for (const f of from) {
|
|
8864
|
+
const forward = tracesOf(f);
|
|
8865
|
+
for (const t of to) if (forward.has(t)) return true;
|
|
8866
|
+
}
|
|
8867
|
+
if (allowReverse) {
|
|
8868
|
+
for (const t of to) {
|
|
8869
|
+
const reverse = tracesOf(t);
|
|
8870
|
+
for (const f of from) if (reverse.has(f)) return true;
|
|
8871
|
+
}
|
|
8872
|
+
}
|
|
8873
|
+
return false;
|
|
8874
|
+
},
|
|
8875
|
+
reachFrom: (start2) => {
|
|
8876
|
+
const seen = /* @__PURE__ */ new Set([start2]);
|
|
8877
|
+
const stack = [start2];
|
|
8878
|
+
while (stack.length) {
|
|
8879
|
+
const from = stack.pop();
|
|
8880
|
+
for (const to of tracesOf(from)) {
|
|
8881
|
+
if (seen.has(to)) continue;
|
|
8882
|
+
seen.add(to);
|
|
8883
|
+
stack.push(to);
|
|
8884
|
+
}
|
|
8885
|
+
}
|
|
8886
|
+
return seen;
|
|
8887
|
+
}
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
function buildOwnershipIndex(ctx) {
|
|
8891
|
+
const ownedBy = /* @__PURE__ */ new Map();
|
|
8892
|
+
for (const comp of ctx.components) {
|
|
8893
|
+
if (isRetired(comp) || !isPattern(comp)) continue;
|
|
8894
|
+
for (const memberId of comp.owns) {
|
|
8895
|
+
const member = ctx.componentMap.get(memberId);
|
|
8896
|
+
if (!member || isPattern(member)) continue;
|
|
8897
|
+
if (!ownedBy.has(memberId)) ownedBy.set(memberId, comp.id);
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
return {
|
|
8901
|
+
ownedMembers: new Set(ownedBy.keys()),
|
|
8902
|
+
ownerOf: (memberId) => ownedBy.get(memberId)
|
|
8903
|
+
};
|
|
8904
|
+
}
|
|
8905
|
+
function buildDependencyEdges(ctx) {
|
|
8906
|
+
const all = [];
|
|
8907
|
+
const matrix = [];
|
|
8908
|
+
for (const from of ctx.components) {
|
|
8909
|
+
const fromDraft = ctx.isComponentDraft(from.id);
|
|
8910
|
+
const profile = ctx.ext.profiles[ctx.getComponentProfile(from.id)];
|
|
8911
|
+
for (const ref of from.dependsOn) {
|
|
8912
|
+
const to = ctx.componentMap.get(ref);
|
|
8913
|
+
if (!to) {
|
|
8914
|
+
all.push(offTreeEdge(ctx, from, ref, fromDraft));
|
|
8915
|
+
continue;
|
|
8916
|
+
}
|
|
8917
|
+
const edge = {
|
|
8918
|
+
from,
|
|
8919
|
+
ref,
|
|
8920
|
+
to,
|
|
8921
|
+
reach: to.subsystem === from.subsystem ? "internal" : "cross-subsystem",
|
|
8922
|
+
retired: isRetired(from) || isRetired(to),
|
|
8923
|
+
draftContext: fromDraft || ctx.isComponentDraft(to.id),
|
|
8924
|
+
licensed: profile?.allowedEdges?.some(
|
|
8925
|
+
(e) => e.from.includes(from.componentType) && e.to.includes(to.componentType)
|
|
8926
|
+
) ?? false
|
|
8927
|
+
};
|
|
8928
|
+
all.push(edge);
|
|
8929
|
+
if (edge.reach === "internal" && !edge.retired && !edge.licensed) matrix.push(edge);
|
|
8930
|
+
}
|
|
8931
|
+
}
|
|
8932
|
+
return { all, matrix };
|
|
8933
|
+
}
|
|
8934
|
+
function offTreeEdge(ctx, from, ref, fromDraft) {
|
|
8935
|
+
const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
|
|
8936
|
+
const external = ctx.isExternalNamespaceRef(ref);
|
|
8937
|
+
if (external || ctx.isCollapsedCrossTreeRef(ref, from.subsystem)) {
|
|
8938
|
+
const surface = ctx.resolveSurfaceRef(ref, from.subsystem);
|
|
8939
|
+
if (surface.kind === "ambiguous") return { ...base, reach: "ambiguous", surface };
|
|
8940
|
+
if (surface.kind === "resolved") return { ...base, reach: "surface", surface };
|
|
8941
|
+
if (external) return { ...base, reach: "unpinned" };
|
|
8942
|
+
}
|
|
8943
|
+
return { ...base, reach: "missing" };
|
|
8944
|
+
}
|
|
8945
|
+
var NO_NAMES;
|
|
8946
|
+
var init_read_model = __esm({
|
|
8947
|
+
"src/core/rules/read-model.ts"() {
|
|
8948
|
+
"use strict";
|
|
8949
|
+
init_models();
|
|
8950
|
+
NO_NAMES = /* @__PURE__ */ new Set();
|
|
8951
|
+
}
|
|
8952
|
+
});
|
|
8953
|
+
|
|
8751
8954
|
// src/core/rules/integrity/hierarchy-integrity.ts
|
|
8752
8955
|
var hierarchyRule;
|
|
8753
8956
|
var init_hierarchy_integrity = __esm({
|
|
@@ -9956,8 +10159,7 @@ var init_narrative_detail = __esm({
|
|
|
9956
10159
|
{ code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
|
|
9957
10160
|
],
|
|
9958
10161
|
check(ctx) {
|
|
9959
|
-
const
|
|
9960
|
-
for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
|
|
10162
|
+
const code = ctx.codeIndex();
|
|
9961
10163
|
for (const impl of ctx.implementations) {
|
|
9962
10164
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
9963
10165
|
if (!contract) continue;
|
|
@@ -9983,7 +10185,7 @@ var init_narrative_detail = __esm({
|
|
|
9983
10185
|
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
9984
10186
|
const file = methodSourceFile(implMethod, impl.sourcePath);
|
|
9985
10187
|
if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
|
|
9986
|
-
const facts =
|
|
10188
|
+
const facts = code.factsAt(file);
|
|
9987
10189
|
const symbol = implMethod.symbol ?? implMethod.name;
|
|
9988
10190
|
const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
|
|
9989
10191
|
const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
|
|
@@ -10129,7 +10331,7 @@ var init_logic_declaration = __esm({
|
|
|
10129
10331
|
logicDeclarationRule = {
|
|
10130
10332
|
name: "logic-declaration",
|
|
10131
10333
|
scope: "spec",
|
|
10132
|
-
description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by
|
|
10334
|
+
description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by logic-dependency-class. Intrinsic to one component: no tree required.",
|
|
10133
10335
|
codes: [
|
|
10134
10336
|
{ code: "DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR", defaultSeverity: "error", summary: "dependencyClass declared on a component that is not an Orchestrator" }
|
|
10135
10337
|
],
|
|
@@ -10355,257 +10557,341 @@ var init_portal_call_auth = __esm({
|
|
|
10355
10557
|
}
|
|
10356
10558
|
});
|
|
10357
10559
|
|
|
10358
|
-
// src/core/rules/doctrine/
|
|
10359
|
-
var
|
|
10360
|
-
var
|
|
10361
|
-
"src/core/rules/doctrine/
|
|
10560
|
+
// src/core/rules/doctrine/subsystem-boundary-dependencies.ts
|
|
10561
|
+
var subsystemBoundaryDepsRule;
|
|
10562
|
+
var init_subsystem_boundary_dependencies = __esm({
|
|
10563
|
+
"src/core/rules/doctrine/subsystem-boundary-dependencies.ts"() {
|
|
10362
10564
|
"use strict";
|
|
10363
10565
|
init_models();
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
|
|
10368
|
-
}
|
|
10369
|
-
return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
|
|
10370
|
-
};
|
|
10371
|
-
isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
|
|
10372
|
-
isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
|
|
10373
|
-
stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
|
|
10374
|
-
stereotypeDepsRule = {
|
|
10375
|
-
name: "stereotype-dependencies",
|
|
10376
|
-
description: "Enforces the component-stereotype interaction matrix and the cross-subsystem shape. The matrix: a Portal reaches the data layer only through Repository/Index READ faces, and writes route through Orchestrators (PORTAL_WRITE_SHORTCUT); Stores are depended upon; Registries write their Store; Adapters are sinks; Views stay passive; an Orchestrator's dependencyClass bounds what it may depend on; a Supervisor reaches data only through workflows; a live Actor is reached through a Supervisor that supervises it; a Query reads its Store; any block may use pure logic. Across subsystems: client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A governing pack profile may license intra-subsystem edges the matrix refuses via allowedEdges (its platform idiom, with the stated reason); boundary rules stay unrelaxable.",
|
|
10566
|
+
subsystemBoundaryDepsRule = {
|
|
10567
|
+
name: "subsystem-boundary-dependencies",
|
|
10568
|
+
description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A reference that names nothing in this tree is resolved against the stored surface snapshots: a hit is a DECLARED remote portal and the same Adapter requirement applies to it, snapshots of several providers that disagree make the reference ambiguous, and a reference authored to leave this root that no snapshot covers warns instead of erroring. Boundary rules are never relaxed by a pack profile.",
|
|
10377
10569
|
codes: [
|
|
10378
10570
|
{ code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
|
|
10379
10571
|
{ code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
|
|
10380
10572
|
{ code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
|
|
10381
10573
|
{ code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
|
|
10382
10574
|
{ code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
|
|
10383
|
-
{ code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
|
|
10384
|
-
{ code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
|
|
10385
|
-
{ code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
|
|
10386
|
-
{ code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
|
|
10387
|
-
{ code: "ARCHITECTURE_VIOLATION_REGISTRY_DEP", defaultSeverity: "warning", summary: "Registry depending on anything but its Store, a backend Adapter or pure logic (warning while new; aligned to the standard \xA77 validate\u2192write path)" },
|
|
10388
|
-
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
|
|
10389
|
-
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
|
|
10390
|
-
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
|
|
10391
|
-
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" },
|
|
10392
|
-
{ code: "DEPENDENCY_CLASS_VIOLATION", defaultSeverity: "error", summary: "Orchestrator depending on a component its dependencyClass does not allow: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters" },
|
|
10393
|
-
{ code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on anything but Actors, Orchestrators, Adapters or other Supervisors \u2014 it reaches data only through workflows" },
|
|
10394
|
-
{ code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise, without also depending on a Supervisor that supervises it \u2014 a live Actor is reached by id through its Supervisor" },
|
|
10395
|
-
{ code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
|
|
10575
|
+
{ code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
|
|
10396
10576
|
],
|
|
10397
10577
|
check(ctx) {
|
|
10398
|
-
for (const
|
|
10399
|
-
|
|
10400
|
-
const
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
if (!depComp) {
|
|
10404
|
-
const external = ctx.isExternalNamespaceRef(depId);
|
|
10405
|
-
if (external || ctx.isCollapsedCrossTreeRef(depId, comp.subsystem)) {
|
|
10406
|
-
const resolved = ctx.resolveSurfaceRef(depId, comp.subsystem);
|
|
10407
|
-
if (resolved.kind === "ambiguous") {
|
|
10408
|
-
ctx.addIssue("error", "SURFACE_REF_AMBIGUOUS", ambiguityMessage(resolved, `Component "${comp.id}" depends on`, depId), comp.id, isDraftCtx);
|
|
10409
|
-
continue;
|
|
10410
|
-
}
|
|
10411
|
-
if (resolved.kind === "resolved") {
|
|
10412
|
-
if (comp.componentType !== "Adapter") {
|
|
10413
|
-
ctx.addIssue(
|
|
10414
|
-
"error",
|
|
10415
|
-
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10416
|
-
`Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${depId}", a surface of project "${resolved.snapshot.projectName}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
|
|
10417
|
-
comp.id,
|
|
10418
|
-
isDraftCtx,
|
|
10419
|
-
true
|
|
10420
|
-
);
|
|
10421
|
-
}
|
|
10422
|
-
continue;
|
|
10423
|
-
}
|
|
10424
|
-
if (external) {
|
|
10425
|
-
ctx.addIssue(
|
|
10426
|
-
"warning",
|
|
10427
|
-
"CROSS_TREE_REF_UNRESOLVED",
|
|
10428
|
-
`Component "${comp.id}" depends on cross-tree component "${depId}", and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
10429
|
-
comp.id,
|
|
10430
|
-
isDraftCtx
|
|
10431
|
-
);
|
|
10432
|
-
continue;
|
|
10433
|
-
}
|
|
10434
|
-
}
|
|
10578
|
+
for (const edge of ctx.dependencyEdges().all) {
|
|
10579
|
+
if (edge.retired) continue;
|
|
10580
|
+
const comp = edge.from;
|
|
10581
|
+
switch (edge.reach) {
|
|
10582
|
+
case "missing":
|
|
10435
10583
|
ctx.addIssue(
|
|
10436
10584
|
"error",
|
|
10437
10585
|
"INVALID_DEPENDENCY_REFERENCE",
|
|
10438
|
-
`Component "${comp.id}" lists dependency "${
|
|
10586
|
+
`Component "${comp.id}" lists dependency "${edge.ref}" which does not exist.`,
|
|
10439
10587
|
comp.id,
|
|
10440
|
-
|
|
10588
|
+
edge.draftContext
|
|
10441
10589
|
);
|
|
10442
10590
|
continue;
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
"error",
|
|
10452
|
-
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10453
|
-
`Boundary violation: ${comp.componentType} "${comp.id}" (subsystem "${comp.subsystem}") depends directly on "${depComp.id}" in subsystem "${depComp.subsystem}". Only a local client Adapter may cross a subsystem boundary \u2014 route this hop through an Adapter that calls "${depComp.subsystem}"'s public interface, or declare a trustedLink on "${comp.subsystem}" (with the reason) to license a direct in-process edge to that peer's published Portal.`,
|
|
10454
|
-
comp.id,
|
|
10455
|
-
crossDraft
|
|
10456
|
-
);
|
|
10457
|
-
}
|
|
10458
|
-
const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
|
|
10459
|
-
if (!targetIsPublic) {
|
|
10460
|
-
ctx.addIssue(
|
|
10461
|
-
"error",
|
|
10462
|
-
"CROSS_SUBSYSTEM_PRIVATE_ACCESS",
|
|
10463
|
-
`Boundary violation: "${comp.id}" depends on "${depComp.id}", which is not part of subsystem "${depComp.subsystem}"'s published public surface. Depend on one of its publicInterfaces components instead.`,
|
|
10464
|
-
comp.id,
|
|
10465
|
-
crossDraft
|
|
10466
|
-
);
|
|
10467
|
-
} else if (depComp.componentType !== "Portal") {
|
|
10468
|
-
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
10469
|
-
ctx.addIssue(
|
|
10470
|
-
"error",
|
|
10471
|
-
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
10472
|
-
`Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
|
|
10473
|
-
comp.id,
|
|
10474
|
-
crossDraft
|
|
10475
|
-
);
|
|
10476
|
-
}
|
|
10477
|
-
continue;
|
|
10478
|
-
}
|
|
10479
|
-
const governingProfile2 = ctx.ext.profiles[ctx.getComponentProfile(comp.id)];
|
|
10480
|
-
if (governingProfile2?.allowedEdges?.some((e) => e.from.includes(comp.componentType) && e.to.includes(depComp.componentType))) {
|
|
10591
|
+
case "unpinned":
|
|
10592
|
+
ctx.addIssue(
|
|
10593
|
+
"warning",
|
|
10594
|
+
"CROSS_TREE_REF_UNRESOLVED",
|
|
10595
|
+
`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.`,
|
|
10596
|
+
comp.id,
|
|
10597
|
+
edge.draftContext
|
|
10598
|
+
);
|
|
10481
10599
|
continue;
|
|
10482
|
-
|
|
10483
|
-
const edgeDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
|
|
10484
|
-
const depPure = isPureLogic(depComp);
|
|
10485
|
-
const depRead = isReadLogic(depComp);
|
|
10486
|
-
const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
|
|
10487
|
-
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
|
|
10600
|
+
case "ambiguous":
|
|
10488
10601
|
ctx.addIssue(
|
|
10489
10602
|
"error",
|
|
10490
|
-
"
|
|
10491
|
-
`
|
|
10603
|
+
"SURFACE_REF_AMBIGUOUS",
|
|
10604
|
+
ambiguityMessage(edge.surface, `Component "${comp.id}" depends on`, edge.ref),
|
|
10492
10605
|
comp.id,
|
|
10493
|
-
|
|
10606
|
+
edge.draftContext
|
|
10494
10607
|
);
|
|
10495
10608
|
continue;
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
if (forbiddenTypes.includes(depComp.componentType)) {
|
|
10609
|
+
case "surface":
|
|
10610
|
+
if (comp.componentType !== "Adapter") {
|
|
10611
|
+
const provider = edge.surface.kind === "resolved" ? edge.surface.snapshot.projectName : "";
|
|
10500
10612
|
ctx.addIssue(
|
|
10501
10613
|
"error",
|
|
10502
|
-
"
|
|
10503
|
-
`
|
|
10614
|
+
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10615
|
+
`Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${edge.ref}", a surface of project "${provider}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
|
|
10504
10616
|
comp.id,
|
|
10505
|
-
|
|
10617
|
+
edge.draftContext,
|
|
10618
|
+
true
|
|
10506
10619
|
);
|
|
10507
10620
|
}
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10621
|
+
continue;
|
|
10622
|
+
case "cross-subsystem":
|
|
10623
|
+
break;
|
|
10624
|
+
default:
|
|
10625
|
+
continue;
|
|
10626
|
+
}
|
|
10627
|
+
const depComp = edge.to;
|
|
10628
|
+
const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
|
|
10629
|
+
const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
|
|
10630
|
+
if (comp.componentType !== "Adapter" && !licensed) {
|
|
10631
|
+
ctx.addIssue(
|
|
10632
|
+
"error",
|
|
10633
|
+
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10634
|
+
`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.`,
|
|
10635
|
+
comp.id,
|
|
10636
|
+
edge.draftContext
|
|
10637
|
+
);
|
|
10638
|
+
}
|
|
10639
|
+
const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
|
|
10640
|
+
if (!targetIsPublic) {
|
|
10641
|
+
ctx.addIssue(
|
|
10642
|
+
"error",
|
|
10643
|
+
"CROSS_SUBSYSTEM_PRIVATE_ACCESS",
|
|
10644
|
+
`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.`,
|
|
10645
|
+
comp.id,
|
|
10646
|
+
edge.draftContext
|
|
10647
|
+
);
|
|
10648
|
+
} else if (depComp.componentType !== "Portal") {
|
|
10649
|
+
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
10650
|
+
ctx.addIssue(
|
|
10651
|
+
"error",
|
|
10652
|
+
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
10653
|
+
`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.`,
|
|
10654
|
+
comp.id,
|
|
10655
|
+
edge.draftContext
|
|
10656
|
+
);
|
|
10657
|
+
}
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
};
|
|
10661
|
+
}
|
|
10662
|
+
});
|
|
10663
|
+
|
|
10664
|
+
// src/core/rules/doctrine/stereotype-terms.ts
|
|
10665
|
+
var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf;
|
|
10666
|
+
var init_stereotype_terms = __esm({
|
|
10667
|
+
"src/core/rules/doctrine/stereotype-terms.ts"() {
|
|
10668
|
+
"use strict";
|
|
10669
|
+
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.";
|
|
10670
|
+
storeResolutionHint = (consumerType, storeId) => {
|
|
10671
|
+
if (consumerType === "Orchestrator") {
|
|
10672
|
+
return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
|
|
10673
|
+
}
|
|
10674
|
+
return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
|
|
10675
|
+
};
|
|
10676
|
+
isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
|
|
10677
|
+
isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
|
|
10678
|
+
stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
|
|
10679
|
+
}
|
|
10680
|
+
});
|
|
10681
|
+
|
|
10682
|
+
// src/core/rules/doctrine/logic-dependency-class.ts
|
|
10683
|
+
var logicDependencyClassRule;
|
|
10684
|
+
var init_logic_dependency_class = __esm({
|
|
10685
|
+
"src/core/rules/doctrine/logic-dependency-class.ts"() {
|
|
10686
|
+
"use strict";
|
|
10687
|
+
init_stereotype_terms();
|
|
10688
|
+
logicDependencyClassRule = {
|
|
10689
|
+
name: "logic-dependency-class",
|
|
10690
|
+
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.",
|
|
10691
|
+
codes: [
|
|
10692
|
+
{ 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" }
|
|
10693
|
+
],
|
|
10694
|
+
check(ctx) {
|
|
10695
|
+
for (const edge of ctx.dependencyEdges().matrix) {
|
|
10696
|
+
const comp = edge.from;
|
|
10697
|
+
const depComp = edge.to;
|
|
10698
|
+
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
|
|
10699
|
+
const depPure = isPureLogic(depComp);
|
|
10700
|
+
const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
|
|
10701
|
+
if (isPureLogic(comp) && !depPure) {
|
|
10702
|
+
ctx.addIssue(
|
|
10703
|
+
"error",
|
|
10704
|
+
"DEPENDENCY_CLASS_VIOLATION",
|
|
10705
|
+
`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,
|
|
10706
|
+
comp.id,
|
|
10707
|
+
edge.draftContext
|
|
10708
|
+
);
|
|
10709
|
+
}
|
|
10710
|
+
if (isReadLogic(comp) && !depPure && !isReadLogic(depComp) && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
|
|
10711
|
+
ctx.addIssue(
|
|
10712
|
+
"error",
|
|
10713
|
+
"DEPENDENCY_CLASS_VIOLATION",
|
|
10714
|
+
`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,
|
|
10715
|
+
comp.id,
|
|
10716
|
+
edge.draftContext
|
|
10717
|
+
);
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
}
|
|
10721
|
+
};
|
|
10722
|
+
}
|
|
10723
|
+
});
|
|
10724
|
+
|
|
10725
|
+
// src/core/rules/doctrine/data-block-dependencies.ts
|
|
10726
|
+
var dataBlockDepsRule;
|
|
10727
|
+
var init_data_block_dependencies = __esm({
|
|
10728
|
+
"src/core/rules/doctrine/data-block-dependencies.ts"() {
|
|
10729
|
+
"use strict";
|
|
10730
|
+
init_stereotype_terms();
|
|
10731
|
+
dataBlockDepsRule = {
|
|
10732
|
+
name: "data-block-dependencies",
|
|
10733
|
+
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.",
|
|
10734
|
+
codes: [
|
|
10735
|
+
{ code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
|
|
10736
|
+
{ 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)" },
|
|
10737
|
+
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
|
|
10738
|
+
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
|
|
10739
|
+
{ code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
|
|
10740
|
+
],
|
|
10741
|
+
check(ctx) {
|
|
10742
|
+
for (const edge of ctx.dependencyEdges().matrix) {
|
|
10743
|
+
const comp = edge.from;
|
|
10744
|
+
const depComp = edge.to;
|
|
10745
|
+
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
|
|
10746
|
+
const depPure = isPureLogic(depComp);
|
|
10747
|
+
const ownStoreOnly = depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure;
|
|
10748
|
+
if (comp.componentType === "Store" && ownStoreOnly) {
|
|
10749
|
+
ctx.addIssue(
|
|
10750
|
+
"error",
|
|
10751
|
+
"ARCHITECTURE_VIOLATION_STORE_DEP",
|
|
10752
|
+
`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.`,
|
|
10753
|
+
comp.id,
|
|
10754
|
+
edge.draftContext
|
|
10755
|
+
);
|
|
10756
|
+
}
|
|
10757
|
+
if (comp.componentType === "Registry" && ownStoreOnly) {
|
|
10758
|
+
ctx.addIssue(
|
|
10759
|
+
"warning",
|
|
10760
|
+
"ARCHITECTURE_VIOLATION_REGISTRY_DEP",
|
|
10761
|
+
`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.`,
|
|
10762
|
+
comp.id,
|
|
10763
|
+
edge.draftContext
|
|
10764
|
+
);
|
|
10765
|
+
}
|
|
10766
|
+
if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
|
|
10767
|
+
ctx.addIssue(
|
|
10768
|
+
"error",
|
|
10769
|
+
"ARCHITECTURE_VIOLATION_ADAPTER_DEP",
|
|
10770
|
+
`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.`,
|
|
10771
|
+
comp.id,
|
|
10772
|
+
edge.draftContext
|
|
10773
|
+
);
|
|
10774
|
+
}
|
|
10775
|
+
if (comp.componentType === "Index" && ownStoreOnly) {
|
|
10776
|
+
ctx.addIssue(
|
|
10777
|
+
"error",
|
|
10778
|
+
"ARCHITECTURE_VIOLATION_INDEX_DEP",
|
|
10779
|
+
`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.`,
|
|
10780
|
+
comp.id,
|
|
10781
|
+
edge.draftContext
|
|
10782
|
+
);
|
|
10783
|
+
}
|
|
10784
|
+
if (comp.componentType === "Query" && ownStoreOnly) {
|
|
10785
|
+
ctx.addIssue(
|
|
10786
|
+
"error",
|
|
10787
|
+
"ARCHITECTURE_VIOLATION_QUERY_DEP",
|
|
10788
|
+
`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.`,
|
|
10789
|
+
comp.id,
|
|
10790
|
+
edge.draftContext
|
|
10791
|
+
);
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
10795
|
+
};
|
|
10796
|
+
}
|
|
10797
|
+
});
|
|
10798
|
+
|
|
10799
|
+
// src/core/rules/doctrine/entrypoint-dependencies.ts
|
|
10800
|
+
var entrypointDepsRule;
|
|
10801
|
+
var init_entrypoint_dependencies = __esm({
|
|
10802
|
+
"src/core/rules/doctrine/entrypoint-dependencies.ts"() {
|
|
10803
|
+
"use strict";
|
|
10804
|
+
init_stereotype_terms();
|
|
10805
|
+
entrypointDepsRule = {
|
|
10806
|
+
name: "entrypoint-dependencies",
|
|
10807
|
+
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.",
|
|
10808
|
+
codes: [
|
|
10809
|
+
{ code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
|
|
10810
|
+
{ code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
|
|
10811
|
+
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
|
|
10812
|
+
{ 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" },
|
|
10813
|
+
{ 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" }
|
|
10814
|
+
],
|
|
10815
|
+
check(ctx) {
|
|
10816
|
+
for (const edge of ctx.dependencyEdges().matrix) {
|
|
10817
|
+
const comp = edge.from;
|
|
10818
|
+
const depComp = edge.to;
|
|
10819
|
+
const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
|
|
10820
|
+
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
|
|
10821
|
+
ctx.addIssue(
|
|
10822
|
+
"error",
|
|
10823
|
+
"ARCHITECTURE_VIOLATION_PORTAL_DEP",
|
|
10824
|
+
`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.`,
|
|
10825
|
+
comp.id,
|
|
10826
|
+
edge.draftContext
|
|
10827
|
+
);
|
|
10828
|
+
continue;
|
|
10829
|
+
}
|
|
10830
|
+
if (comp.componentType === "Portal" || comp.componentType === "Observer") {
|
|
10831
|
+
const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
|
|
10832
|
+
if (forbiddenTypes.includes(depComp.componentType)) {
|
|
10573
10833
|
ctx.addIssue(
|
|
10574
10834
|
"error",
|
|
10575
|
-
"
|
|
10576
|
-
`Architectural violation:
|
|
10835
|
+
"ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
|
|
10836
|
+
`Architectural violation: ${comp.componentType} component "${comp.id}" cannot depend directly on "${depComp.componentType}" component "${depComp.id}". ${comp.componentType}s coordinate through Orchestrators (and Supervisors); they do not reach the data layer directly.` + storeHint,
|
|
10577
10837
|
comp.id,
|
|
10578
|
-
|
|
10838
|
+
edge.draftContext
|
|
10579
10839
|
);
|
|
10580
10840
|
}
|
|
10581
|
-
|
|
10841
|
+
}
|
|
10842
|
+
if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !isPureLogic(depComp))) {
|
|
10843
|
+
ctx.addIssue(
|
|
10844
|
+
"error",
|
|
10845
|
+
"ARCHITECTURE_VIOLATION_VIEW_DEP",
|
|
10846
|
+
`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,
|
|
10847
|
+
comp.id,
|
|
10848
|
+
edge.draftContext
|
|
10849
|
+
);
|
|
10850
|
+
}
|
|
10851
|
+
if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
|
|
10852
|
+
ctx.addIssue(
|
|
10853
|
+
"error",
|
|
10854
|
+
"ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
|
|
10855
|
+
`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,
|
|
10856
|
+
comp.id,
|
|
10857
|
+
edge.draftContext
|
|
10858
|
+
);
|
|
10859
|
+
}
|
|
10860
|
+
if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
|
|
10861
|
+
const reachedThroughSupervisor = comp.dependsOn.some((id) => {
|
|
10862
|
+
const supervisor = ctx.componentMap.get(id);
|
|
10863
|
+
return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
|
|
10864
|
+
});
|
|
10865
|
+
if (!reachedThroughSupervisor) {
|
|
10866
|
+
const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
|
|
10867
|
+
const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
|
|
10582
10868
|
ctx.addIssue(
|
|
10583
10869
|
"error",
|
|
10584
|
-
"
|
|
10585
|
-
`Architectural violation:
|
|
10870
|
+
"ACTOR_REACHED_WITHOUT_SUPERVISOR",
|
|
10871
|
+
`Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
|
|
10586
10872
|
comp.id,
|
|
10587
|
-
|
|
10873
|
+
edge.draftContext
|
|
10588
10874
|
);
|
|
10589
10875
|
}
|
|
10590
|
-
if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
|
|
10591
|
-
const reachedThroughSupervisor = comp.dependsOn.some((id) => {
|
|
10592
|
-
const supervisor = ctx.componentMap.get(id);
|
|
10593
|
-
return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
|
|
10594
|
-
});
|
|
10595
|
-
if (!reachedThroughSupervisor) {
|
|
10596
|
-
const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
|
|
10597
|
-
const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
|
|
10598
|
-
ctx.addIssue(
|
|
10599
|
-
"error",
|
|
10600
|
-
"ACTOR_REACHED_WITHOUT_SUPERVISOR",
|
|
10601
|
-
`Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
|
|
10602
|
-
comp.id,
|
|
10603
|
-
edgeDraft
|
|
10604
|
-
);
|
|
10605
|
-
}
|
|
10606
|
-
}
|
|
10607
10876
|
}
|
|
10608
10877
|
}
|
|
10878
|
+
}
|
|
10879
|
+
};
|
|
10880
|
+
}
|
|
10881
|
+
});
|
|
10882
|
+
|
|
10883
|
+
// src/core/rules/doctrine/portal-write-shortcut.ts
|
|
10884
|
+
var portalWriteShortcutRule;
|
|
10885
|
+
var init_portal_write_shortcut = __esm({
|
|
10886
|
+
"src/core/rules/doctrine/portal-write-shortcut.ts"() {
|
|
10887
|
+
"use strict";
|
|
10888
|
+
portalWriteShortcutRule = {
|
|
10889
|
+
name: "portal-write-shortcut",
|
|
10890
|
+
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.",
|
|
10891
|
+
codes: [
|
|
10892
|
+
{ 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)" }
|
|
10893
|
+
],
|
|
10894
|
+
check(ctx) {
|
|
10609
10895
|
for (const impl of ctx.implementations) {
|
|
10610
10896
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10611
10897
|
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
@@ -10650,31 +10936,23 @@ var init_stereotype_dependencies = __esm({
|
|
|
10650
10936
|
}
|
|
10651
10937
|
});
|
|
10652
10938
|
|
|
10653
|
-
// src/core/rules/doctrine/pattern-
|
|
10654
|
-
var
|
|
10655
|
-
var
|
|
10656
|
-
"src/core/rules/doctrine/pattern-
|
|
10939
|
+
// src/core/rules/doctrine/pattern-membership.ts
|
|
10940
|
+
var patternMembershipRule;
|
|
10941
|
+
var init_pattern_membership = __esm({
|
|
10942
|
+
"src/core/rules/doctrine/pattern-membership.ts"() {
|
|
10657
10943
|
"use strict";
|
|
10658
10944
|
init_models();
|
|
10659
|
-
|
|
10660
|
-
name: "pattern-
|
|
10661
|
-
description: "
|
|
10945
|
+
patternMembershipRule = {
|
|
10946
|
+
name: "pattern-membership",
|
|
10947
|
+
description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern to claim it.",
|
|
10662
10948
|
codes: [
|
|
10663
10949
|
{ code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
|
|
10664
10950
|
{ code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block using owns" },
|
|
10665
10951
|
{ code: "INVALID_OWNED_MEMBER", defaultSeverity: "error", summary: "owns names a non-existent component" },
|
|
10666
10952
|
{ code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
|
|
10667
|
-
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
|
|
10668
|
-
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
|
|
10669
|
-
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
10670
|
-
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
|
|
10671
|
-
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
10672
|
-
{ code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
|
|
10673
|
-
{ code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" },
|
|
10674
|
-
{ code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
|
|
10953
|
+
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
|
|
10675
10954
|
],
|
|
10676
10955
|
check(ctx) {
|
|
10677
|
-
const ownedBy = /* @__PURE__ */ new Map();
|
|
10678
10956
|
for (const comp of ctx.components) {
|
|
10679
10957
|
if (isRetired(comp)) continue;
|
|
10680
10958
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
@@ -10685,6 +10963,12 @@ var init_pattern_ownership = __esm({
|
|
|
10685
10963
|
if (!pattern && comp.owns.length > 0) {
|
|
10686
10964
|
ctx.addIssue("error", "BLOCK_OWNS_MEMBERS", `Building block "${comp.id}" (${comp.componentType}) cannot own members; only patterns (${Array.from(PATTERN_TYPES).join("/")}) use "owns".`, comp.id, isDraftCtx);
|
|
10687
10965
|
}
|
|
10966
|
+
}
|
|
10967
|
+
const ownership = ctx.ownershipIndex();
|
|
10968
|
+
for (const comp of ctx.components) {
|
|
10969
|
+
if (isRetired(comp)) continue;
|
|
10970
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10971
|
+
const pattern = isPattern(comp);
|
|
10688
10972
|
for (const memberId of comp.owns) {
|
|
10689
10973
|
const member = ctx.componentMap.get(memberId);
|
|
10690
10974
|
if (!member) {
|
|
@@ -10696,26 +10980,50 @@ var init_pattern_ownership = __esm({
|
|
|
10696
10980
|
ctx.addIssue("error", "PATTERN_OWNS_PATTERN", `Pattern "${comp.id}" owns "${memberId}", which is itself a pattern. Patterns own only building blocks \u2014 compose patterns at the subsystem (L1) level.`, comp.id, isDraftCtx);
|
|
10697
10981
|
continue;
|
|
10698
10982
|
}
|
|
10699
|
-
const firstOwner =
|
|
10983
|
+
const firstOwner = ownership.ownerOf(memberId);
|
|
10700
10984
|
if (firstOwner && firstOwner !== comp.id) {
|
|
10701
10985
|
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${firstOwner}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
|
|
10702
10986
|
}
|
|
10703
|
-
if (!firstOwner) ownedBy.set(memberId, comp.id);
|
|
10704
10987
|
}
|
|
10705
|
-
|
|
10988
|
+
}
|
|
10989
|
+
}
|
|
10990
|
+
};
|
|
10991
|
+
}
|
|
10992
|
+
});
|
|
10993
|
+
|
|
10994
|
+
// src/core/rules/doctrine/pattern-containment.ts
|
|
10995
|
+
var REPOSITORY_MEMBERS, patternContainmentRule;
|
|
10996
|
+
var init_pattern_containment = __esm({
|
|
10997
|
+
"src/core/rules/doctrine/pattern-containment.ts"() {
|
|
10998
|
+
"use strict";
|
|
10999
|
+
init_models();
|
|
11000
|
+
REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
|
|
11001
|
+
patternContainmentRule = {
|
|
11002
|
+
name: "pattern-containment",
|
|
11003
|
+
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.",
|
|
11004
|
+
codes: [
|
|
11005
|
+
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
|
|
11006
|
+
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
11007
|
+
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" }
|
|
11008
|
+
],
|
|
11009
|
+
check(ctx) {
|
|
11010
|
+
for (const comp of ctx.components) {
|
|
11011
|
+
if (comp.componentType !== "Repository" || isRetired(comp)) continue;
|
|
11012
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11013
|
+
for (const memberId of comp.owns) {
|
|
10706
11014
|
const member = ctx.componentMap.get(memberId);
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
continue;
|
|
10710
|
-
}
|
|
10711
|
-
if (comp.componentType === "Repository") {
|
|
10712
|
-
const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
|
|
10713
|
-
for (const memberId of comp.owns) {
|
|
10714
|
-
const member = ctx.componentMap.get(memberId);
|
|
10715
|
-
if (!member || isRetired(member) || allowed.has(member.componentType)) continue;
|
|
10716
|
-
ctx.addIssue("error", "REPOSITORY_CONTAINMENT", `Repository "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Repository may own only Store, Registry, Index, Query, and (optionally) Adapter.`, comp.id, isDraftCtx);
|
|
10717
|
-
}
|
|
11015
|
+
if (!member || isRetired(member) || REPOSITORY_MEMBERS.has(member.componentType)) continue;
|
|
11016
|
+
ctx.addIssue("error", "REPOSITORY_CONTAINMENT", `Repository "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Repository may own only Store, Registry, Index, Query, and (optionally) Adapter.`, comp.id, isDraftCtx);
|
|
10718
11017
|
}
|
|
11018
|
+
}
|
|
11019
|
+
for (const comp of ctx.components) {
|
|
11020
|
+
if (comp.componentType !== "FeatureComponent" && comp.componentType !== "RouterComponent") continue;
|
|
11021
|
+
if (isRetired(comp)) continue;
|
|
11022
|
+
if (comp.owns.some((memberId) => {
|
|
11023
|
+
const member = ctx.componentMap.get(memberId);
|
|
11024
|
+
return member !== void 0 && isRetired(member);
|
|
11025
|
+
})) continue;
|
|
11026
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10719
11027
|
if (comp.componentType === "FeatureComponent") {
|
|
10720
11028
|
let orchestrators = 0;
|
|
10721
11029
|
let views = 0;
|
|
@@ -10746,45 +11054,83 @@ var init_pattern_ownership = __esm({
|
|
|
10746
11054
|
}
|
|
10747
11055
|
}
|
|
10748
11056
|
}
|
|
11057
|
+
}
|
|
11058
|
+
};
|
|
11059
|
+
}
|
|
11060
|
+
});
|
|
11061
|
+
|
|
11062
|
+
// src/core/rules/doctrine/unowned-blocks.ts
|
|
11063
|
+
var unownedBlocksRule;
|
|
11064
|
+
var init_unowned_blocks = __esm({
|
|
11065
|
+
"src/core/rules/doctrine/unowned-blocks.ts"() {
|
|
11066
|
+
"use strict";
|
|
11067
|
+
unownedBlocksRule = {
|
|
11068
|
+
name: "unowned-blocks",
|
|
11069
|
+
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.`,
|
|
11070
|
+
codes: [
|
|
11071
|
+
{ 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" },
|
|
11072
|
+
{ 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" },
|
|
11073
|
+
{ code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
|
|
11074
|
+
],
|
|
11075
|
+
check(ctx) {
|
|
11076
|
+
const ownership = ctx.ownershipIndex();
|
|
10749
11077
|
for (const comp of ctx.components) {
|
|
10750
|
-
if (
|
|
10751
|
-
ctx.
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
);
|
|
11078
|
+
if (ownership.ownedMembers.has(comp.id)) continue;
|
|
11079
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11080
|
+
if (comp.componentType === "Store") {
|
|
11081
|
+
ctx.addIssue(
|
|
11082
|
+
"warning",
|
|
11083
|
+
"UNOWNED_STORE",
|
|
11084
|
+
`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.`,
|
|
11085
|
+
comp.id,
|
|
11086
|
+
isDraftCtx
|
|
11087
|
+
);
|
|
11088
|
+
}
|
|
11089
|
+
if (comp.componentType === "Registry" && !comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store")) {
|
|
11090
|
+
ctx.addIssue(
|
|
11091
|
+
"warning",
|
|
11092
|
+
"REGISTRY_WITHOUT_STORE",
|
|
11093
|
+
`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.`,
|
|
11094
|
+
comp.id,
|
|
11095
|
+
isDraftCtx
|
|
11096
|
+
);
|
|
11097
|
+
}
|
|
11098
|
+
if (comp.componentType === "Query") {
|
|
11099
|
+
ctx.addIssue(
|
|
11100
|
+
"error",
|
|
11101
|
+
"UNOWNED_QUERY",
|
|
11102
|
+
`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]).`,
|
|
11103
|
+
comp.id,
|
|
11104
|
+
isDraftCtx
|
|
11105
|
+
);
|
|
11106
|
+
}
|
|
10780
11107
|
}
|
|
11108
|
+
}
|
|
11109
|
+
};
|
|
11110
|
+
}
|
|
11111
|
+
});
|
|
11112
|
+
|
|
11113
|
+
// src/core/rules/doctrine/member-visibility.ts
|
|
11114
|
+
var memberVisibilityRule;
|
|
11115
|
+
var init_member_visibility = __esm({
|
|
11116
|
+
"src/core/rules/doctrine/member-visibility.ts"() {
|
|
11117
|
+
"use strict";
|
|
11118
|
+
init_models();
|
|
11119
|
+
memberVisibilityRule = {
|
|
11120
|
+
name: "member-visibility",
|
|
11121
|
+
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.",
|
|
11122
|
+
codes: [
|
|
11123
|
+
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
|
|
11124
|
+
],
|
|
11125
|
+
check(ctx) {
|
|
11126
|
+
const ownership = ctx.ownershipIndex();
|
|
10781
11127
|
for (const comp of ctx.components) {
|
|
10782
11128
|
if (isRetired(comp)) continue;
|
|
10783
11129
|
for (const depId of comp.dependsOn) {
|
|
10784
|
-
const owner =
|
|
11130
|
+
const owner = ownership.ownerOf(depId);
|
|
10785
11131
|
if (!owner) continue;
|
|
10786
11132
|
if (owner === comp.id) continue;
|
|
10787
|
-
if (
|
|
11133
|
+
if (ownership.ownerOf(comp.id) === owner) continue;
|
|
10788
11134
|
ctx.addIssue("error", "VISIBILITY_VIOLATION", `Component "${comp.id}" depends on "${depId}", which is privately owned by pattern "${owner}". Depend on the facade "${owner}" instead.`, comp.id, ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(depId));
|
|
10789
11135
|
}
|
|
10790
11136
|
}
|
|
@@ -12182,8 +12528,7 @@ var init_source_file_linkage = __esm({
|
|
|
12182
12528
|
{ code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
|
|
12183
12529
|
],
|
|
12184
12530
|
check(ctx) {
|
|
12185
|
-
const
|
|
12186
|
-
for (const facts of ctx.codeModel.files) factsByPath.set(pathKey(facts.path), facts);
|
|
12531
|
+
const code = ctx.codeIndex();
|
|
12187
12532
|
const degradedTsFiles = ctx.codeModel.files.filter(
|
|
12188
12533
|
(f) => f.status === "analyzed" && (f.language === "typescript" || f.language === "javascript") && f.analysisGrade !== "exact"
|
|
12189
12534
|
);
|
|
@@ -12231,7 +12576,7 @@ var init_source_file_linkage = __esm({
|
|
|
12231
12576
|
const key = pathKey(file);
|
|
12232
12577
|
if (reported.has(key)) continue;
|
|
12233
12578
|
reported.add(key);
|
|
12234
|
-
const facts =
|
|
12579
|
+
const facts = code.factsAt(key);
|
|
12235
12580
|
if (!facts || facts.status === "analyzed") continue;
|
|
12236
12581
|
const isImplementationFile = !!impl.sourcePath && pathKey(impl.sourcePath) === key;
|
|
12237
12582
|
const users = isImplementationFile ? [] : impl.methods.filter((m) => m.sourcePath && pathKey(m.sourcePath) === key).map((m) => m.name);
|
|
@@ -12281,14 +12626,7 @@ var init_method_realization = __esm({
|
|
|
12281
12626
|
{ code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" }
|
|
12282
12627
|
],
|
|
12283
12628
|
check(ctx) {
|
|
12284
|
-
const
|
|
12285
|
-
for (const facts of ctx.codeModel.files) {
|
|
12286
|
-
lookups.set(pathKey(facts.path), {
|
|
12287
|
-
declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
|
|
12288
|
-
anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
|
|
12289
|
-
facts
|
|
12290
|
-
});
|
|
12291
|
-
}
|
|
12629
|
+
const code = ctx.codeIndex();
|
|
12292
12630
|
for (const impl of ctx.implementations) {
|
|
12293
12631
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12294
12632
|
if (!contract) continue;
|
|
@@ -12303,11 +12641,11 @@ var init_method_realization = __esm({
|
|
|
12303
12641
|
if (tier === "off") continue;
|
|
12304
12642
|
const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
|
|
12305
12643
|
if (!file) continue;
|
|
12306
|
-
const
|
|
12307
|
-
if (!
|
|
12644
|
+
const facts = code.factsAt(file);
|
|
12645
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
12308
12646
|
const symbol = methodImpl?.symbol ?? method2.name;
|
|
12309
|
-
const inDeclared =
|
|
12310
|
-
const inAnchored = inDeclared ||
|
|
12647
|
+
const inDeclared = code.declarationsAt(file).has(symbol);
|
|
12648
|
+
const inAnchored = inDeclared || code.anchorsAt(file).has(symbol);
|
|
12311
12649
|
const realized = tier === "declared" ? inDeclared : inAnchored;
|
|
12312
12650
|
if (realized) continue;
|
|
12313
12651
|
const label = methodImpl?.symbol ? `"${method2.name}" (symbol "${symbol}")` : `"${method2.name}"`;
|
|
@@ -12315,7 +12653,7 @@ var init_method_realization = __esm({
|
|
|
12315
12653
|
ctx.addIssue(
|
|
12316
12654
|
"warning",
|
|
12317
12655
|
"UNREALIZED_METHOD",
|
|
12318
|
-
`Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${
|
|
12656
|
+
`Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${facts.analysisGrade}).${weakHint}`,
|
|
12319
12657
|
impl.id,
|
|
12320
12658
|
draft
|
|
12321
12659
|
);
|
|
@@ -12339,10 +12677,7 @@ var init_finding_realization = __esm({
|
|
|
12339
12677
|
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" }
|
|
12340
12678
|
],
|
|
12341
12679
|
check(ctx) {
|
|
12342
|
-
const
|
|
12343
|
-
for (const facts of ctx.codeModel.files) {
|
|
12344
|
-
lookups.set(pathKey(facts.path), { findingAnchors: new Set(facts.anchoredNames), facts });
|
|
12345
|
-
}
|
|
12680
|
+
const code = ctx.codeIndex();
|
|
12346
12681
|
for (const impl of ctx.implementations) {
|
|
12347
12682
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12348
12683
|
if (!contract) continue;
|
|
@@ -12358,14 +12693,15 @@ var init_finding_realization = __esm({
|
|
|
12358
12693
|
if (tier === "off") continue;
|
|
12359
12694
|
const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
|
|
12360
12695
|
if (!file) continue;
|
|
12361
|
-
const
|
|
12362
|
-
if (!
|
|
12696
|
+
const facts = code.factsAt(file);
|
|
12697
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
12698
|
+
const findingAnchors = code.findingAnchorsAt(file);
|
|
12363
12699
|
for (const finding of method2.findings) {
|
|
12364
|
-
if (
|
|
12700
|
+
if (findingAnchors.has(finding.code)) continue;
|
|
12365
12701
|
ctx.addIssue(
|
|
12366
12702
|
"warning",
|
|
12367
12703
|
"UNREALIZED_FINDING",
|
|
12368
|
-
`Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${
|
|
12704
|
+
`Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
|
|
12369
12705
|
impl.id,
|
|
12370
12706
|
draft
|
|
12371
12707
|
);
|
|
@@ -12409,8 +12745,7 @@ var init_call_conformance = __esm({
|
|
|
12409
12745
|
{ code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step whose target method name (or symbol) never appears among the realized function's callees in the method's source file (exact grade, set membership)" }
|
|
12410
12746
|
],
|
|
12411
12747
|
check(ctx) {
|
|
12412
|
-
const
|
|
12413
|
-
for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
|
|
12748
|
+
const code = ctx.codeIndex();
|
|
12414
12749
|
for (const impl of ctx.implementations) {
|
|
12415
12750
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12416
12751
|
if (!contract) continue;
|
|
@@ -12425,7 +12760,7 @@ var init_call_conformance = __esm({
|
|
|
12425
12760
|
if (!implMethod.narrative.length) continue;
|
|
12426
12761
|
const file = methodSourceFile(implMethod, impl.sourcePath);
|
|
12427
12762
|
if (!file) continue;
|
|
12428
|
-
const facts =
|
|
12763
|
+
const facts = code.factsAt(file);
|
|
12429
12764
|
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
12430
12765
|
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
12431
12766
|
const callees = closedCallees(facts, fnSymbol);
|
|
@@ -12479,6 +12814,7 @@ var init_hidden_state = __esm({
|
|
|
12479
12814
|
{ code: "HIDDEN_STATE", defaultSeverity: "warning", summary: "Module-scope mutable binding in a file mapped only to stateless logic (Orchestrators) \u2014 held state hiding outside a Store, Supervisor or Actor" }
|
|
12480
12815
|
],
|
|
12481
12816
|
check(ctx) {
|
|
12817
|
+
const code = ctx.codeIndex();
|
|
12482
12818
|
const byPath = /* @__PURE__ */ new Map();
|
|
12483
12819
|
for (const impl of ctx.implementations) {
|
|
12484
12820
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
@@ -12500,11 +12836,11 @@ var init_hidden_state = __esm({
|
|
|
12500
12836
|
byPath.set(key, list2);
|
|
12501
12837
|
}
|
|
12502
12838
|
}
|
|
12503
|
-
for (const
|
|
12504
|
-
|
|
12839
|
+
for (const path70 of code.exactPaths) {
|
|
12840
|
+
const facts = code.factsAt(path70);
|
|
12505
12841
|
const bindings = facts.topLevelMutableBindings ?? [];
|
|
12506
12842
|
if (bindings.length === 0) continue;
|
|
12507
|
-
const mapped = byPath.get(
|
|
12843
|
+
const mapped = byPath.get(path70) ?? [];
|
|
12508
12844
|
if (mapped.length === 0) continue;
|
|
12509
12845
|
if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
|
|
12510
12846
|
const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
|
|
@@ -12536,54 +12872,21 @@ var init_dependency_conformance = __esm({
|
|
|
12536
12872
|
{ code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
|
|
12537
12873
|
],
|
|
12538
12874
|
check(ctx) {
|
|
12539
|
-
const
|
|
12540
|
-
const
|
|
12541
|
-
const
|
|
12542
|
-
const
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
if (!
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
let node = nodes.get(p);
|
|
12555
|
-
if (!node) {
|
|
12556
|
-
node = { path: p, components: [], anchorImplId: impl.id, imports: facts.imports, reexports: facts.reexports, draft: false };
|
|
12557
|
-
nodes.set(p, node);
|
|
12558
|
-
}
|
|
12559
|
-
if (!node.components.some((c) => c.id === component.id)) node.components.push(component);
|
|
12560
|
-
node.draft = node.draft || ctx.isImplementationDraft(impl);
|
|
12561
|
-
if (!filesByComponent.has(component.id)) filesByComponent.set(component.id, /* @__PURE__ */ new Set());
|
|
12562
|
-
filesByComponent.get(component.id).add(p);
|
|
12563
|
-
mapped = true;
|
|
12564
|
-
}
|
|
12565
|
-
if (mapped) {
|
|
12566
|
-
if (!implsByComponent.has(component.id)) implsByComponent.set(component.id, []);
|
|
12567
|
-
implsByComponent.get(component.id).push(impl);
|
|
12568
|
-
}
|
|
12569
|
-
}
|
|
12570
|
-
const mappedPaths = new Set(nodes.keys());
|
|
12571
|
-
const edges = /* @__PURE__ */ new Map();
|
|
12572
|
-
const realizationEdges = /* @__PURE__ */ new Map();
|
|
12573
|
-
for (const node of nodes.values()) {
|
|
12574
|
-
const targets = /* @__PURE__ */ new Set();
|
|
12575
|
-
for (const spec of node.imports) {
|
|
12576
|
-
const resolved = resolveImport(node.path, spec, mappedPaths);
|
|
12577
|
-
if (resolved && resolved !== node.path) targets.add(resolved);
|
|
12578
|
-
}
|
|
12579
|
-
edges.set(node.path, targets);
|
|
12580
|
-
const realized = new Set(targets);
|
|
12581
|
-
for (const spec of node.reexports) {
|
|
12582
|
-
const resolved = resolveImport(node.path, spec, mappedPaths);
|
|
12583
|
-
if (resolved && resolved !== node.path) realized.add(resolved);
|
|
12584
|
-
}
|
|
12585
|
-
realizationEdges.set(node.path, realized);
|
|
12586
|
-
}
|
|
12875
|
+
const code = ctx.codeIndex();
|
|
12876
|
+
const realization = ctx.realizationIndex();
|
|
12877
|
+
const isExact = (p) => code.exactPaths.has(p);
|
|
12878
|
+
const mappedPaths = new Set(realization.paths.filter(isExact));
|
|
12879
|
+
const graph = ctx.importGraph(mappedPaths);
|
|
12880
|
+
const exactFiles = /* @__PURE__ */ new Map();
|
|
12881
|
+
const filesOf = (compId) => {
|
|
12882
|
+
let files = exactFiles.get(compId);
|
|
12883
|
+
if (!files) {
|
|
12884
|
+
files = realization.filesOf(compId).filter(isExact);
|
|
12885
|
+
exactFiles.set(compId, files);
|
|
12886
|
+
}
|
|
12887
|
+
return files;
|
|
12888
|
+
};
|
|
12889
|
+
const mappedImplsOf = (compId) => realization.implementationsOf(compId).filter((impl) => implementationSourceFiles(impl).some((f) => isExact(pathKey(f))));
|
|
12587
12890
|
const dependsOrOwns = (from, toId) => from.dependsOn.includes(toId) || (from.owns ?? []).includes(toId);
|
|
12588
12891
|
const ownerOf = /* @__PURE__ */ new Map();
|
|
12589
12892
|
for (const c of ctx.components) {
|
|
@@ -12594,9 +12897,9 @@ var init_dependency_conformance = __esm({
|
|
|
12594
12897
|
if (!published || published.size === 0) return false;
|
|
12595
12898
|
return from.dependsOn.some((d) => published.has(d));
|
|
12596
12899
|
};
|
|
12597
|
-
const edgeJustified = (
|
|
12598
|
-
for (const cf of
|
|
12599
|
-
for (const cg of
|
|
12900
|
+
const edgeJustified = (from, to) => {
|
|
12901
|
+
for (const cf of from) {
|
|
12902
|
+
for (const cg of to) {
|
|
12600
12903
|
if (cf.id === cg.id) return true;
|
|
12601
12904
|
if (dependsOrOwns(cf, cg.id)) return true;
|
|
12602
12905
|
if (cf.subsystem === cg.subsystem && dependsOrOwns(cg, cf.id) && (cg.componentType === "Portal" || cg.componentType === "Observer")) return true;
|
|
@@ -12610,60 +12913,35 @@ var init_dependency_conformance = __esm({
|
|
|
12610
12913
|
}
|
|
12611
12914
|
return false;
|
|
12612
12915
|
};
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12916
|
+
const draftAt = (path70) => realization.implementationsAt(path70).some((impl) => ctx.isImplementationDraft(impl));
|
|
12917
|
+
for (const fromPath of mappedPaths) {
|
|
12918
|
+
const fromComponents = realization.componentsAt(fromPath);
|
|
12919
|
+
for (const toPath of graph.importsOf(fromPath)) {
|
|
12920
|
+
const toComponents = realization.componentsAt(toPath);
|
|
12921
|
+
if (edgeJustified(fromComponents, toComponents)) continue;
|
|
12618
12922
|
ctx.addIssue(
|
|
12619
12923
|
"warning",
|
|
12620
12924
|
"UNDECLARED_DEPENDENCY",
|
|
12621
|
-
`"${fromPath}" (realizing ${
|
|
12622
|
-
|
|
12623
|
-
|
|
12925
|
+
`"${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.`,
|
|
12926
|
+
realization.implementationsAt(fromPath)[0]?.id,
|
|
12927
|
+
draftAt(fromPath) || draftAt(toPath)
|
|
12624
12928
|
);
|
|
12625
12929
|
}
|
|
12626
12930
|
}
|
|
12627
|
-
const hasImportBetween = (fromFiles, toFiles, allowReverse) => {
|
|
12628
|
-
for (const f of fromFiles) {
|
|
12629
|
-
const targets = realizationEdges.get(f);
|
|
12630
|
-
if (!targets) continue;
|
|
12631
|
-
for (const t of toFiles) if (targets.has(t)) return true;
|
|
12632
|
-
}
|
|
12633
|
-
if (allowReverse) {
|
|
12634
|
-
for (const t of toFiles) {
|
|
12635
|
-
const reverse = realizationEdges.get(t);
|
|
12636
|
-
if (!reverse) continue;
|
|
12637
|
-
for (const f of fromFiles) if (reverse.has(f)) return true;
|
|
12638
|
-
}
|
|
12639
|
-
}
|
|
12640
|
-
return false;
|
|
12641
|
-
};
|
|
12642
12931
|
for (const component of ctx.components) {
|
|
12643
|
-
const fromFiles =
|
|
12644
|
-
if (
|
|
12932
|
+
const fromFiles = filesOf(component.id);
|
|
12933
|
+
if (fromFiles.length === 0) continue;
|
|
12645
12934
|
const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
|
|
12646
12935
|
const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
|
|
12647
12936
|
for (const targetId of declaredTargets) {
|
|
12648
12937
|
const target = ctx.componentMap.get(targetId);
|
|
12649
12938
|
if (!target) continue;
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
for (const f of fromFiles) subsystemFiles.delete(f);
|
|
12657
|
-
if (subsystemFiles.size === 0) continue;
|
|
12658
|
-
if (hasImportBetween(fromFiles, subsystemFiles, isMountingDeclarer)) continue;
|
|
12659
|
-
} else {
|
|
12660
|
-
const toFiles = filesByComponent.get(targetId);
|
|
12661
|
-
if (!toFiles) continue;
|
|
12662
|
-
if ([...fromFiles].some((f) => toFiles.has(f))) continue;
|
|
12663
|
-
if (hasImportBetween(fromFiles, toFiles, isMountingDeclarer)) continue;
|
|
12664
|
-
}
|
|
12665
|
-
const impls = implsByComponent.get(component.id) ?? [];
|
|
12666
|
-
const anchor = impls[0];
|
|
12939
|
+
const crossSubsystem = component.subsystem !== target.subsystem;
|
|
12940
|
+
const toFiles = crossSubsystem ? realization.filesIn(target.subsystem).filter((p) => isExact(p) && !fromFiles.includes(p)) : filesOf(targetId);
|
|
12941
|
+
if (toFiles.length === 0) continue;
|
|
12942
|
+
if (fromFiles.some((f) => toFiles.includes(f))) continue;
|
|
12943
|
+
if (graph.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
|
|
12944
|
+
const impls = mappedImplsOf(component.id);
|
|
12667
12945
|
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
12668
12946
|
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
12669
12947
|
const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
|
|
@@ -12671,8 +12949,8 @@ var init_dependency_conformance = __esm({
|
|
|
12671
12949
|
ctx.addIssue(
|
|
12672
12950
|
"warning",
|
|
12673
12951
|
"UNREALIZED_DEPENDENCY",
|
|
12674
|
-
`Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${
|
|
12675
|
-
|
|
12952
|
+
`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.`,
|
|
12953
|
+
impls[0]?.id ?? component.id,
|
|
12676
12954
|
draft
|
|
12677
12955
|
);
|
|
12678
12956
|
}
|
|
@@ -12698,50 +12976,24 @@ var init_integration_conformance = __esm({
|
|
|
12698
12976
|
{ code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
|
|
12699
12977
|
],
|
|
12700
12978
|
check(ctx) {
|
|
12701
|
-
const
|
|
12702
|
-
const
|
|
12703
|
-
const
|
|
12704
|
-
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
12705
|
-
const implsByComponent = /* @__PURE__ */ new Map();
|
|
12706
|
-
const adopted = /* @__PURE__ */ new Set();
|
|
12979
|
+
const code = ctx.codeIndex();
|
|
12980
|
+
const realization = ctx.realizationIndex();
|
|
12981
|
+
const graph = ctx.importGraph();
|
|
12707
12982
|
const exists = (p) => {
|
|
12708
|
-
const status2 =
|
|
12983
|
+
const status2 = code.factsAt(p)?.status;
|
|
12709
12984
|
return status2 === "analyzed" || status2 === "unreadable";
|
|
12710
12985
|
};
|
|
12986
|
+
const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
|
|
12987
|
+
const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
|
|
12988
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
12711
12989
|
for (const impl of ctx.implementations) {
|
|
12990
|
+
if (!impl.simPath) continue;
|
|
12712
12991
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12713
12992
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
12714
12993
|
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
const p = pathKey(file);
|
|
12719
|
-
if (!exists(p)) continue;
|
|
12720
|
-
if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
|
|
12721
|
-
filesByComponent.get(comp.id).add(p);
|
|
12722
|
-
if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
|
|
12723
|
-
filesBySubsystem.get(comp.subsystem).add(p);
|
|
12724
|
-
}
|
|
12725
|
-
if (impl.simPath) adopted.add(comp.subsystem);
|
|
12726
|
-
}
|
|
12727
|
-
const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
|
|
12728
|
-
const reachFrom = (start2) => {
|
|
12729
|
-
const seen = /* @__PURE__ */ new Set([start2]);
|
|
12730
|
-
const stack = [start2];
|
|
12731
|
-
while (stack.length) {
|
|
12732
|
-
const from = stack.pop();
|
|
12733
|
-
const facts = factsByPath.get(from);
|
|
12734
|
-
if (!facts || facts.status !== "analyzed") continue;
|
|
12735
|
-
for (const spec of [...facts.imports, ...facts.reexports]) {
|
|
12736
|
-
const to = resolveImport(from, spec, allPaths);
|
|
12737
|
-
if (to && !seen.has(to)) {
|
|
12738
|
-
seen.add(to);
|
|
12739
|
-
stack.push(to);
|
|
12740
|
-
}
|
|
12741
|
-
}
|
|
12742
|
-
}
|
|
12743
|
-
return seen;
|
|
12744
|
-
};
|
|
12994
|
+
adopted.add(comp.subsystem);
|
|
12995
|
+
}
|
|
12996
|
+
const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
|
|
12745
12997
|
for (const impl of ctx.implementations) {
|
|
12746
12998
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12747
12999
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
@@ -12761,7 +13013,7 @@ var init_integration_conformance = __esm({
|
|
|
12761
13013
|
continue;
|
|
12762
13014
|
}
|
|
12763
13015
|
const simPath = pathKey(impl.simPath);
|
|
12764
|
-
const facts =
|
|
13016
|
+
const facts = code.factsAt(simPath);
|
|
12765
13017
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
12766
13018
|
if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
|
|
12767
13019
|
const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
|
|
@@ -12775,19 +13027,19 @@ var init_integration_conformance = __esm({
|
|
|
12775
13027
|
continue;
|
|
12776
13028
|
}
|
|
12777
13029
|
if (facts.analysisGrade !== "exact") continue;
|
|
12778
|
-
const reach = reachFrom(simPath);
|
|
12779
|
-
const ownFiles =
|
|
13030
|
+
const reach = graph.reachFrom(simPath);
|
|
13031
|
+
const ownFiles = modulesOf(comp.id);
|
|
12780
13032
|
const missing = [];
|
|
12781
|
-
if (ownFiles.
|
|
12782
|
-
missing.push(`the component's own module (${
|
|
13033
|
+
if (ownFiles.length > 0 && !ownFiles.some((f) => reach.has(f))) {
|
|
13034
|
+
missing.push(`the component's own module (${ownFiles.join(" | ")})`);
|
|
12783
13035
|
}
|
|
12784
13036
|
for (const dep of deps) {
|
|
12785
13037
|
if (isTechBoundary(dep)) continue;
|
|
12786
13038
|
const depComp = ctx.componentMap.get(dep);
|
|
12787
|
-
const depFiles = depComp.subsystem !== comp.subsystem ?
|
|
12788
|
-
if (
|
|
12789
|
-
if (!
|
|
12790
|
-
const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${
|
|
13039
|
+
const depFiles = depComp.subsystem !== comp.subsystem ? modulesIn(depComp.subsystem) : modulesOf(dep);
|
|
13040
|
+
if (depFiles.length === 0) continue;
|
|
13041
|
+
if (!depFiles.some((f) => reach.has(f))) {
|
|
13042
|
+
const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${depFiles.join(" | ")})`;
|
|
12791
13043
|
missing.push(label);
|
|
12792
13044
|
}
|
|
12793
13045
|
}
|
|
@@ -13962,8 +14214,15 @@ var init_repository = __esm({
|
|
|
13962
14214
|
init_retired_stereotypes();
|
|
13963
14215
|
init_portal_endpoints();
|
|
13964
14216
|
init_portal_call_auth();
|
|
13965
|
-
|
|
13966
|
-
|
|
14217
|
+
init_subsystem_boundary_dependencies();
|
|
14218
|
+
init_logic_dependency_class();
|
|
14219
|
+
init_data_block_dependencies();
|
|
14220
|
+
init_entrypoint_dependencies();
|
|
14221
|
+
init_portal_write_shortcut();
|
|
14222
|
+
init_pattern_membership();
|
|
14223
|
+
init_pattern_containment();
|
|
14224
|
+
init_unowned_blocks();
|
|
14225
|
+
init_member_visibility();
|
|
13967
14226
|
init_facade_forwarding();
|
|
13968
14227
|
init_profile_registration();
|
|
13969
14228
|
init_profile_stereotype_fencing();
|
|
@@ -14040,8 +14299,24 @@ var init_repository = __esm({
|
|
|
14040
14299
|
// Cross-call auth: a narrative call into an authed Portal must name its
|
|
14041
14300
|
// credential source (rides with the portal family).
|
|
14042
14301
|
portalCallAuthRule,
|
|
14043
|
-
|
|
14044
|
-
|
|
14302
|
+
// Dependencies in five questions, one owner each: where an edge is
|
|
14303
|
+
// allowed to LAND (the boundary, and what an unresolved id means), then
|
|
14304
|
+
// the intra-subsystem matrix by the layer that answers for it — an
|
|
14305
|
+
// Orchestrator's declared class, the data blocks, the entry points and
|
|
14306
|
+
// the process layer — and finally the Portal read-face guard, the one
|
|
14307
|
+
// that reads narratives and dispatch tables rather than dependsOn.
|
|
14308
|
+
subsystemBoundaryDepsRule,
|
|
14309
|
+
logicDependencyClassRule,
|
|
14310
|
+
dataBlockDepsRule,
|
|
14311
|
+
entrypointDepsRule,
|
|
14312
|
+
portalWriteShortcutRule,
|
|
14313
|
+
// Patterns in four questions: who may own and what a claim must name,
|
|
14314
|
+
// what each pattern must contain, which data blocks are left standing
|
|
14315
|
+
// alone, and who may see a private member.
|
|
14316
|
+
patternMembershipRule,
|
|
14317
|
+
patternContainmentRule,
|
|
14318
|
+
unownedBlocksRule,
|
|
14319
|
+
memberVisibilityRule,
|
|
14045
14320
|
// Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
|
|
14046
14321
|
facadeForwardingRule,
|
|
14047
14322
|
// Profiles in three questions, three owners: is the name real (the
|
|
@@ -14808,11 +15083,15 @@ function buildRuleContext(opts) {
|
|
|
14808
15083
|
}
|
|
14809
15084
|
return { kind: "unresolved" };
|
|
14810
15085
|
};
|
|
15086
|
+
const contractMethods = /* @__PURE__ */ new Map();
|
|
14811
15087
|
const interfaceMethodsOf = (compId) => {
|
|
15088
|
+
const cached = contractMethods.get(compId);
|
|
15089
|
+
if (cached) return cached;
|
|
14812
15090
|
const out = [];
|
|
14813
15091
|
for (const intf of interfacesByComponent.get(compId) ?? []) {
|
|
14814
15092
|
out.push(...intf.methods);
|
|
14815
15093
|
}
|
|
15094
|
+
contractMethods.set(compId, out);
|
|
14816
15095
|
return out;
|
|
14817
15096
|
};
|
|
14818
15097
|
const profileDefFor = (subsystemId) => {
|
|
@@ -14908,7 +15187,26 @@ function buildRuleContext(opts) {
|
|
|
14908
15187
|
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
14909
15188
|
});
|
|
14910
15189
|
};
|
|
14911
|
-
|
|
15190
|
+
let codeIndexMemo;
|
|
15191
|
+
const codeIndex = () => codeIndexMemo ??= buildCodeIndex(ctx.codeModel);
|
|
15192
|
+
let realizationMemo;
|
|
15193
|
+
const realizationIndex = () => realizationMemo ??= buildRealizationIndex(ctx);
|
|
15194
|
+
const importGraphs = /* @__PURE__ */ new Map();
|
|
15195
|
+
const importGraph = (paths) => {
|
|
15196
|
+
const universe = paths ?? codeIndex().paths;
|
|
15197
|
+
const key = `${universe.size}\0${[...universe].join("\0")}`;
|
|
15198
|
+
let graph = importGraphs.get(key);
|
|
15199
|
+
if (!graph) {
|
|
15200
|
+
graph = buildImportGraph(codeIndex(), universe);
|
|
15201
|
+
importGraphs.set(key, graph);
|
|
15202
|
+
}
|
|
15203
|
+
return graph;
|
|
15204
|
+
};
|
|
15205
|
+
let ownershipMemo;
|
|
15206
|
+
const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
|
|
15207
|
+
let dependencyEdgesMemo;
|
|
15208
|
+
const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
|
|
15209
|
+
const ctx = {
|
|
14912
15210
|
system,
|
|
14913
15211
|
subsystems,
|
|
14914
15212
|
components,
|
|
@@ -14937,6 +15235,11 @@ function buildRuleContext(opts) {
|
|
|
14937
15235
|
isCollapsedCrossTreeRef,
|
|
14938
15236
|
resolveSurfaceRef,
|
|
14939
15237
|
interfaceMethodsOf,
|
|
15238
|
+
codeIndex,
|
|
15239
|
+
realizationIndex,
|
|
15240
|
+
importGraph,
|
|
15241
|
+
ownershipIndex,
|
|
15242
|
+
dependencyEdges,
|
|
14940
15243
|
complexityConfigFor,
|
|
14941
15244
|
documentationConfigFor,
|
|
14942
15245
|
namingConfigFor,
|
|
@@ -14951,6 +15254,7 @@ function buildRuleContext(opts) {
|
|
|
14951
15254
|
knownIssueCodes: opts.knownIssueCodes,
|
|
14952
15255
|
addIssue
|
|
14953
15256
|
};
|
|
15257
|
+
return ctx;
|
|
14954
15258
|
}
|
|
14955
15259
|
var DEPTH_RANK, DEPTH_GATED_CODES, COMPLETENESS_RULES;
|
|
14956
15260
|
var init_rules = __esm({
|
|
@@ -14959,6 +15263,7 @@ var init_rules = __esm({
|
|
|
14959
15263
|
init_models();
|
|
14960
15264
|
init_extensions();
|
|
14961
15265
|
init_types();
|
|
15266
|
+
init_read_model();
|
|
14962
15267
|
init_repository();
|
|
14963
15268
|
init_lint_allows();
|
|
14964
15269
|
init_source_analysis();
|