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