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