@wairon/cli 5.1.1-dev.24 → 5.1.1-dev.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1552 -923
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1553 -922
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.26";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -1710,7 +1710,7 @@ var init_specs = __esm({
|
|
|
1710
1710
|
* entity's write methods must carry (UNASSERTED_INVARIANT otherwise).
|
|
1711
1711
|
*/
|
|
1712
1712
|
assertsInvariants: import_zod7.z.array(import_zod7.z.string()).optional(),
|
|
1713
|
-
// --- flow config (per type; validated by
|
|
1713
|
+
// --- flow config (per type; validated by narrative-step-config) -----------
|
|
1714
1714
|
condition: import_zod7.z.string().optional(),
|
|
1715
1715
|
// branch; loop (while/doWhile)
|
|
1716
1716
|
onTrueStep: import_zod7.z.number().int().positive().optional(),
|
|
@@ -1811,7 +1811,7 @@ var init_specs = __esm({
|
|
|
1811
1811
|
methods: import_zod7.z.array(MethodImplementationSchema).default([]),
|
|
1812
1812
|
/** Spec-level narrative detail default for all methods (each may override). */
|
|
1813
1813
|
detail: NarrativeDetailSchema.optional(),
|
|
1814
|
-
/** Spec-level
|
|
1814
|
+
/** Spec-level conformance tier default (each method may override). */
|
|
1815
1815
|
conformance: ConformanceTierSchema.optional(),
|
|
1816
1816
|
/** Per-spec lint suppressions (see LintConfigSchema). */
|
|
1817
1817
|
lint: LintConfigSchema.optional(),
|
|
@@ -2242,6 +2242,149 @@ var init_step_graph = __esm({
|
|
|
2242
2242
|
}
|
|
2243
2243
|
});
|
|
2244
2244
|
|
|
2245
|
+
// src/models/step-config.ts
|
|
2246
|
+
function flowConfigOn(step) {
|
|
2247
|
+
return FLOW_FIELDS.filter((f) => {
|
|
2248
|
+
const v = step[f];
|
|
2249
|
+
if (v === void 0) return false;
|
|
2250
|
+
if (Array.isArray(v) && v.length === 0) return false;
|
|
2251
|
+
return true;
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
function stepConfigVerdict(method2) {
|
|
2255
|
+
const steps = method2.narrative ?? [];
|
|
2256
|
+
const problems = [];
|
|
2257
|
+
let sound = true;
|
|
2258
|
+
const malformed = (stepNumber, detail) => {
|
|
2259
|
+
sound = false;
|
|
2260
|
+
problems.push({ stepNumber, code: "MALFORMED_FLOW_STEP", detail });
|
|
2261
|
+
};
|
|
2262
|
+
const labelled = /* @__PURE__ */ new Map();
|
|
2263
|
+
for (const step of steps) {
|
|
2264
|
+
const label = step.label;
|
|
2265
|
+
if (!label) continue;
|
|
2266
|
+
const first = labelled.get(label);
|
|
2267
|
+
if (first !== void 0) {
|
|
2268
|
+
problems.push({
|
|
2269
|
+
stepNumber: step.stepNumber,
|
|
2270
|
+
code: "DUPLICATE_STEP_LABEL",
|
|
2271
|
+
detail: `steps ${first} and ${step.stepNumber} share the label "${label}". A label anchors one step for later deltas to address; two steps holding it makes every symbolic reference ambiguous and blocks all further edits to this implementation. Rename one.`
|
|
2272
|
+
});
|
|
2273
|
+
} else {
|
|
2274
|
+
labelled.set(label, step.stepNumber);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
const byNum = /* @__PURE__ */ new Map();
|
|
2278
|
+
for (const s of steps) {
|
|
2279
|
+
if (byNum.has(s.stepNumber)) malformed(s.stepNumber, `duplicate stepNumber ${s.stepNumber} \u2014 jump targets would be ambiguous.`);
|
|
2280
|
+
byNum.set(s.stepNumber, s);
|
|
2281
|
+
}
|
|
2282
|
+
const nums = [...byNum.keys()].sort((a, b) => a - b);
|
|
2283
|
+
const indexOf = new Map(nums.map((n, i) => [n, i]));
|
|
2284
|
+
const nextOf = (n) => {
|
|
2285
|
+
const i = indexOf.get(n);
|
|
2286
|
+
return i !== void 0 && i + 1 < nums.length ? nums[i + 1] : void 0;
|
|
2287
|
+
};
|
|
2288
|
+
for (const s of steps) {
|
|
2289
|
+
if (s.detach && s.type !== "call" && s.type !== "dispatch") {
|
|
2290
|
+
malformed(s.stepNumber, `step ${s.stepNumber} (${s.type}) carries "detach" \u2014 fire-and-forget applies to call/dispatch steps only.`);
|
|
2291
|
+
}
|
|
2292
|
+
switch (s.type) {
|
|
2293
|
+
case "local":
|
|
2294
|
+
case "call":
|
|
2295
|
+
case "register":
|
|
2296
|
+
case "dispatch": {
|
|
2297
|
+
const extra = flowConfigOn(s);
|
|
2298
|
+
if (extra.length) malformed(s.stepNumber, `step ${s.stepNumber} (${s.type}) carries flow config (${extra.join(", ")}) \u2014 use a flow step type instead.`);
|
|
2299
|
+
break;
|
|
2300
|
+
}
|
|
2301
|
+
case "branch":
|
|
2302
|
+
if (!s.condition) malformed(s.stepNumber, `branch step ${s.stepNumber} requires "condition".`);
|
|
2303
|
+
if (s.onFalseStep === void 0) malformed(s.stepNumber, `branch step ${s.stepNumber} requires "onFalseStep" (true continues at onTrueStep or the next step).`);
|
|
2304
|
+
break;
|
|
2305
|
+
case "switch":
|
|
2306
|
+
if (!s.cases || !s.cases.length) malformed(s.stepNumber, `switch step ${s.stepNumber} requires non-empty "cases".`);
|
|
2307
|
+
break;
|
|
2308
|
+
case "loop": {
|
|
2309
|
+
if (s.endStep === void 0) malformed(s.stepNumber, `loop step ${s.stepNumber} requires "endStep" (last step of the body).`);
|
|
2310
|
+
else if (s.endStep <= s.stepNumber) malformed(s.stepNumber, `loop step ${s.stepNumber} "endStep" (${s.endStep}) must lie beyond the header.`);
|
|
2311
|
+
const kind = s.loopKind ?? (s.over ? "forEach" : "while");
|
|
2312
|
+
if ((kind === "while" || kind === "doWhile") && !s.condition) malformed(s.stepNumber, `${kind} loop step ${s.stepNumber} requires "condition".`);
|
|
2313
|
+
if ((kind === "forEach" || kind === "for") && !s.over) malformed(s.stepNumber, `${kind} loop step ${s.stepNumber} requires "over" (the iteration source).`);
|
|
2314
|
+
break;
|
|
2315
|
+
}
|
|
2316
|
+
case "try":
|
|
2317
|
+
if (s.endStep === void 0) malformed(s.stepNumber, `try step ${s.stepNumber} requires "endStep" (last step of the guarded body).`);
|
|
2318
|
+
else if (s.endStep <= s.stepNumber) malformed(s.stepNumber, `try step ${s.stepNumber} "endStep" (${s.endStep}) must lie beyond the header.`);
|
|
2319
|
+
if ((!s.catches || !s.catches.length) && s.finallyStep === void 0) malformed(s.stepNumber, `try step ${s.stepNumber} requires "catches" and/or "finallyStep" \u2014 a guard that handles nothing guards nothing.`);
|
|
2320
|
+
break;
|
|
2321
|
+
case "parallel": {
|
|
2322
|
+
if (s.endStep === void 0) malformed(s.stepNumber, `parallel step ${s.stepNumber} requires "endStep" (last step of the fan-out body).`);
|
|
2323
|
+
else if (s.endStep <= s.stepNumber) malformed(s.stepNumber, `parallel step ${s.stepNumber} "endStep" (${s.endStep}) must lie beyond the header.`);
|
|
2324
|
+
const entries = (s.branches ?? []).map((b) => b.step);
|
|
2325
|
+
if (entries.length < 2) {
|
|
2326
|
+
malformed(s.stepNumber, `parallel step ${s.stepNumber} requires "branches" with at least two arms \u2014 a single arm is sequential flow.`);
|
|
2327
|
+
} else {
|
|
2328
|
+
const sorted = [...entries].sort((a, b) => a - b);
|
|
2329
|
+
if (entries.some((e, i) => e !== sorted[i])) malformed(s.stepNumber, `parallel step ${s.stepNumber} "branches" must list arm entries in ascending order \u2014 arms are contiguous sub-regions of the body.`);
|
|
2330
|
+
if (new Set(entries).size !== entries.length) malformed(s.stepNumber, `parallel step ${s.stepNumber} "branches" lists the same entry step twice.`);
|
|
2331
|
+
const bodyStart = nextOf(s.stepNumber);
|
|
2332
|
+
if (bodyStart !== void 0 && sorted[0] !== bodyStart) malformed(s.stepNumber, `parallel step ${s.stepNumber}: the first arm must start at the body's first step (${bodyStart}), got ${sorted[0]} \u2014 steps before the first arm would belong to no arm.`);
|
|
2333
|
+
if (s.endStep !== void 0 && sorted.some((e) => e > s.endStep)) malformed(s.stepNumber, `parallel step ${s.stepNumber}: every arm entry must lie within the body (..${s.endStep}).`);
|
|
2334
|
+
}
|
|
2335
|
+
break;
|
|
2336
|
+
}
|
|
2337
|
+
case "jump":
|
|
2338
|
+
if (s.toStep === void 0) malformed(s.stepNumber, `jump step ${s.stepNumber} requires "toStep".`);
|
|
2339
|
+
break;
|
|
2340
|
+
}
|
|
2341
|
+
for (const [field, target] of jumpFieldsOf(s)) {
|
|
2342
|
+
if (byNum.has(target)) continue;
|
|
2343
|
+
sound = false;
|
|
2344
|
+
problems.push({
|
|
2345
|
+
stepNumber: s.stepNumber,
|
|
2346
|
+
code: "INVALID_STEP_JUMP",
|
|
2347
|
+
detail: `step ${s.stepNumber} "${field}" targets step ${target}, which does not exist in this narrative.`
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
return { problems, sound };
|
|
2352
|
+
}
|
|
2353
|
+
function jumpFieldsOf(s) {
|
|
2354
|
+
const fields = [];
|
|
2355
|
+
if (s.onTrueStep !== void 0) fields.push(["onTrueStep", s.onTrueStep]);
|
|
2356
|
+
if (s.onFalseStep !== void 0) fields.push(["onFalseStep", s.onFalseStep]);
|
|
2357
|
+
if (s.defaultStep !== void 0) fields.push(["defaultStep", s.defaultStep]);
|
|
2358
|
+
if (s.endStep !== void 0) fields.push(["endStep", s.endStep]);
|
|
2359
|
+
if (s.finallyStep !== void 0) fields.push(["finallyStep", s.finallyStep]);
|
|
2360
|
+
if (s.toStep !== void 0) fields.push(["toStep", s.toStep]);
|
|
2361
|
+
(s.cases ?? []).forEach((c, i) => fields.push([`cases[${i}].step`, c.step]));
|
|
2362
|
+
(s.catches ?? []).forEach((c, i) => fields.push([`catches[${i}].step`, c.step]));
|
|
2363
|
+
(s.branches ?? []).forEach((b, i) => fields.push([`branches[${i}].step`, b.step]));
|
|
2364
|
+
return fields;
|
|
2365
|
+
}
|
|
2366
|
+
var FLOW_FIELDS;
|
|
2367
|
+
var init_step_config = __esm({
|
|
2368
|
+
"src/models/step-config.ts"() {
|
|
2369
|
+
"use strict";
|
|
2370
|
+
FLOW_FIELDS = [
|
|
2371
|
+
"condition",
|
|
2372
|
+
"onTrueStep",
|
|
2373
|
+
"onFalseStep",
|
|
2374
|
+
"on",
|
|
2375
|
+
"cases",
|
|
2376
|
+
"defaultStep",
|
|
2377
|
+
"loopKind",
|
|
2378
|
+
"over",
|
|
2379
|
+
"endStep",
|
|
2380
|
+
"catches",
|
|
2381
|
+
"finallyStep",
|
|
2382
|
+
"branches",
|
|
2383
|
+
"toStep"
|
|
2384
|
+
];
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2245
2388
|
// src/models/code-model.ts
|
|
2246
2389
|
function pathKey(sourcePath) {
|
|
2247
2390
|
return sourcePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -2302,6 +2445,7 @@ var init_models = __esm({
|
|
|
2302
2445
|
init_specs();
|
|
2303
2446
|
init_type_references();
|
|
2304
2447
|
init_step_graph();
|
|
2448
|
+
init_step_config();
|
|
2305
2449
|
init_code_model();
|
|
2306
2450
|
init_surface_references();
|
|
2307
2451
|
}
|
|
@@ -8604,6 +8748,209 @@ var init_types = __esm({
|
|
|
8604
8748
|
}
|
|
8605
8749
|
});
|
|
8606
8750
|
|
|
8751
|
+
// src/core/rules/read-model.ts
|
|
8752
|
+
function appendUnique(map, key, value, same) {
|
|
8753
|
+
const list2 = map.get(key);
|
|
8754
|
+
if (!list2) {
|
|
8755
|
+
map.set(key, [value]);
|
|
8756
|
+
return;
|
|
8757
|
+
}
|
|
8758
|
+
if (same ? list2.some((v) => same(v, value)) : list2.includes(value)) return;
|
|
8759
|
+
list2.push(value);
|
|
8760
|
+
}
|
|
8761
|
+
function buildCodeIndex(model) {
|
|
8762
|
+
const facts = /* @__PURE__ */ new Map();
|
|
8763
|
+
const paths = /* @__PURE__ */ new Set();
|
|
8764
|
+
const exactPaths = /* @__PURE__ */ new Set();
|
|
8765
|
+
for (const f of model.files) {
|
|
8766
|
+
const key = pathKey(f.path);
|
|
8767
|
+
facts.set(key, f);
|
|
8768
|
+
paths.add(key);
|
|
8769
|
+
if (f.status === "analyzed" && f.analysisGrade === "exact") exactPaths.add(key);
|
|
8770
|
+
}
|
|
8771
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
8772
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
8773
|
+
const findingAnchors = /* @__PURE__ */ new Map();
|
|
8774
|
+
const namesAt = (cache2, path70, make) => {
|
|
8775
|
+
const cached = cache2.get(path70);
|
|
8776
|
+
if (cached) return cached;
|
|
8777
|
+
const f = facts.get(path70);
|
|
8778
|
+
if (!f) return NO_NAMES;
|
|
8779
|
+
const built = make(f);
|
|
8780
|
+
cache2.set(path70, built);
|
|
8781
|
+
return built;
|
|
8782
|
+
};
|
|
8783
|
+
return {
|
|
8784
|
+
paths,
|
|
8785
|
+
exactPaths,
|
|
8786
|
+
factsAt: (path70) => facts.get(pathKey(path70)),
|
|
8787
|
+
declarationsAt: (path70) => namesAt(declarations, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames])),
|
|
8788
|
+
anchorsAt: (path70) => namesAt(anchors, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
8789
|
+
findingAnchorsAt: (path70) => namesAt(findingAnchors, pathKey(path70), (f) => new Set(f.anchoredNames))
|
|
8790
|
+
};
|
|
8791
|
+
}
|
|
8792
|
+
function buildRealizationIndex(ctx) {
|
|
8793
|
+
const implsByComponent = /* @__PURE__ */ new Map();
|
|
8794
|
+
const implsByPath = /* @__PURE__ */ new Map();
|
|
8795
|
+
const filesByComponent = /* @__PURE__ */ new Map();
|
|
8796
|
+
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
8797
|
+
const componentsByPath = /* @__PURE__ */ new Map();
|
|
8798
|
+
const paths = [];
|
|
8799
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
8800
|
+
for (const impl of ctx.implementations) {
|
|
8801
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
8802
|
+
if (!contract) continue;
|
|
8803
|
+
const component = ctx.componentMap.get(contract.component);
|
|
8804
|
+
if (!component) continue;
|
|
8805
|
+
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
8806
|
+
appendUnique(implsByComponent, component.id, impl);
|
|
8807
|
+
for (const file of implementationSourceFiles(impl)) {
|
|
8808
|
+
const p = pathKey(file);
|
|
8809
|
+
if (!seenPaths.has(p)) {
|
|
8810
|
+
seenPaths.add(p);
|
|
8811
|
+
paths.push(p);
|
|
8812
|
+
}
|
|
8813
|
+
appendUnique(implsByPath, p, impl);
|
|
8814
|
+
appendUnique(filesByComponent, component.id, p);
|
|
8815
|
+
appendUnique(filesBySubsystem, component.subsystem, p);
|
|
8816
|
+
appendUnique(componentsByPath, p, component, (a, b) => a.id === b.id);
|
|
8817
|
+
}
|
|
8818
|
+
}
|
|
8819
|
+
const NONE_IMPL = [];
|
|
8820
|
+
const NONE_COMP = [];
|
|
8821
|
+
const NONE_PATH = [];
|
|
8822
|
+
return {
|
|
8823
|
+
paths,
|
|
8824
|
+
implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
|
|
8825
|
+
implementationsAt: (path70) => implsByPath.get(pathKey(path70)) ?? NONE_IMPL,
|
|
8826
|
+
filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
|
|
8827
|
+
filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
|
|
8828
|
+
componentsAt: (path70) => componentsByPath.get(pathKey(path70)) ?? NONE_COMP
|
|
8829
|
+
};
|
|
8830
|
+
}
|
|
8831
|
+
function buildImportGraph(index, universe) {
|
|
8832
|
+
const imports = /* @__PURE__ */ new Map();
|
|
8833
|
+
const traces = /* @__PURE__ */ new Map();
|
|
8834
|
+
const resolveAll = (from, specifiers, into) => {
|
|
8835
|
+
for (const specifier of specifiers) {
|
|
8836
|
+
const to = resolveImport(from, specifier, universe);
|
|
8837
|
+
if (to && to !== from) into.add(to);
|
|
8838
|
+
}
|
|
8839
|
+
};
|
|
8840
|
+
const importsOf = (path70) => {
|
|
8841
|
+
const cached = imports.get(path70);
|
|
8842
|
+
if (cached) return cached;
|
|
8843
|
+
const facts = index.factsAt(path70);
|
|
8844
|
+
const out = /* @__PURE__ */ new Set();
|
|
8845
|
+
if (facts?.status === "analyzed") resolveAll(path70, facts.imports, out);
|
|
8846
|
+
imports.set(path70, out);
|
|
8847
|
+
return out;
|
|
8848
|
+
};
|
|
8849
|
+
const tracesOf = (path70) => {
|
|
8850
|
+
const cached = traces.get(path70);
|
|
8851
|
+
if (cached) return cached;
|
|
8852
|
+
const facts = index.factsAt(path70);
|
|
8853
|
+
const out = new Set(importsOf(path70));
|
|
8854
|
+
if (facts?.status === "analyzed") resolveAll(path70, facts.reexports, out);
|
|
8855
|
+
traces.set(path70, out);
|
|
8856
|
+
return out;
|
|
8857
|
+
};
|
|
8858
|
+
return {
|
|
8859
|
+
paths: [...universe],
|
|
8860
|
+
importsOf,
|
|
8861
|
+
tracesOf,
|
|
8862
|
+
connects: (from, to, allowReverse) => {
|
|
8863
|
+
for (const f of from) {
|
|
8864
|
+
const forward = tracesOf(f);
|
|
8865
|
+
for (const t of to) if (forward.has(t)) return true;
|
|
8866
|
+
}
|
|
8867
|
+
if (allowReverse) {
|
|
8868
|
+
for (const t of to) {
|
|
8869
|
+
const reverse = tracesOf(t);
|
|
8870
|
+
for (const f of from) if (reverse.has(f)) return true;
|
|
8871
|
+
}
|
|
8872
|
+
}
|
|
8873
|
+
return false;
|
|
8874
|
+
},
|
|
8875
|
+
reachFrom: (start2) => {
|
|
8876
|
+
const seen = /* @__PURE__ */ new Set([start2]);
|
|
8877
|
+
const stack = [start2];
|
|
8878
|
+
while (stack.length) {
|
|
8879
|
+
const from = stack.pop();
|
|
8880
|
+
for (const to of tracesOf(from)) {
|
|
8881
|
+
if (seen.has(to)) continue;
|
|
8882
|
+
seen.add(to);
|
|
8883
|
+
stack.push(to);
|
|
8884
|
+
}
|
|
8885
|
+
}
|
|
8886
|
+
return seen;
|
|
8887
|
+
}
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
function buildOwnershipIndex(ctx) {
|
|
8891
|
+
const ownedBy = /* @__PURE__ */ new Map();
|
|
8892
|
+
for (const comp of ctx.components) {
|
|
8893
|
+
if (isRetired(comp) || !isPattern(comp)) continue;
|
|
8894
|
+
for (const memberId of comp.owns) {
|
|
8895
|
+
const member = ctx.componentMap.get(memberId);
|
|
8896
|
+
if (!member || isPattern(member)) continue;
|
|
8897
|
+
if (!ownedBy.has(memberId)) ownedBy.set(memberId, comp.id);
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
return {
|
|
8901
|
+
ownedMembers: new Set(ownedBy.keys()),
|
|
8902
|
+
ownerOf: (memberId) => ownedBy.get(memberId)
|
|
8903
|
+
};
|
|
8904
|
+
}
|
|
8905
|
+
function buildDependencyEdges(ctx) {
|
|
8906
|
+
const all = [];
|
|
8907
|
+
const matrix = [];
|
|
8908
|
+
for (const from of ctx.components) {
|
|
8909
|
+
const fromDraft = ctx.isComponentDraft(from.id);
|
|
8910
|
+
const profile = ctx.ext.profiles[ctx.getComponentProfile(from.id)];
|
|
8911
|
+
for (const ref of from.dependsOn) {
|
|
8912
|
+
const to = ctx.componentMap.get(ref);
|
|
8913
|
+
if (!to) {
|
|
8914
|
+
all.push(offTreeEdge(ctx, from, ref, fromDraft));
|
|
8915
|
+
continue;
|
|
8916
|
+
}
|
|
8917
|
+
const edge = {
|
|
8918
|
+
from,
|
|
8919
|
+
ref,
|
|
8920
|
+
to,
|
|
8921
|
+
reach: to.subsystem === from.subsystem ? "internal" : "cross-subsystem",
|
|
8922
|
+
retired: isRetired(from) || isRetired(to),
|
|
8923
|
+
draftContext: fromDraft || ctx.isComponentDraft(to.id),
|
|
8924
|
+
licensed: profile?.allowedEdges?.some(
|
|
8925
|
+
(e) => e.from.includes(from.componentType) && e.to.includes(to.componentType)
|
|
8926
|
+
) ?? false
|
|
8927
|
+
};
|
|
8928
|
+
all.push(edge);
|
|
8929
|
+
if (edge.reach === "internal" && !edge.retired && !edge.licensed) matrix.push(edge);
|
|
8930
|
+
}
|
|
8931
|
+
}
|
|
8932
|
+
return { all, matrix };
|
|
8933
|
+
}
|
|
8934
|
+
function offTreeEdge(ctx, from, ref, fromDraft) {
|
|
8935
|
+
const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
|
|
8936
|
+
const external = ctx.isExternalNamespaceRef(ref);
|
|
8937
|
+
if (external || ctx.isCollapsedCrossTreeRef(ref, from.subsystem)) {
|
|
8938
|
+
const surface = ctx.resolveSurfaceRef(ref, from.subsystem);
|
|
8939
|
+
if (surface.kind === "ambiguous") return { ...base, reach: "ambiguous", surface };
|
|
8940
|
+
if (surface.kind === "resolved") return { ...base, reach: "surface", surface };
|
|
8941
|
+
if (external) return { ...base, reach: "unpinned" };
|
|
8942
|
+
}
|
|
8943
|
+
return { ...base, reach: "missing" };
|
|
8944
|
+
}
|
|
8945
|
+
var NO_NAMES;
|
|
8946
|
+
var init_read_model = __esm({
|
|
8947
|
+
"src/core/rules/read-model.ts"() {
|
|
8948
|
+
"use strict";
|
|
8949
|
+
init_models();
|
|
8950
|
+
NO_NAMES = /* @__PURE__ */ new Set();
|
|
8951
|
+
}
|
|
8952
|
+
});
|
|
8953
|
+
|
|
8607
8954
|
// src/core/rules/integrity/hierarchy-integrity.ts
|
|
8608
8955
|
var hierarchyRule;
|
|
8609
8956
|
var init_hierarchy_integrity = __esm({
|
|
@@ -8751,18 +9098,16 @@ var init_roundtrip_serialization = __esm({
|
|
|
8751
9098
|
}
|
|
8752
9099
|
});
|
|
8753
9100
|
|
|
8754
|
-
// src/core/rules/integrity/type-
|
|
8755
|
-
var
|
|
8756
|
-
var
|
|
8757
|
-
"src/core/rules/integrity/type-
|
|
9101
|
+
// src/core/rules/integrity/type-declarations.ts
|
|
9102
|
+
var typeDeclarationsRule;
|
|
9103
|
+
var init_type_declarations = __esm({
|
|
9104
|
+
"src/core/rules/integrity/type-declarations.ts"() {
|
|
8758
9105
|
"use strict";
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
description: "Type fields and interface method signatures may only reference builtins, in-scope generic parameters, or defined entity/value-object types. Types owned by a subsystem must reference an existing subsystem.",
|
|
9106
|
+
typeDeclarationsRule = {
|
|
9107
|
+
name: "type-declarations",
|
|
9108
|
+
description: "A type owned by a subsystem must reference an existing one, and a type declaring neither fields nor methods is a placeholder that can inform neither implementers nor the ERD.",
|
|
8763
9109
|
codes: [
|
|
8764
9110
|
{ code: "INVALID_SUBSYSTEM_REFERENCE", defaultSeverity: "error", summary: "Type references a non-existent owning subsystem" },
|
|
8765
|
-
{ code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" },
|
|
8766
9111
|
{ code: "HOLLOW_TYPE", defaultSeverity: "warning", summary: "Type declares no fields and no methods \u2014 a placeholder that informs neither implementers nor the ERD" }
|
|
8767
9112
|
],
|
|
8768
9113
|
check(ctx) {
|
|
@@ -8787,23 +9132,62 @@ var init_type_references2 = __esm({
|
|
|
8787
9132
|
isDraftCtx
|
|
8788
9133
|
);
|
|
8789
9134
|
}
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
9135
|
+
}
|
|
9136
|
+
}
|
|
9137
|
+
};
|
|
9138
|
+
}
|
|
9139
|
+
});
|
|
9140
|
+
|
|
9141
|
+
// src/core/rules/integrity/field-type-references.ts
|
|
9142
|
+
var fieldTypeReferencesRule;
|
|
9143
|
+
var init_field_type_references = __esm({
|
|
9144
|
+
"src/core/rules/integrity/field-type-references.ts"() {
|
|
9145
|
+
"use strict";
|
|
9146
|
+
init_models();
|
|
9147
|
+
fieldTypeReferencesRule = {
|
|
9148
|
+
name: "field-type-references",
|
|
9149
|
+
description: "Every type identifier a type field's declared type names must resolve to a builtin or a defined entity/value-object type \u2014 the type's own generic parameters are not field references.",
|
|
9150
|
+
codes: [
|
|
9151
|
+
{ code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
|
|
9152
|
+
],
|
|
9153
|
+
check(ctx) {
|
|
9154
|
+
for (const t of ctx.types) {
|
|
9155
|
+
const sub = ctx.subsystems.find((s) => s.id === t.subsystem);
|
|
9156
|
+
const isDraftCtx = sub ? sub.status === "draft" || sub.status === "design" : false;
|
|
9157
|
+
if (!t.fields) continue;
|
|
9158
|
+
for (const field of t.fields) {
|
|
9159
|
+
const refs = fieldTypeRefs(t, field.type);
|
|
9160
|
+
for (const ref of refs) {
|
|
9161
|
+
if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
|
|
9162
|
+
ctx.addIssue(
|
|
9163
|
+
"error",
|
|
9164
|
+
"UNDEFINED_TYPE_REFERENCE",
|
|
9165
|
+
`Type "${t.id}" field "${field.name}" references undefined type "${ref}" in "${field.type}".`,
|
|
9166
|
+
t.id,
|
|
9167
|
+
isDraftCtx
|
|
9168
|
+
);
|
|
8803
9169
|
}
|
|
8804
9170
|
}
|
|
8805
9171
|
}
|
|
8806
9172
|
}
|
|
9173
|
+
}
|
|
9174
|
+
};
|
|
9175
|
+
}
|
|
9176
|
+
});
|
|
9177
|
+
|
|
9178
|
+
// src/core/rules/integrity/signature-type-references.ts
|
|
9179
|
+
var signatureTypeReferencesRule;
|
|
9180
|
+
var init_signature_type_references = __esm({
|
|
9181
|
+
"src/core/rules/integrity/signature-type-references.ts"() {
|
|
9182
|
+
"use strict";
|
|
9183
|
+
init_models();
|
|
9184
|
+
signatureTypeReferencesRule = {
|
|
9185
|
+
name: "signature-type-references",
|
|
9186
|
+
description: "Every type identifier an interface method signature names must resolve to a builtin, a generic parameter in scope on the interface or the method, or a defined entity/value-object type.",
|
|
9187
|
+
codes: [
|
|
9188
|
+
{ code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
|
|
9189
|
+
],
|
|
9190
|
+
check(ctx) {
|
|
8807
9191
|
for (const intf of ctx.interfaces) {
|
|
8808
9192
|
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
8809
9193
|
const interfaceGenerics = new Set(
|
|
@@ -8959,27 +9343,17 @@ var init_lint_allows = __esm({
|
|
|
8959
9343
|
}
|
|
8960
9344
|
});
|
|
8961
9345
|
|
|
8962
|
-
// src/core/rules/narrative/contract-symmetry
|
|
8963
|
-
var
|
|
8964
|
-
var
|
|
8965
|
-
"src/core/rules/narrative/contract-symmetry
|
|
9346
|
+
// src/core/rules/narrative/contract-symmetry.ts
|
|
9347
|
+
var contractSymmetryRule;
|
|
9348
|
+
var init_contract_symmetry = __esm({
|
|
9349
|
+
"src/core/rules/narrative/contract-symmetry.ts"() {
|
|
8966
9350
|
"use strict";
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
description: "Implementations mirror their contract method-for-method. Narrative call steps (and register handoffs, which share the identical target shape) must name an existing component and method, the caller must declare the dependency, and asserted semantic guarantees must be backed by the target contract.",
|
|
9351
|
+
contractSymmetryRule = {
|
|
9352
|
+
name: "contract-symmetry",
|
|
9353
|
+
description: "Implementations mirror their contract method-for-method: every method an implementation declares is defined on the contract it realizes, and every contract method has an implementation.",
|
|
8971
9354
|
codes: [
|
|
8972
9355
|
{ code: "UNEXPECTED_IMPLEMENTATION_METHOD", defaultSeverity: "error", summary: "Implementation method not present on the contract" },
|
|
8973
|
-
{ code: "MISSING_IMPLEMENTATION_METHOD", defaultSeverity: "error", summary: "Contract method missing from the implementation" }
|
|
8974
|
-
{ code: "MISSING_TARGET_COMPONENT", defaultSeverity: "error", summary: "Call/register step missing targetComponent" },
|
|
8975
|
-
{ code: "MISSING_TARGET_METHOD", defaultSeverity: "error", summary: "Call/register step missing targetMethod" },
|
|
8976
|
-
{ code: "INVALID_TARGET_COMPONENT_REFERENCE", defaultSeverity: "error", summary: "Call/register step targets a non-existent component" },
|
|
8977
|
-
{ code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree reference (super::/:: form) with no surface snapshot covering it \u2014 only the parent project can verify it" },
|
|
8978
|
-
{ code: "SURFACE_REF_NOT_EXPOSED", defaultSeverity: "error", summary: "Cross-tree reference resolves to a surface snapshot that does not expose the called method/capability" },
|
|
8979
|
-
{ code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree call/dispatch/register target matched by surface snapshots of several providers with different contracts" },
|
|
8980
|
-
{ code: "UNDECLARED_DEPENDENCY_CALL", defaultSeverity: "error", summary: "Call step targets a component the caller does not depend on or own" },
|
|
8981
|
-
{ code: "INVALID_TARGET_METHOD_REFERENCE", defaultSeverity: "error", summary: "Call step targets a method not on any target interface" },
|
|
8982
|
-
{ code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Narrative asserts a guarantee the called contract does not declare" }
|
|
9356
|
+
{ code: "MISSING_IMPLEMENTATION_METHOD", defaultSeverity: "error", summary: "Contract method missing from the implementation" }
|
|
8983
9357
|
],
|
|
8984
9358
|
check(ctx) {
|
|
8985
9359
|
for (const impl of ctx.implementations) {
|
|
@@ -9010,6 +9384,33 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
9010
9384
|
);
|
|
9011
9385
|
}
|
|
9012
9386
|
}
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
};
|
|
9390
|
+
}
|
|
9391
|
+
});
|
|
9392
|
+
|
|
9393
|
+
// src/core/rules/narrative/narrative-target-references.ts
|
|
9394
|
+
var narrativeTargetReferencesRule;
|
|
9395
|
+
var init_narrative_target_references = __esm({
|
|
9396
|
+
"src/core/rules/narrative/narrative-target-references.ts"() {
|
|
9397
|
+
"use strict";
|
|
9398
|
+
narrativeTargetReferencesRule = {
|
|
9399
|
+
name: "narrative-target-references",
|
|
9400
|
+
description: "A narrative call, dispatch or register step must name a target component, and a target method unless it dispatches. A target this tree contains must be a collaborator the calling component declares, must carry the named method on one of its interfaces, and that method must declare every semantic guarantee the step asserts.",
|
|
9401
|
+
codes: [
|
|
9402
|
+
{ code: "MISSING_TARGET_COMPONENT", defaultSeverity: "error", summary: "Call/register step missing targetComponent" },
|
|
9403
|
+
{ code: "MISSING_TARGET_METHOD", defaultSeverity: "error", summary: "Call/register step missing targetMethod" },
|
|
9404
|
+
{ code: "UNDECLARED_DEPENDENCY_CALL", defaultSeverity: "error", summary: "Call step targets a component the caller does not depend on or own" },
|
|
9405
|
+
{ code: "INVALID_TARGET_METHOD_REFERENCE", defaultSeverity: "error", summary: "Call step targets a method not on any target interface" },
|
|
9406
|
+
{ code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Narrative asserts a guarantee the called contract does not declare" }
|
|
9407
|
+
],
|
|
9408
|
+
check(ctx) {
|
|
9409
|
+
for (const impl of ctx.implementations) {
|
|
9410
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
9411
|
+
if (!contract) continue;
|
|
9412
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9413
|
+
const caller = ctx.componentMap.get(contract.component);
|
|
9013
9414
|
for (const implMethod of impl.methods) {
|
|
9014
9415
|
for (const step of implMethod.narrative) {
|
|
9015
9416
|
if (step.type !== "call" && step.type !== "dispatch" && step.type !== "register") continue;
|
|
@@ -9024,156 +9425,28 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
9024
9425
|
continue;
|
|
9025
9426
|
}
|
|
9026
9427
|
const target = step.targetComponent;
|
|
9027
|
-
const
|
|
9028
|
-
|
|
9029
|
-
const fromSubsystem = caller?.subsystem;
|
|
9030
|
-
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
|
|
9031
|
-
const reportUndeclared = (reaches, surfaceResolved) => {
|
|
9032
|
-
if (!caller || target === caller.id || caller.dependsOn.includes(target) || caller.owns.includes(target)) return;
|
|
9428
|
+
const verb = step.type === "dispatch" ? "dispatches through" : step.type === "register" ? "registers callback" : "calls";
|
|
9429
|
+
if (step.type !== "dispatch" && !step.targetMethod) {
|
|
9033
9430
|
ctx.addIssue(
|
|
9034
9431
|
"error",
|
|
9035
|
-
"
|
|
9036
|
-
`Method "${implMethod.name}" in implementation "${impl.id}"
|
|
9432
|
+
"MISSING_TARGET_METHOD",
|
|
9433
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" has a ${step.type} step (${step.stepNumber}) missing "targetMethod".`,
|
|
9037
9434
|
impl.id,
|
|
9038
|
-
isDraftCtx
|
|
9039
|
-
surfaceResolved
|
|
9435
|
+
isDraftCtx
|
|
9040
9436
|
);
|
|
9041
|
-
};
|
|
9042
|
-
if (step.type === "dispatch") {
|
|
9043
|
-
const dispatchTarget = ctx.componentMap.get(target);
|
|
9044
|
-
if (!dispatchTarget) {
|
|
9045
|
-
if (isCrossTreeForm || isCollapsedForm) {
|
|
9046
|
-
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
9047
|
-
if (resolved.kind === "ambiguous") {
|
|
9048
|
-
ctx.addIssue(
|
|
9049
|
-
"error",
|
|
9050
|
-
"SURFACE_REF_AMBIGUOUS",
|
|
9051
|
-
ambiguityMessage(
|
|
9052
|
-
resolved,
|
|
9053
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
|
|
9054
|
-
target
|
|
9055
|
-
),
|
|
9056
|
-
impl.id,
|
|
9057
|
-
isDraftCtx
|
|
9058
|
-
);
|
|
9059
|
-
continue;
|
|
9060
|
-
}
|
|
9061
|
-
if (resolved.kind === "resolved") {
|
|
9062
|
-
reportUndeclared("dispatches through", true);
|
|
9063
|
-
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
9064
|
-
ctx.addIssue(
|
|
9065
|
-
"error",
|
|
9066
|
-
"SURFACE_REF_NOT_EXPOSED",
|
|
9067
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
|
|
9068
|
-
impl.id,
|
|
9069
|
-
isDraftCtx,
|
|
9070
|
-
true
|
|
9071
|
-
);
|
|
9072
|
-
}
|
|
9073
|
-
continue;
|
|
9074
|
-
}
|
|
9075
|
-
}
|
|
9076
|
-
if (isCrossTreeForm) {
|
|
9077
|
-
ctx.addIssue(
|
|
9078
|
-
"warning",
|
|
9079
|
-
"CROSS_TREE_REF_UNRESOLVED",
|
|
9080
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${target}" (step ${step.stepNumber}), 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.`,
|
|
9081
|
-
impl.id,
|
|
9082
|
-
isDraftCtx
|
|
9083
|
-
);
|
|
9084
|
-
} else {
|
|
9085
|
-
ctx.addIssue(
|
|
9086
|
-
"error",
|
|
9087
|
-
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
9088
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
9089
|
-
impl.id,
|
|
9090
|
-
isDraftCtx
|
|
9091
|
-
);
|
|
9092
|
-
}
|
|
9093
|
-
continue;
|
|
9094
|
-
}
|
|
9095
|
-
reportUndeclared("dispatches through");
|
|
9096
9437
|
continue;
|
|
9097
9438
|
}
|
|
9098
|
-
|
|
9099
|
-
if (!
|
|
9439
|
+
if (!ctx.componentMap.get(target)) continue;
|
|
9440
|
+
if (caller && target !== caller.id && !caller.dependsOn.includes(target) && !caller.owns.includes(target)) {
|
|
9100
9441
|
ctx.addIssue(
|
|
9101
9442
|
"error",
|
|
9102
|
-
"
|
|
9103
|
-
`Method "${implMethod.name}" in implementation "${impl.id}"
|
|
9443
|
+
"UNDECLARED_DEPENDENCY_CALL",
|
|
9444
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${caller.id}") ${verb} component "${target}" (step ${step.stepNumber}) but component "${caller.id}" does not list "${target}" as a dependency.`,
|
|
9104
9445
|
impl.id,
|
|
9105
|
-
isDraftCtx
|
|
9446
|
+
isDraftCtx || ctx.isComponentDraft(caller.id)
|
|
9106
9447
|
);
|
|
9107
|
-
continue;
|
|
9108
|
-
}
|
|
9109
|
-
const targetComp = ctx.componentMap.get(target);
|
|
9110
|
-
if (!targetComp) {
|
|
9111
|
-
if (isCrossTreeForm || isCollapsedForm) {
|
|
9112
|
-
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
9113
|
-
if (resolved.kind === "ambiguous") {
|
|
9114
|
-
ctx.addIssue(
|
|
9115
|
-
"error",
|
|
9116
|
-
"SURFACE_REF_AMBIGUOUS",
|
|
9117
|
-
ambiguityMessage(
|
|
9118
|
-
resolved,
|
|
9119
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
|
|
9120
|
-
target
|
|
9121
|
-
),
|
|
9122
|
-
impl.id,
|
|
9123
|
-
isDraftCtx
|
|
9124
|
-
);
|
|
9125
|
-
continue;
|
|
9126
|
-
}
|
|
9127
|
-
if (resolved.kind === "resolved") {
|
|
9128
|
-
reportUndeclared(verb, true);
|
|
9129
|
-
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
9130
|
-
if (!surfaceMethod) {
|
|
9131
|
-
ctx.addIssue(
|
|
9132
|
-
"error",
|
|
9133
|
-
"SURFACE_REF_NOT_EXPOSED",
|
|
9134
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
|
|
9135
|
-
impl.id,
|
|
9136
|
-
isDraftCtx,
|
|
9137
|
-
true
|
|
9138
|
-
);
|
|
9139
|
-
} else if (step.assertsGuarantees) {
|
|
9140
|
-
const declared = new Set(surfaceMethod.guarantees ?? []);
|
|
9141
|
-
for (const g of step.assertsGuarantees) {
|
|
9142
|
-
if (!declared.has(g)) {
|
|
9143
|
-
ctx.addIssue(
|
|
9144
|
-
"warning",
|
|
9145
|
-
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
9146
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" asserts guarantee "${g}", but the surface snapshot of "${resolved.snapshot.projectName}" does not declare it on "${resolved.entry.id}.${step.targetMethod}".`,
|
|
9147
|
-
impl.id,
|
|
9148
|
-
isDraftCtx,
|
|
9149
|
-
true
|
|
9150
|
-
);
|
|
9151
|
-
}
|
|
9152
|
-
}
|
|
9153
|
-
}
|
|
9154
|
-
continue;
|
|
9155
|
-
}
|
|
9156
|
-
}
|
|
9157
|
-
if (isCrossTreeForm) {
|
|
9158
|
-
ctx.addIssue(
|
|
9159
|
-
"warning",
|
|
9160
|
-
"CROSS_TREE_REF_UNRESOLVED",
|
|
9161
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${target}" (step ${step.stepNumber}), 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.`,
|
|
9162
|
-
impl.id,
|
|
9163
|
-
isDraftCtx
|
|
9164
|
-
);
|
|
9165
|
-
} else {
|
|
9166
|
-
ctx.addIssue(
|
|
9167
|
-
"error",
|
|
9168
|
-
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
9169
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
9170
|
-
impl.id,
|
|
9171
|
-
isDraftCtx
|
|
9172
|
-
);
|
|
9173
|
-
}
|
|
9174
|
-
continue;
|
|
9175
9448
|
}
|
|
9176
|
-
|
|
9449
|
+
if (step.type === "dispatch") continue;
|
|
9177
9450
|
const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
|
|
9178
9451
|
let targetMethodSpec;
|
|
9179
9452
|
for (const targetIntf of targetInterfaces) {
|
|
@@ -9191,20 +9464,18 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
9191
9464
|
impl.id,
|
|
9192
9465
|
isDraftCtx || ctx.isComponentDraft(target)
|
|
9193
9466
|
);
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
}
|
|
9207
|
-
}
|
|
9467
|
+
continue;
|
|
9468
|
+
}
|
|
9469
|
+
const declared = new Set(targetMethodSpec.guarantees ?? []);
|
|
9470
|
+
for (const g of step.assertsGuarantees ?? []) {
|
|
9471
|
+
if (!declared.has(g)) {
|
|
9472
|
+
ctx.addIssue(
|
|
9473
|
+
"warning",
|
|
9474
|
+
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
9475
|
+
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${target}" \u2014 does not list "${g}" among its L3 contract guarantees. Declare it on that method (and ensure its shape can deliver it), or revise the narrative.`,
|
|
9476
|
+
impl.id,
|
|
9477
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
9478
|
+
);
|
|
9208
9479
|
}
|
|
9209
9480
|
}
|
|
9210
9481
|
}
|
|
@@ -9215,21 +9486,184 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
9215
9486
|
}
|
|
9216
9487
|
});
|
|
9217
9488
|
|
|
9218
|
-
// src/core/rules/narrative/
|
|
9219
|
-
var
|
|
9220
|
-
var
|
|
9221
|
-
"src/core/rules/narrative/
|
|
9489
|
+
// src/core/rules/narrative/cross-tree-references.ts
|
|
9490
|
+
var crossTreeReferencesRule;
|
|
9491
|
+
var init_cross_tree_references = __esm({
|
|
9492
|
+
"src/core/rules/narrative/cross-tree-references.ts"() {
|
|
9222
9493
|
"use strict";
|
|
9223
9494
|
init_models();
|
|
9224
|
-
|
|
9225
|
-
name: "
|
|
9226
|
-
description: "
|
|
9495
|
+
crossTreeReferencesRule = {
|
|
9496
|
+
name: "cross-tree-references",
|
|
9497
|
+
description: "A narrative call, dispatch or register target that is not a component of this tree must resolve against the stored surface snapshots to exactly one declared contract. Snapshots that expose the name with disagreeing contracts leave the reference ambiguous; no snapshot at all is a warning for a reference that points outside this loading root (only the parent project can verify it) and the error a genuine local typo has always got.",
|
|
9227
9498
|
codes: [
|
|
9228
|
-
{ code: "
|
|
9499
|
+
{ code: "INVALID_TARGET_COMPONENT_REFERENCE", defaultSeverity: "error", summary: "Call/register step targets a non-existent component" },
|
|
9500
|
+
{ code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree reference (super::/:: form) with no surface snapshot covering it \u2014 only the parent project can verify it" },
|
|
9501
|
+
{ code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree call/dispatch/register target matched by surface snapshots of several providers with different contracts" }
|
|
9229
9502
|
],
|
|
9230
9503
|
check(ctx) {
|
|
9231
|
-
const
|
|
9232
|
-
|
|
9504
|
+
for (const impl of ctx.implementations) {
|
|
9505
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
9506
|
+
if (!contract) continue;
|
|
9507
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9508
|
+
const caller = ctx.componentMap.get(contract.component);
|
|
9509
|
+
const fromSubsystem = caller?.subsystem;
|
|
9510
|
+
for (const implMethod of impl.methods) {
|
|
9511
|
+
for (const step of implMethod.narrative) {
|
|
9512
|
+
if (step.type !== "call" && step.type !== "dispatch" && step.type !== "register") continue;
|
|
9513
|
+
if (!step.targetComponent) continue;
|
|
9514
|
+
if (step.type !== "dispatch" && !step.targetMethod) continue;
|
|
9515
|
+
const target = step.targetComponent;
|
|
9516
|
+
if (ctx.componentMap.get(target)) continue;
|
|
9517
|
+
const verb = step.type === "dispatch" ? "dispatches through" : step.type === "register" ? "registers callback" : "calls";
|
|
9518
|
+
const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
|
|
9519
|
+
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
|
|
9520
|
+
if (isCrossTreeForm || isCollapsedForm) {
|
|
9521
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
9522
|
+
if (resolved.kind === "ambiguous") {
|
|
9523
|
+
ctx.addIssue(
|
|
9524
|
+
"error",
|
|
9525
|
+
"SURFACE_REF_AMBIGUOUS",
|
|
9526
|
+
ambiguityMessage(
|
|
9527
|
+
resolved,
|
|
9528
|
+
step.type === "dispatch" ? `Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through` : `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
|
|
9529
|
+
target
|
|
9530
|
+
),
|
|
9531
|
+
impl.id,
|
|
9532
|
+
isDraftCtx
|
|
9533
|
+
);
|
|
9534
|
+
continue;
|
|
9535
|
+
}
|
|
9536
|
+
if (resolved.kind === "resolved") continue;
|
|
9537
|
+
}
|
|
9538
|
+
if (isCrossTreeForm) {
|
|
9539
|
+
ctx.addIssue(
|
|
9540
|
+
"warning",
|
|
9541
|
+
"CROSS_TREE_REF_UNRESOLVED",
|
|
9542
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${target}" (step ${step.stepNumber}), 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.`,
|
|
9543
|
+
impl.id,
|
|
9544
|
+
isDraftCtx
|
|
9545
|
+
);
|
|
9546
|
+
} else {
|
|
9547
|
+
ctx.addIssue(
|
|
9548
|
+
"error",
|
|
9549
|
+
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
9550
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
9551
|
+
impl.id,
|
|
9552
|
+
isDraftCtx
|
|
9553
|
+
);
|
|
9554
|
+
}
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9557
|
+
}
|
|
9558
|
+
}
|
|
9559
|
+
};
|
|
9560
|
+
}
|
|
9561
|
+
});
|
|
9562
|
+
|
|
9563
|
+
// src/core/rules/narrative/surface-reference-backing.ts
|
|
9564
|
+
var surfaceReferenceBackingRule;
|
|
9565
|
+
var init_surface_reference_backing = __esm({
|
|
9566
|
+
"src/core/rules/narrative/surface-reference-backing.ts"() {
|
|
9567
|
+
"use strict";
|
|
9568
|
+
surfaceReferenceBackingRule = {
|
|
9569
|
+
name: "surface-reference-backing",
|
|
9570
|
+
description: "The surface snapshot a cross-tree narrative target resolves to must back what the step asks of it: the calling component declares the collaborator, the snapshot exposes the called method (or serves the dispatched capability), and it declares every semantic guarantee the step asserts. A snapshot is the provider's declared contract, so these are contract verdicts rather than resolution failures.",
|
|
9571
|
+
codes: [
|
|
9572
|
+
{ code: "SURFACE_REF_NOT_EXPOSED", defaultSeverity: "error", summary: "Cross-tree reference resolves to a surface snapshot that does not expose the called method/capability" },
|
|
9573
|
+
{ code: "UNDECLARED_DEPENDENCY_CALL", defaultSeverity: "error", summary: "Call step targets a component the caller does not depend on or own" },
|
|
9574
|
+
{ code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Narrative asserts a guarantee the called contract does not declare" }
|
|
9575
|
+
],
|
|
9576
|
+
check(ctx) {
|
|
9577
|
+
for (const impl of ctx.implementations) {
|
|
9578
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
9579
|
+
if (!contract) continue;
|
|
9580
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9581
|
+
const caller = ctx.componentMap.get(contract.component);
|
|
9582
|
+
const fromSubsystem = caller?.subsystem;
|
|
9583
|
+
for (const implMethod of impl.methods) {
|
|
9584
|
+
for (const step of implMethod.narrative) {
|
|
9585
|
+
if (step.type !== "call" && step.type !== "dispatch" && step.type !== "register") continue;
|
|
9586
|
+
if (!step.targetComponent) continue;
|
|
9587
|
+
if (step.type !== "dispatch" && !step.targetMethod) continue;
|
|
9588
|
+
const target = step.targetComponent;
|
|
9589
|
+
if (ctx.componentMap.get(target)) continue;
|
|
9590
|
+
const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
|
|
9591
|
+
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
|
|
9592
|
+
if (!isCrossTreeForm && !isCollapsedForm) continue;
|
|
9593
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
9594
|
+
if (resolved.kind !== "resolved") continue;
|
|
9595
|
+
const verb = step.type === "dispatch" ? "dispatches through" : step.type === "register" ? "registers callback" : "calls";
|
|
9596
|
+
if (caller && target !== caller.id && !caller.dependsOn.includes(target) && !caller.owns.includes(target)) {
|
|
9597
|
+
ctx.addIssue(
|
|
9598
|
+
"error",
|
|
9599
|
+
"UNDECLARED_DEPENDENCY_CALL",
|
|
9600
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${caller.id}") ${verb} component "${target}" (step ${step.stepNumber}) but component "${caller.id}" does not list "${target}" as a dependency.`,
|
|
9601
|
+
impl.id,
|
|
9602
|
+
isDraftCtx || ctx.isComponentDraft(caller.id),
|
|
9603
|
+
true
|
|
9604
|
+
);
|
|
9605
|
+
}
|
|
9606
|
+
if (step.type === "dispatch") {
|
|
9607
|
+
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
9608
|
+
ctx.addIssue(
|
|
9609
|
+
"error",
|
|
9610
|
+
"SURFACE_REF_NOT_EXPOSED",
|
|
9611
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
|
|
9612
|
+
impl.id,
|
|
9613
|
+
isDraftCtx,
|
|
9614
|
+
true
|
|
9615
|
+
);
|
|
9616
|
+
}
|
|
9617
|
+
continue;
|
|
9618
|
+
}
|
|
9619
|
+
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
9620
|
+
if (!surfaceMethod) {
|
|
9621
|
+
ctx.addIssue(
|
|
9622
|
+
"error",
|
|
9623
|
+
"SURFACE_REF_NOT_EXPOSED",
|
|
9624
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
|
|
9625
|
+
impl.id,
|
|
9626
|
+
isDraftCtx,
|
|
9627
|
+
true
|
|
9628
|
+
);
|
|
9629
|
+
continue;
|
|
9630
|
+
}
|
|
9631
|
+
const declared = new Set(surfaceMethod.guarantees ?? []);
|
|
9632
|
+
for (const g of step.assertsGuarantees ?? []) {
|
|
9633
|
+
if (!declared.has(g)) {
|
|
9634
|
+
ctx.addIssue(
|
|
9635
|
+
"warning",
|
|
9636
|
+
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
9637
|
+
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" asserts guarantee "${g}", but the surface snapshot of "${resolved.snapshot.projectName}" does not declare it on "${resolved.entry.id}.${step.targetMethod}".`,
|
|
9638
|
+
impl.id,
|
|
9639
|
+
isDraftCtx,
|
|
9640
|
+
true
|
|
9641
|
+
);
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
9648
|
+
};
|
|
9649
|
+
}
|
|
9650
|
+
});
|
|
9651
|
+
|
|
9652
|
+
// src/core/rules/narrative/guarantee-tokens.ts
|
|
9653
|
+
var guaranteeTokensRule;
|
|
9654
|
+
var init_guarantee_tokens = __esm({
|
|
9655
|
+
"src/core/rules/narrative/guarantee-tokens.ts"() {
|
|
9656
|
+
"use strict";
|
|
9657
|
+
init_models();
|
|
9658
|
+
guaranteeTokensRule = {
|
|
9659
|
+
name: "guarantee-tokens",
|
|
9660
|
+
description: "Every semantic-guarantee token an L3 method declares or a narrative step asserts must be a builtin guarantee or one a loaded extension pack declares in its `guarantees` list. The vocabulary is open for packs, not for typos \u2014 an undeclared token is matched by nothing and silently escapes the narrative\u2194contract consistency checks.",
|
|
9661
|
+
codes: [
|
|
9662
|
+
{ code: "UNKNOWN_GUARANTEE", defaultSeverity: "warning", summary: "Guarantee token is neither builtin nor pack-declared" }
|
|
9663
|
+
],
|
|
9664
|
+
check(ctx) {
|
|
9665
|
+
const known = /* @__PURE__ */ new Set([...SEMANTIC_GUARANTEES, ...ctx.ext.guarantees]);
|
|
9666
|
+
const fixHint = `Known tokens: builtin ${SEMANTIC_GUARANTEES.join(", ")}${ctx.ext.guarantees.length ? `; pack-declared ${ctx.ext.guarantees.join(", ")}` : ""}. Fix the spelling or declare the token in an extension pack's \`guarantees\` list.`;
|
|
9233
9667
|
for (const intf of ctx.interfaces) {
|
|
9234
9668
|
const isDraftCtx = intf.status === "draft" || intf.status === "design" || ctx.isComponentDraft(intf.component);
|
|
9235
9669
|
for (const method2 of intf.methods) {
|
|
@@ -9269,166 +9703,68 @@ var init_guarantee_tokens = __esm({
|
|
|
9269
9703
|
}
|
|
9270
9704
|
});
|
|
9271
9705
|
|
|
9272
|
-
// src/core/rules/narrative/narrative-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
if (v === void 0) return false;
|
|
9277
|
-
if (Array.isArray(v) && v.length === 0) return false;
|
|
9278
|
-
return true;
|
|
9279
|
-
});
|
|
9280
|
-
}
|
|
9281
|
-
var FLOW_FIELDS, narrativeFlowRule;
|
|
9282
|
-
var init_narrative_flow = __esm({
|
|
9283
|
-
"src/core/rules/narrative/narrative-flow.ts"() {
|
|
9706
|
+
// src/core/rules/narrative/narrative-step-config.ts
|
|
9707
|
+
var narrativeStepConfigRule;
|
|
9708
|
+
var init_narrative_step_config = __esm({
|
|
9709
|
+
"src/core/rules/narrative/narrative-step-config.ts"() {
|
|
9284
9710
|
"use strict";
|
|
9285
9711
|
init_models();
|
|
9286
|
-
|
|
9287
|
-
"
|
|
9288
|
-
"
|
|
9289
|
-
"onFalseStep",
|
|
9290
|
-
"on",
|
|
9291
|
-
"cases",
|
|
9292
|
-
"defaultStep",
|
|
9293
|
-
"loopKind",
|
|
9294
|
-
"over",
|
|
9295
|
-
"endStep",
|
|
9296
|
-
"catches",
|
|
9297
|
-
"finallyStep",
|
|
9298
|
-
"branches",
|
|
9299
|
-
"toStep"
|
|
9300
|
-
];
|
|
9301
|
-
narrativeFlowRule = {
|
|
9302
|
-
name: "narrative-flow",
|
|
9303
|
-
description: 'Control-flow soundness of L5 narratives: flow steps (branch, switch, loop, try, parallel, jump) must carry their required config, every jump field must target an existing step number in the same narrative, and every step must be reachable from the first step following fall-through and jumps. A parallel body is covered by contiguous, ordered arms (>= 2) whose last steps continue at the join after endStep; "detach" (fire-and-forget) is legal on call/dispatch steps only.',
|
|
9712
|
+
narrativeStepConfigRule = {
|
|
9713
|
+
name: "narrative-step-config",
|
|
9714
|
+
description: 'The step configuration of L5 narratives: every flow step (branch, switch, loop, try, parallel, jump) carries the config its type requires and no simple step carries flow config, "detach" (fire-and-forget) is legal on call/dispatch steps only, a parallel body is covered by contiguous, ordered arms (>= 2), no two steps share a step number or a label, and every jump field targets an existing step number in the same narrative.',
|
|
9304
9715
|
codes: [
|
|
9305
9716
|
{ code: "MALFORMED_FLOW_STEP", defaultSeverity: "error", summary: "Flow step missing required config (or flow config on a local/call step)" },
|
|
9306
9717
|
{ code: "INVALID_STEP_JUMP", defaultSeverity: "error", summary: "Jump field targets a step number that does not exist in the narrative" },
|
|
9307
|
-
{ code: "UNREACHABLE_STEP", defaultSeverity: "warning", summary: "Step not reachable from the first step following fall-through and jumps" },
|
|
9308
|
-
{ code: "REGION_OVERLAP", defaultSeverity: "error", summary: "loop/try regions interleave \u2014 regions must nest or be disjoint to map onto structured code" },
|
|
9309
|
-
{ code: "JUMP_INTO_REGION", defaultSeverity: "warning", summary: "Jump lands in the middle of a loop/try body from outside \u2014 regions are entered through their header" },
|
|
9310
|
-
{ code: "FALLTHROUGH_INTO_HANDLER", defaultSeverity: "warning", summary: "try body falls through into its own catch/finally region on the success path" },
|
|
9311
|
-
{ code: "BACKWARD_JUMP", defaultSeverity: "warning", summary: "Backward jump that is not a continue to an enclosing loop header \u2014 model repetition with a loop step" },
|
|
9312
9718
|
{ code: "DUPLICATE_STEP_LABEL", defaultSeverity: "error", summary: "Two steps in one narrative share a label \u2014 the symbolic anchor later deltas address by" }
|
|
9313
9719
|
],
|
|
9720
|
+
check(ctx) {
|
|
9721
|
+
for (const impl of ctx.implementations) {
|
|
9722
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9723
|
+
for (const implMethod of impl.methods) {
|
|
9724
|
+
if (!implMethod.narrative.length) continue;
|
|
9725
|
+
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
9726
|
+
for (const problem of stepConfigVerdict(implMethod).problems) {
|
|
9727
|
+
ctx.addIssue("error", problem.code, where + problem.detail, impl.id, isDraftCtx);
|
|
9728
|
+
}
|
|
9729
|
+
}
|
|
9730
|
+
}
|
|
9731
|
+
}
|
|
9732
|
+
};
|
|
9733
|
+
}
|
|
9734
|
+
});
|
|
9735
|
+
|
|
9736
|
+
// src/core/rules/narrative/narrative-reachability.ts
|
|
9737
|
+
function regionsOf(steps) {
|
|
9738
|
+
const regions = [];
|
|
9739
|
+
for (const s of steps) {
|
|
9740
|
+
if ((s.type === "loop" || s.type === "try" || s.type === "parallel") && s.endStep !== void 0) {
|
|
9741
|
+
regions.push({ h: s.stepNumber, end: s.endStep, kind: s.type });
|
|
9742
|
+
}
|
|
9743
|
+
}
|
|
9744
|
+
return regions;
|
|
9745
|
+
}
|
|
9746
|
+
var narrativeReachabilityRule;
|
|
9747
|
+
var init_narrative_reachability = __esm({
|
|
9748
|
+
"src/core/rules/narrative/narrative-reachability.ts"() {
|
|
9749
|
+
"use strict";
|
|
9750
|
+
init_models();
|
|
9751
|
+
narrativeReachabilityRule = {
|
|
9752
|
+
name: "narrative-reachability",
|
|
9753
|
+
description: "Every step of an L5 narrative must be reachable from the first step following fall-through and jumps, and the loop/try/parallel regions it declares must nest or stay disjoint \u2014 interleaved regions map onto no structured code. Judged over the step graph, and only for a narrative whose step configuration is sound (narrative-step-config reports the rest).",
|
|
9754
|
+
codes: [
|
|
9755
|
+
{ code: "UNREACHABLE_STEP", defaultSeverity: "warning", summary: "Step not reachable from the first step following fall-through and jumps" },
|
|
9756
|
+
{ code: "REGION_OVERLAP", defaultSeverity: "error", summary: "loop/try regions interleave \u2014 regions must nest or be disjoint to map onto structured code" }
|
|
9757
|
+
],
|
|
9314
9758
|
check(ctx) {
|
|
9315
9759
|
for (const impl of ctx.implementations) {
|
|
9316
9760
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9317
9761
|
for (const implMethod of impl.methods) {
|
|
9318
9762
|
const steps = implMethod.narrative;
|
|
9319
9763
|
if (!steps.length) continue;
|
|
9764
|
+
if (!stepConfigVerdict(implMethod).sound) continue;
|
|
9320
9765
|
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
9321
|
-
const labelled = /* @__PURE__ */ new Map();
|
|
9322
|
-
for (const step of steps) {
|
|
9323
|
-
const label = step.label;
|
|
9324
|
-
if (!label) continue;
|
|
9325
|
-
const first = labelled.get(label);
|
|
9326
|
-
if (first !== void 0) {
|
|
9327
|
-
ctx.addIssue(
|
|
9328
|
-
"error",
|
|
9329
|
-
"DUPLICATE_STEP_LABEL",
|
|
9330
|
-
`${where}steps ${first} and ${step.stepNumber} share the label "${label}". A label anchors one step for later deltas to address; two steps holding it makes every symbolic reference ambiguous and blocks all further edits to this implementation. Rename one.`,
|
|
9331
|
-
impl.id,
|
|
9332
|
-
isDraftCtx
|
|
9333
|
-
);
|
|
9334
|
-
} else {
|
|
9335
|
-
labelled.set(label, step.stepNumber);
|
|
9336
|
-
}
|
|
9337
|
-
}
|
|
9338
|
-
let sound = true;
|
|
9339
|
-
const malformed = (msg) => {
|
|
9340
|
-
sound = false;
|
|
9341
|
-
ctx.addIssue("error", "MALFORMED_FLOW_STEP", where + msg, impl.id, isDraftCtx);
|
|
9342
|
-
};
|
|
9343
|
-
const byNum = /* @__PURE__ */ new Map();
|
|
9344
|
-
for (const s of steps) {
|
|
9345
|
-
if (byNum.has(s.stepNumber)) malformed(`duplicate stepNumber ${s.stepNumber} \u2014 jump targets would be ambiguous.`);
|
|
9346
|
-
byNum.set(s.stepNumber, s);
|
|
9347
|
-
}
|
|
9348
|
-
const nums = [...byNum.keys()].sort((a, b) => a - b);
|
|
9349
|
-
const indexOf = new Map(nums.map((n, i) => [n, i]));
|
|
9350
|
-
const nextOf = (n) => {
|
|
9351
|
-
const i = indexOf.get(n);
|
|
9352
|
-
return i !== void 0 && i + 1 < nums.length ? nums[i + 1] : void 0;
|
|
9353
|
-
};
|
|
9354
|
-
for (const s of steps) {
|
|
9355
|
-
if (s.detach && s.type !== "call" && s.type !== "dispatch") {
|
|
9356
|
-
malformed(`step ${s.stepNumber} (${s.type}) carries "detach" \u2014 fire-and-forget applies to call/dispatch steps only.`);
|
|
9357
|
-
}
|
|
9358
|
-
switch (s.type) {
|
|
9359
|
-
case "local":
|
|
9360
|
-
case "call":
|
|
9361
|
-
case "register":
|
|
9362
|
-
case "dispatch": {
|
|
9363
|
-
const extra = flowConfigOn(s);
|
|
9364
|
-
if (extra.length) malformed(`step ${s.stepNumber} (${s.type}) carries flow config (${extra.join(", ")}) \u2014 use a flow step type instead.`);
|
|
9365
|
-
break;
|
|
9366
|
-
}
|
|
9367
|
-
case "branch":
|
|
9368
|
-
if (!s.condition) malformed(`branch step ${s.stepNumber} requires "condition".`);
|
|
9369
|
-
if (s.onFalseStep === void 0) malformed(`branch step ${s.stepNumber} requires "onFalseStep" (true continues at onTrueStep or the next step).`);
|
|
9370
|
-
break;
|
|
9371
|
-
case "switch":
|
|
9372
|
-
if (!s.cases || !s.cases.length) malformed(`switch step ${s.stepNumber} requires non-empty "cases".`);
|
|
9373
|
-
break;
|
|
9374
|
-
case "loop": {
|
|
9375
|
-
if (s.endStep === void 0) malformed(`loop step ${s.stepNumber} requires "endStep" (last step of the body).`);
|
|
9376
|
-
else if (s.endStep <= s.stepNumber) malformed(`loop step ${s.stepNumber} "endStep" (${s.endStep}) must lie beyond the header.`);
|
|
9377
|
-
const kind = s.loopKind ?? (s.over ? "forEach" : "while");
|
|
9378
|
-
if ((kind === "while" || kind === "doWhile") && !s.condition) malformed(`${kind} loop step ${s.stepNumber} requires "condition".`);
|
|
9379
|
-
if ((kind === "forEach" || kind === "for") && !s.over) malformed(`${kind} loop step ${s.stepNumber} requires "over" (the iteration source).`);
|
|
9380
|
-
break;
|
|
9381
|
-
}
|
|
9382
|
-
case "try":
|
|
9383
|
-
if (s.endStep === void 0) malformed(`try step ${s.stepNumber} requires "endStep" (last step of the guarded body).`);
|
|
9384
|
-
else if (s.endStep <= s.stepNumber) malformed(`try step ${s.stepNumber} "endStep" (${s.endStep}) must lie beyond the header.`);
|
|
9385
|
-
if ((!s.catches || !s.catches.length) && s.finallyStep === void 0) malformed(`try step ${s.stepNumber} requires "catches" and/or "finallyStep" \u2014 a guard that handles nothing guards nothing.`);
|
|
9386
|
-
break;
|
|
9387
|
-
case "parallel": {
|
|
9388
|
-
if (s.endStep === void 0) malformed(`parallel step ${s.stepNumber} requires "endStep" (last step of the fan-out body).`);
|
|
9389
|
-
else if (s.endStep <= s.stepNumber) malformed(`parallel step ${s.stepNumber} "endStep" (${s.endStep}) must lie beyond the header.`);
|
|
9390
|
-
const entries = (s.branches ?? []).map((b) => b.step);
|
|
9391
|
-
if (entries.length < 2) {
|
|
9392
|
-
malformed(`parallel step ${s.stepNumber} requires "branches" with at least two arms \u2014 a single arm is sequential flow.`);
|
|
9393
|
-
} else {
|
|
9394
|
-
const sorted = [...entries].sort((a, b) => a - b);
|
|
9395
|
-
if (entries.some((e, i) => e !== sorted[i])) malformed(`parallel step ${s.stepNumber} "branches" must list arm entries in ascending order \u2014 arms are contiguous sub-regions of the body.`);
|
|
9396
|
-
if (new Set(entries).size !== entries.length) malformed(`parallel step ${s.stepNumber} "branches" lists the same entry step twice.`);
|
|
9397
|
-
const bodyStart = nextOf(s.stepNumber);
|
|
9398
|
-
if (bodyStart !== void 0 && sorted[0] !== bodyStart) malformed(`parallel step ${s.stepNumber}: the first arm must start at the body's first step (${bodyStart}), got ${sorted[0]} \u2014 steps before the first arm would belong to no arm.`);
|
|
9399
|
-
if (s.endStep !== void 0 && sorted.some((e) => e > s.endStep)) malformed(`parallel step ${s.stepNumber}: every arm entry must lie within the body (..${s.endStep}).`);
|
|
9400
|
-
}
|
|
9401
|
-
break;
|
|
9402
|
-
}
|
|
9403
|
-
case "jump":
|
|
9404
|
-
if (s.toStep === void 0) malformed(`jump step ${s.stepNumber} requires "toStep".`);
|
|
9405
|
-
break;
|
|
9406
|
-
}
|
|
9407
|
-
const jumpFields = [];
|
|
9408
|
-
if (s.onTrueStep !== void 0) jumpFields.push(["onTrueStep", s.onTrueStep]);
|
|
9409
|
-
if (s.onFalseStep !== void 0) jumpFields.push(["onFalseStep", s.onFalseStep]);
|
|
9410
|
-
if (s.defaultStep !== void 0) jumpFields.push(["defaultStep", s.defaultStep]);
|
|
9411
|
-
if (s.endStep !== void 0) jumpFields.push(["endStep", s.endStep]);
|
|
9412
|
-
if (s.finallyStep !== void 0) jumpFields.push(["finallyStep", s.finallyStep]);
|
|
9413
|
-
if (s.toStep !== void 0) jumpFields.push(["toStep", s.toStep]);
|
|
9414
|
-
(s.cases ?? []).forEach((c, i) => jumpFields.push([`cases[${i}].step`, c.step]));
|
|
9415
|
-
(s.catches ?? []).forEach((c, i) => jumpFields.push([`catches[${i}].step`, c.step]));
|
|
9416
|
-
(s.branches ?? []).forEach((b, i) => jumpFields.push([`branches[${i}].step`, b.step]));
|
|
9417
|
-
for (const [field, target] of jumpFields) {
|
|
9418
|
-
if (!byNum.has(target)) {
|
|
9419
|
-
sound = false;
|
|
9420
|
-
ctx.addIssue(
|
|
9421
|
-
"error",
|
|
9422
|
-
"INVALID_STEP_JUMP",
|
|
9423
|
-
`${where}step ${s.stepNumber} "${field}" targets step ${target}, which does not exist in this narrative.`,
|
|
9424
|
-
impl.id,
|
|
9425
|
-
isDraftCtx
|
|
9426
|
-
);
|
|
9427
|
-
}
|
|
9428
|
-
}
|
|
9429
|
-
}
|
|
9430
|
-
if (!sound) continue;
|
|
9431
9766
|
const graph = stepGraph(implMethod);
|
|
9767
|
+
const nums = graph.stepNumbers;
|
|
9432
9768
|
const visited = /* @__PURE__ */ new Set();
|
|
9433
9769
|
const stack = [nums[0]];
|
|
9434
9770
|
while (stack.length) {
|
|
@@ -9449,13 +9785,7 @@ var init_narrative_flow = __esm({
|
|
|
9449
9785
|
isDraftCtx
|
|
9450
9786
|
);
|
|
9451
9787
|
}
|
|
9452
|
-
const regions =
|
|
9453
|
-
for (const s of steps) {
|
|
9454
|
-
if ((s.type === "loop" || s.type === "try" || s.type === "parallel") && s.endStep !== void 0) {
|
|
9455
|
-
regions.push({ h: s.stepNumber, end: s.endStep, kind: s.type });
|
|
9456
|
-
}
|
|
9457
|
-
}
|
|
9458
|
-
const inBody = (n, r) => n > r.h && n <= r.end;
|
|
9788
|
+
const regions = regionsOf(steps);
|
|
9459
9789
|
for (const a of regions) {
|
|
9460
9790
|
for (const b of regions) {
|
|
9461
9791
|
if (b.h > a.h && b.h <= a.end && b.end > a.end) {
|
|
@@ -9469,19 +9799,63 @@ var init_narrative_flow = __esm({
|
|
|
9469
9799
|
}
|
|
9470
9800
|
}
|
|
9471
9801
|
}
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9802
|
+
}
|
|
9803
|
+
}
|
|
9804
|
+
}
|
|
9805
|
+
};
|
|
9806
|
+
}
|
|
9807
|
+
});
|
|
9808
|
+
|
|
9809
|
+
// src/core/rules/narrative/narrative-jump-edges.ts
|
|
9810
|
+
function regionsOf2(steps) {
|
|
9811
|
+
const regions = [];
|
|
9812
|
+
for (const s of steps) {
|
|
9813
|
+
if ((s.type === "loop" || s.type === "try" || s.type === "parallel") && s.endStep !== void 0) {
|
|
9814
|
+
regions.push({ h: s.stepNumber, end: s.endStep, kind: s.type });
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
return regions;
|
|
9818
|
+
}
|
|
9819
|
+
function jumpEdgesOf(steps) {
|
|
9820
|
+
const edges = [];
|
|
9821
|
+
for (const s of steps) {
|
|
9822
|
+
const push2 = (field, to) => {
|
|
9823
|
+
if (to !== void 0) edges.push({ from: s.stepNumber, to, field, kind: s.type });
|
|
9824
|
+
};
|
|
9825
|
+
push2("onTrueStep", s.onTrueStep);
|
|
9826
|
+
push2("onFalseStep", s.onFalseStep);
|
|
9827
|
+
push2("defaultStep", s.defaultStep);
|
|
9828
|
+
push2("toStep", s.toStep);
|
|
9829
|
+
push2("finallyStep", s.finallyStep);
|
|
9830
|
+
(s.cases ?? []).forEach((c, i) => push2(`cases[${i}].step`, c.step));
|
|
9831
|
+
(s.catches ?? []).forEach((c, i) => push2(`catches[${i}].step`, c.step));
|
|
9832
|
+
}
|
|
9833
|
+
return edges;
|
|
9834
|
+
}
|
|
9835
|
+
var narrativeJumpEdgesRule;
|
|
9836
|
+
var init_narrative_jump_edges = __esm({
|
|
9837
|
+
"src/core/rules/narrative/narrative-jump-edges.ts"() {
|
|
9838
|
+
"use strict";
|
|
9839
|
+
init_models();
|
|
9840
|
+
narrativeJumpEdgesRule = {
|
|
9841
|
+
name: "narrative-jump-edges",
|
|
9842
|
+
description: "Where an L5 narrative's jump edges land: a loop/try/parallel region is entered through its header, never into the middle of its body; a backward jump is only idiomatic as a continue to an enclosing loop header, and otherwise models repetition that belongs in a loop step; and a try body must not fall through into its own catch/finally region on the success path. Judged only for a narrative whose step configuration is sound (narrative-step-config reports the rest).",
|
|
9843
|
+
codes: [
|
|
9844
|
+
{ code: "JUMP_INTO_REGION", defaultSeverity: "warning", summary: "Jump lands in the middle of a loop/try body from outside \u2014 regions are entered through their header" },
|
|
9845
|
+
{ code: "BACKWARD_JUMP", defaultSeverity: "warning", summary: "Backward jump that is not a continue to an enclosing loop header \u2014 model repetition with a loop step" },
|
|
9846
|
+
{ code: "FALLTHROUGH_INTO_HANDLER", defaultSeverity: "warning", summary: "try body falls through into its own catch/finally region on the success path" }
|
|
9847
|
+
],
|
|
9848
|
+
check(ctx) {
|
|
9849
|
+
for (const impl of ctx.implementations) {
|
|
9850
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9851
|
+
for (const implMethod of impl.methods) {
|
|
9852
|
+
const steps = implMethod.narrative;
|
|
9853
|
+
if (!steps.length) continue;
|
|
9854
|
+
if (!stepConfigVerdict(implMethod).sound) continue;
|
|
9855
|
+
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
9856
|
+
const regions = regionsOf2(steps);
|
|
9857
|
+
const jumpEdges = jumpEdgesOf(steps);
|
|
9858
|
+
const inBody = (n, r) => n > r.h && n <= r.end;
|
|
9485
9859
|
for (const e of jumpEdges) {
|
|
9486
9860
|
for (const r of regions) {
|
|
9487
9861
|
if (inBody(e.to, r) && e.from !== r.h && !inBody(e.from, r)) {
|
|
@@ -9494,19 +9868,26 @@ var init_narrative_flow = __esm({
|
|
|
9494
9868
|
);
|
|
9495
9869
|
}
|
|
9496
9870
|
}
|
|
9497
|
-
if ((e.kind === "branch" || e.kind === "switch" || e.kind === "jump") && e.to < e.from) {
|
|
9498
|
-
const continueToLoop = regions.some((r) => r.kind === "loop" && r.h === e.to && inBody(e.from, r));
|
|
9499
|
-
if (!continueToLoop) {
|
|
9500
|
-
ctx.addIssue(
|
|
9501
|
-
"warning",
|
|
9502
|
-
"BACKWARD_JUMP",
|
|
9503
|
-
`${where}step ${e.from} "${e.field}" jumps backwards to step ${e.to} \u2014 model repetition with a loop step (backward jumps are only idiomatic as a continue to an enclosing loop header).`,
|
|
9504
|
-
impl.id,
|
|
9505
|
-
isDraftCtx
|
|
9506
|
-
);
|
|
9507
|
-
}
|
|
9508
|
-
}
|
|
9509
9871
|
}
|
|
9872
|
+
for (const e of jumpEdges) {
|
|
9873
|
+
if (e.kind !== "branch" && e.kind !== "switch" && e.kind !== "jump" || e.to >= e.from) continue;
|
|
9874
|
+
const continueToLoop = regions.some((r) => r.kind === "loop" && r.h === e.to && inBody(e.from, r));
|
|
9875
|
+
if (continueToLoop) continue;
|
|
9876
|
+
ctx.addIssue(
|
|
9877
|
+
"warning",
|
|
9878
|
+
"BACKWARD_JUMP",
|
|
9879
|
+
`${where}step ${e.from} "${e.field}" jumps backwards to step ${e.to} \u2014 model repetition with a loop step (backward jumps are only idiomatic as a continue to an enclosing loop header).`,
|
|
9880
|
+
impl.id,
|
|
9881
|
+
isDraftCtx
|
|
9882
|
+
);
|
|
9883
|
+
}
|
|
9884
|
+
const byNum = /* @__PURE__ */ new Map();
|
|
9885
|
+
for (const s of steps) byNum.set(s.stepNumber, s);
|
|
9886
|
+
const nums = [...byNum.keys()].sort((a, b) => a - b);
|
|
9887
|
+
const nextOf = (n) => {
|
|
9888
|
+
const i = nums.indexOf(n);
|
|
9889
|
+
return i !== -1 && i + 1 < nums.length ? nums[i + 1] : void 0;
|
|
9890
|
+
};
|
|
9510
9891
|
for (const s of steps) {
|
|
9511
9892
|
if (s.type !== "try" || s.endStep === void 0) continue;
|
|
9512
9893
|
const handlerStarts = new Set((s.catches ?? []).map((c) => c.step));
|
|
@@ -9778,8 +10159,7 @@ var init_narrative_detail = __esm({
|
|
|
9778
10159
|
{ code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
|
|
9779
10160
|
],
|
|
9780
10161
|
check(ctx) {
|
|
9781
|
-
const
|
|
9782
|
-
for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
|
|
10162
|
+
const code = ctx.codeIndex();
|
|
9783
10163
|
for (const impl of ctx.implementations) {
|
|
9784
10164
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
9785
10165
|
if (!contract) continue;
|
|
@@ -9805,7 +10185,7 @@ var init_narrative_detail = __esm({
|
|
|
9805
10185
|
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
9806
10186
|
const file = methodSourceFile(implMethod, impl.sourcePath);
|
|
9807
10187
|
if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
|
|
9808
|
-
const facts =
|
|
10188
|
+
const facts = code.factsAt(file);
|
|
9809
10189
|
const symbol = implMethod.symbol ?? implMethod.name;
|
|
9810
10190
|
const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
|
|
9811
10191
|
const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
|
|
@@ -9951,7 +10331,7 @@ var init_logic_declaration = __esm({
|
|
|
9951
10331
|
logicDeclarationRule = {
|
|
9952
10332
|
name: "logic-declaration",
|
|
9953
10333
|
scope: "spec",
|
|
9954
|
-
description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by
|
|
10334
|
+
description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by logic-dependency-class. Intrinsic to one component: no tree required.",
|
|
9955
10335
|
codes: [
|
|
9956
10336
|
{ code: "DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR", defaultSeverity: "error", summary: "dependencyClass declared on a component that is not an Orchestrator" }
|
|
9957
10337
|
],
|
|
@@ -10177,257 +10557,341 @@ var init_portal_call_auth = __esm({
|
|
|
10177
10557
|
}
|
|
10178
10558
|
});
|
|
10179
10559
|
|
|
10180
|
-
// src/core/rules/doctrine/
|
|
10181
|
-
var
|
|
10182
|
-
var
|
|
10183
|
-
"src/core/rules/doctrine/
|
|
10560
|
+
// src/core/rules/doctrine/subsystem-boundary-dependencies.ts
|
|
10561
|
+
var subsystemBoundaryDepsRule;
|
|
10562
|
+
var init_subsystem_boundary_dependencies = __esm({
|
|
10563
|
+
"src/core/rules/doctrine/subsystem-boundary-dependencies.ts"() {
|
|
10184
10564
|
"use strict";
|
|
10185
10565
|
init_models();
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
|
|
10190
|
-
}
|
|
10191
|
-
return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
|
|
10192
|
-
};
|
|
10193
|
-
isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
|
|
10194
|
-
isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
|
|
10195
|
-
stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
|
|
10196
|
-
stereotypeDepsRule = {
|
|
10197
|
-
name: "stereotype-dependencies",
|
|
10198
|
-
description: "Enforces the component-stereotype interaction matrix and the cross-subsystem shape. The matrix: a Portal reaches the data layer only through Repository/Index READ faces, and writes route through Orchestrators (PORTAL_WRITE_SHORTCUT); Stores are depended upon; Registries write their Store; Adapters are sinks; Views stay passive; an Orchestrator's dependencyClass bounds what it may depend on; a Supervisor reaches data only through workflows; a live Actor is reached through a Supervisor that supervises it; a Query reads its Store; any block may use pure logic. Across subsystems: client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A governing pack profile may license intra-subsystem edges the matrix refuses via allowedEdges (its platform idiom, with the stated reason); boundary rules stay unrelaxable.",
|
|
10566
|
+
subsystemBoundaryDepsRule = {
|
|
10567
|
+
name: "subsystem-boundary-dependencies",
|
|
10568
|
+
description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A reference that names nothing in this tree is resolved against the stored surface snapshots: a hit is a DECLARED remote portal and the same Adapter requirement applies to it, snapshots of several providers that disagree make the reference ambiguous, and a reference authored to leave this root that no snapshot covers warns instead of erroring. Boundary rules are never relaxed by a pack profile.",
|
|
10199
10569
|
codes: [
|
|
10200
10570
|
{ code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
|
|
10201
10571
|
{ code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
|
|
10202
10572
|
{ code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
|
|
10203
10573
|
{ code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
|
|
10204
10574
|
{ code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
|
|
10205
|
-
{ code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
|
|
10206
|
-
{ code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
|
|
10207
|
-
{ code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
|
|
10208
|
-
{ code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
|
|
10209
|
-
{ 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)" },
|
|
10210
|
-
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
|
|
10211
|
-
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
|
|
10212
|
-
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
|
|
10213
|
-
{ 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)" },
|
|
10214
|
-
{ 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" },
|
|
10215
|
-
{ 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" },
|
|
10216
|
-
{ 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" },
|
|
10217
|
-
{ code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
|
|
10575
|
+
{ code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" }
|
|
10218
10576
|
],
|
|
10219
10577
|
check(ctx) {
|
|
10220
|
-
for (const
|
|
10221
|
-
|
|
10222
|
-
const
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
if (!depComp) {
|
|
10226
|
-
const external = ctx.isExternalNamespaceRef(depId);
|
|
10227
|
-
if (external || ctx.isCollapsedCrossTreeRef(depId, comp.subsystem)) {
|
|
10228
|
-
const resolved = ctx.resolveSurfaceRef(depId, comp.subsystem);
|
|
10229
|
-
if (resolved.kind === "ambiguous") {
|
|
10230
|
-
ctx.addIssue("error", "SURFACE_REF_AMBIGUOUS", ambiguityMessage(resolved, `Component "${comp.id}" depends on`, depId), comp.id, isDraftCtx);
|
|
10231
|
-
continue;
|
|
10232
|
-
}
|
|
10233
|
-
if (resolved.kind === "resolved") {
|
|
10234
|
-
if (comp.componentType !== "Adapter") {
|
|
10235
|
-
ctx.addIssue(
|
|
10236
|
-
"error",
|
|
10237
|
-
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10238
|
-
`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.`,
|
|
10239
|
-
comp.id,
|
|
10240
|
-
isDraftCtx,
|
|
10241
|
-
true
|
|
10242
|
-
);
|
|
10243
|
-
}
|
|
10244
|
-
continue;
|
|
10245
|
-
}
|
|
10246
|
-
if (external) {
|
|
10247
|
-
ctx.addIssue(
|
|
10248
|
-
"warning",
|
|
10249
|
-
"CROSS_TREE_REF_UNRESOLVED",
|
|
10250
|
-
`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.`,
|
|
10251
|
-
comp.id,
|
|
10252
|
-
isDraftCtx
|
|
10253
|
-
);
|
|
10254
|
-
continue;
|
|
10255
|
-
}
|
|
10256
|
-
}
|
|
10578
|
+
for (const edge of ctx.dependencyEdges().all) {
|
|
10579
|
+
if (edge.retired) continue;
|
|
10580
|
+
const comp = edge.from;
|
|
10581
|
+
switch (edge.reach) {
|
|
10582
|
+
case "missing":
|
|
10257
10583
|
ctx.addIssue(
|
|
10258
10584
|
"error",
|
|
10259
10585
|
"INVALID_DEPENDENCY_REFERENCE",
|
|
10260
|
-
`Component "${comp.id}" lists dependency "${
|
|
10586
|
+
`Component "${comp.id}" lists dependency "${edge.ref}" which does not exist.`,
|
|
10261
10587
|
comp.id,
|
|
10262
|
-
|
|
10588
|
+
edge.draftContext
|
|
10263
10589
|
);
|
|
10264
10590
|
continue;
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
"error",
|
|
10274
|
-
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10275
|
-
`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.`,
|
|
10276
|
-
comp.id,
|
|
10277
|
-
crossDraft
|
|
10278
|
-
);
|
|
10279
|
-
}
|
|
10280
|
-
const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
|
|
10281
|
-
if (!targetIsPublic) {
|
|
10282
|
-
ctx.addIssue(
|
|
10283
|
-
"error",
|
|
10284
|
-
"CROSS_SUBSYSTEM_PRIVATE_ACCESS",
|
|
10285
|
-
`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.`,
|
|
10286
|
-
comp.id,
|
|
10287
|
-
crossDraft
|
|
10288
|
-
);
|
|
10289
|
-
} else if (depComp.componentType !== "Portal") {
|
|
10290
|
-
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
10291
|
-
ctx.addIssue(
|
|
10292
|
-
"error",
|
|
10293
|
-
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
10294
|
-
`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.`,
|
|
10295
|
-
comp.id,
|
|
10296
|
-
crossDraft
|
|
10297
|
-
);
|
|
10298
|
-
}
|
|
10299
|
-
continue;
|
|
10300
|
-
}
|
|
10301
|
-
const governingProfile2 = ctx.ext.profiles[ctx.getComponentProfile(comp.id)];
|
|
10302
|
-
if (governingProfile2?.allowedEdges?.some((e) => e.from.includes(comp.componentType) && e.to.includes(depComp.componentType))) {
|
|
10591
|
+
case "unpinned":
|
|
10592
|
+
ctx.addIssue(
|
|
10593
|
+
"warning",
|
|
10594
|
+
"CROSS_TREE_REF_UNRESOLVED",
|
|
10595
|
+
`Component "${comp.id}" depends on cross-tree component "${edge.ref}", and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
10596
|
+
comp.id,
|
|
10597
|
+
edge.draftContext
|
|
10598
|
+
);
|
|
10303
10599
|
continue;
|
|
10304
|
-
|
|
10305
|
-
const edgeDraft = isDraftCtx || ctx.isComponentDraft(depComp.id);
|
|
10306
|
-
const depPure = isPureLogic(depComp);
|
|
10307
|
-
const depRead = isReadLogic(depComp);
|
|
10308
|
-
const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
|
|
10309
|
-
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
|
|
10600
|
+
case "ambiguous":
|
|
10310
10601
|
ctx.addIssue(
|
|
10311
10602
|
"error",
|
|
10312
|
-
"
|
|
10313
|
-
`
|
|
10603
|
+
"SURFACE_REF_AMBIGUOUS",
|
|
10604
|
+
ambiguityMessage(edge.surface, `Component "${comp.id}" depends on`, edge.ref),
|
|
10314
10605
|
comp.id,
|
|
10315
|
-
|
|
10606
|
+
edge.draftContext
|
|
10316
10607
|
);
|
|
10317
10608
|
continue;
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
if (forbiddenTypes.includes(depComp.componentType)) {
|
|
10609
|
+
case "surface":
|
|
10610
|
+
if (comp.componentType !== "Adapter") {
|
|
10611
|
+
const provider = edge.surface.kind === "resolved" ? edge.surface.snapshot.projectName : "";
|
|
10322
10612
|
ctx.addIssue(
|
|
10323
10613
|
"error",
|
|
10324
|
-
"
|
|
10325
|
-
`
|
|
10614
|
+
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10615
|
+
`Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${edge.ref}", a surface of project "${provider}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
|
|
10326
10616
|
comp.id,
|
|
10327
|
-
|
|
10617
|
+
edge.draftContext,
|
|
10618
|
+
true
|
|
10328
10619
|
);
|
|
10329
10620
|
}
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10621
|
+
continue;
|
|
10622
|
+
case "cross-subsystem":
|
|
10623
|
+
break;
|
|
10624
|
+
default:
|
|
10625
|
+
continue;
|
|
10626
|
+
}
|
|
10627
|
+
const depComp = edge.to;
|
|
10628
|
+
const sourceSub = ctx.subsystems.find((s) => s.id === comp.subsystem);
|
|
10629
|
+
const licensed = sourceSub?.trustedLinks?.some((t) => t.subsystem === depComp.subsystem) ?? false;
|
|
10630
|
+
if (comp.componentType !== "Adapter" && !licensed) {
|
|
10631
|
+
ctx.addIssue(
|
|
10632
|
+
"error",
|
|
10633
|
+
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
10634
|
+
`Boundary violation: ${comp.componentType} "${comp.id}" (subsystem "${comp.subsystem}") depends directly on "${depComp.id}" in subsystem "${depComp.subsystem}". Only a local client Adapter may cross a subsystem boundary \u2014 route this hop through an Adapter that calls "${depComp.subsystem}"'s public interface, or declare a trustedLink on "${comp.subsystem}" (with the reason) to license a direct in-process edge to that peer's published Portal.`,
|
|
10635
|
+
comp.id,
|
|
10636
|
+
edge.draftContext
|
|
10637
|
+
);
|
|
10638
|
+
}
|
|
10639
|
+
const targetIsPublic = ctx.publicSet.get(depComp.subsystem)?.has(depComp.id) ?? false;
|
|
10640
|
+
if (!targetIsPublic) {
|
|
10641
|
+
ctx.addIssue(
|
|
10642
|
+
"error",
|
|
10643
|
+
"CROSS_SUBSYSTEM_PRIVATE_ACCESS",
|
|
10644
|
+
`Boundary violation: "${comp.id}" depends on "${depComp.id}", which is not part of subsystem "${depComp.subsystem}"'s published public surface. Depend on one of its publicInterfaces components instead.`,
|
|
10645
|
+
comp.id,
|
|
10646
|
+
edge.draftContext
|
|
10647
|
+
);
|
|
10648
|
+
} else if (depComp.componentType !== "Portal") {
|
|
10649
|
+
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
10650
|
+
ctx.addIssue(
|
|
10651
|
+
"error",
|
|
10652
|
+
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
10653
|
+
`Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
|
|
10654
|
+
comp.id,
|
|
10655
|
+
edge.draftContext
|
|
10656
|
+
);
|
|
10657
|
+
}
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
};
|
|
10661
|
+
}
|
|
10662
|
+
});
|
|
10663
|
+
|
|
10664
|
+
// src/core/rules/doctrine/stereotype-terms.ts
|
|
10665
|
+
var NEVER_INLINE_STATE, storeResolutionHint, isPureLogic, isReadLogic, stereotypeOf;
|
|
10666
|
+
var init_stereotype_terms = __esm({
|
|
10667
|
+
"src/core/rules/doctrine/stereotype-terms.ts"() {
|
|
10668
|
+
"use strict";
|
|
10669
|
+
NEVER_INLINE_STATE = " Never resolve this by merging the store's state into the consuming component \u2014 state hidden inside a logic block is invisible to the spec and unrecoverable.";
|
|
10670
|
+
storeResolutionHint = (consumerType, storeId) => {
|
|
10671
|
+
if (consumerType === "Orchestrator") {
|
|
10672
|
+
return ` Resolution: wrap "${storeId}" in a Repository pattern (owns: Store + Registry + Index) and depend on that Repository facade instead.${NEVER_INLINE_STATE}`;
|
|
10673
|
+
}
|
|
10674
|
+
return ` Resolution: wrap "${storeId}" in a Repository pattern and reach it through an Orchestrator that uses the Repository facade.${NEVER_INLINE_STATE}`;
|
|
10675
|
+
};
|
|
10676
|
+
isPureLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "pure";
|
|
10677
|
+
isReadLogic = (comp) => comp.componentType === "Orchestrator" && comp.dependencyClass === "read";
|
|
10678
|
+
stereotypeOf = (comp) => comp.dependencyClass ? `${comp.componentType} (dependencyClass ${comp.dependencyClass})` : comp.componentType;
|
|
10679
|
+
}
|
|
10680
|
+
});
|
|
10681
|
+
|
|
10682
|
+
// src/core/rules/doctrine/logic-dependency-class.ts
|
|
10683
|
+
var logicDependencyClassRule;
|
|
10684
|
+
var init_logic_dependency_class = __esm({
|
|
10685
|
+
"src/core/rules/doctrine/logic-dependency-class.ts"() {
|
|
10686
|
+
"use strict";
|
|
10687
|
+
init_stereotype_terms();
|
|
10688
|
+
logicDependencyClassRule = {
|
|
10689
|
+
name: "logic-dependency-class",
|
|
10690
|
+
description: "Bounds an Orchestrator's dependencies by the dependencyClass it declares: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters; an Orchestrator that declares no class is a workflow and may depend on anything the rest of the matrix allows. Whether read logic calls a WRITE method on one of those is judged only once facade methods carry effect tags.",
|
|
10691
|
+
codes: [
|
|
10692
|
+
{ code: "DEPENDENCY_CLASS_VIOLATION", defaultSeverity: "error", summary: "Orchestrator depending on a component its dependencyClass does not allow: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters" }
|
|
10693
|
+
],
|
|
10694
|
+
check(ctx) {
|
|
10695
|
+
for (const edge of ctx.dependencyEdges().matrix) {
|
|
10696
|
+
const comp = edge.from;
|
|
10697
|
+
const depComp = edge.to;
|
|
10698
|
+
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
|
|
10699
|
+
const depPure = isPureLogic(depComp);
|
|
10700
|
+
const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
|
|
10701
|
+
if (isPureLogic(comp) && !depPure) {
|
|
10702
|
+
ctx.addIssue(
|
|
10703
|
+
"error",
|
|
10704
|
+
"DEPENDENCY_CLASS_VIOLATION",
|
|
10705
|
+
`Architectural violation: Orchestrator "${comp.id}" (dependencyClass pure) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Pure logic depends only on pure Orchestrators \u2014 move that dependency to the workflow that calls "${comp.id}", or declare "${comp.id}" read when it reads through read logic, a Repository, an Index or an Adapter.` + storeHint,
|
|
10706
|
+
comp.id,
|
|
10707
|
+
edge.draftContext
|
|
10708
|
+
);
|
|
10709
|
+
}
|
|
10710
|
+
if (isReadLogic(comp) && !depPure && !isReadLogic(depComp) && !["Repository", "Index", "Adapter"].includes(depComp.componentType)) {
|
|
10711
|
+
ctx.addIssue(
|
|
10712
|
+
"error",
|
|
10713
|
+
"DEPENDENCY_CLASS_VIOLATION",
|
|
10714
|
+
`Architectural violation: Orchestrator "${comp.id}" (dependencyClass read) cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". Read logic depends only on pure or read Orchestrators, Repositories, Indexes and Adapters \u2014 move that dependency to the workflow that calls "${comp.id}", or unset the dependencyClass of "${comp.id}" when it is itself a workflow.` + storeHint,
|
|
10715
|
+
comp.id,
|
|
10716
|
+
edge.draftContext
|
|
10717
|
+
);
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
}
|
|
10721
|
+
};
|
|
10722
|
+
}
|
|
10723
|
+
});
|
|
10724
|
+
|
|
10725
|
+
// src/core/rules/doctrine/data-block-dependencies.ts
|
|
10726
|
+
var dataBlockDepsRule;
|
|
10727
|
+
var init_data_block_dependencies = __esm({
|
|
10728
|
+
"src/core/rules/doctrine/data-block-dependencies.ts"() {
|
|
10729
|
+
"use strict";
|
|
10730
|
+
init_stereotype_terms();
|
|
10731
|
+
dataBlockDepsRule = {
|
|
10732
|
+
name: "data-block-dependencies",
|
|
10733
|
+
description: "Keeps the data layer facing down. A Store may depend only on another Store, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse. A Registry is the write path to its Store and may depend on that Store, a backend Adapter, or the pure logic that validates the write. An Index is a read projection and a Query a computed read, each over its Store through a backend Adapter where one serves it. An Adapter is a sink toward the system: it may use pure logic, but never a Store or any other Orchestrator.",
|
|
10734
|
+
codes: [
|
|
10735
|
+
{ code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
|
|
10736
|
+
{ code: "ARCHITECTURE_VIOLATION_REGISTRY_DEP", defaultSeverity: "warning", summary: "Registry depending on anything but its Store, a backend Adapter or pure logic (warning while new; aligned to the standard \xA77 validate\u2192write path)" },
|
|
10737
|
+
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
|
|
10738
|
+
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or pure logic" },
|
|
10739
|
+
{ code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
|
|
10740
|
+
],
|
|
10741
|
+
check(ctx) {
|
|
10742
|
+
for (const edge of ctx.dependencyEdges().matrix) {
|
|
10743
|
+
const comp = edge.from;
|
|
10744
|
+
const depComp = edge.to;
|
|
10745
|
+
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") continue;
|
|
10746
|
+
const depPure = isPureLogic(depComp);
|
|
10747
|
+
const ownStoreOnly = depComp.componentType !== "Store" && depComp.componentType !== "Adapter" && !depPure;
|
|
10748
|
+
if (comp.componentType === "Store" && ownStoreOnly) {
|
|
10749
|
+
ctx.addIssue(
|
|
10750
|
+
"error",
|
|
10751
|
+
"ARCHITECTURE_VIOLATION_STORE_DEP",
|
|
10752
|
+
`Architectural violation: Store component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". Stores may only depend on other Stores, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse.`,
|
|
10753
|
+
comp.id,
|
|
10754
|
+
edge.draftContext
|
|
10755
|
+
);
|
|
10756
|
+
}
|
|
10757
|
+
if (comp.componentType === "Registry" && ownStoreOnly) {
|
|
10758
|
+
ctx.addIssue(
|
|
10759
|
+
"warning",
|
|
10760
|
+
"ARCHITECTURE_VIOLATION_REGISTRY_DEP",
|
|
10761
|
+
`Architectural violation: Registry component "${comp.id}" should not depend on "${depComp.componentType}" component "${depComp.id}". A Registry is the write path to its Store and may depend only on that Store, a backend Adapter, or pure logic that validates the write \u2014 the Registry never updates Indexes and never drives workflow.`,
|
|
10762
|
+
comp.id,
|
|
10763
|
+
edge.draftContext
|
|
10764
|
+
);
|
|
10765
|
+
}
|
|
10766
|
+
if (comp.componentType === "Adapter" && (depComp.componentType === "Store" || depComp.componentType === "Orchestrator" && !depPure)) {
|
|
10767
|
+
ctx.addIssue(
|
|
10768
|
+
"error",
|
|
10769
|
+
"ARCHITECTURE_VIOLATION_ADAPTER_DEP",
|
|
10770
|
+
`Architectural violation: Adapter component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". An Adapter may use pure logic, but never a Store or any other Orchestrator.`,
|
|
10771
|
+
comp.id,
|
|
10772
|
+
edge.draftContext
|
|
10773
|
+
);
|
|
10774
|
+
}
|
|
10775
|
+
if (comp.componentType === "Index" && ownStoreOnly) {
|
|
10776
|
+
ctx.addIssue(
|
|
10777
|
+
"error",
|
|
10778
|
+
"ARCHITECTURE_VIOLATION_INDEX_DEP",
|
|
10779
|
+
`Architectural violation: Index component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". An Index is a read projection and may depend only on its Store, a backend Adapter or pure logic.`,
|
|
10780
|
+
comp.id,
|
|
10781
|
+
edge.draftContext
|
|
10782
|
+
);
|
|
10783
|
+
}
|
|
10784
|
+
if (comp.componentType === "Query" && ownStoreOnly) {
|
|
10785
|
+
ctx.addIssue(
|
|
10786
|
+
"error",
|
|
10787
|
+
"ARCHITECTURE_VIOLATION_QUERY_DEP",
|
|
10788
|
+
`Architectural violation: Query "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Query computes reads over its Store, through a backend Adapter where one serves it, with pure logic \u2014 move any other dependency to the workflow that calls the Repository facade.`,
|
|
10789
|
+
comp.id,
|
|
10790
|
+
edge.draftContext
|
|
10791
|
+
);
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
10795
|
+
};
|
|
10796
|
+
}
|
|
10797
|
+
});
|
|
10798
|
+
|
|
10799
|
+
// src/core/rules/doctrine/entrypoint-dependencies.ts
|
|
10800
|
+
var entrypointDepsRule;
|
|
10801
|
+
var init_entrypoint_dependencies = __esm({
|
|
10802
|
+
"src/core/rules/doctrine/entrypoint-dependencies.ts"() {
|
|
10803
|
+
"use strict";
|
|
10804
|
+
init_stereotype_terms();
|
|
10805
|
+
entrypointDepsRule = {
|
|
10806
|
+
name: "entrypoint-dependencies",
|
|
10807
|
+
description: "Judges the edges at the system's entry points and its process layer. Portals and Observers are top-level entry points and subscribers, so nothing may depend on them \u2014 and that is the edge's one finding, which is why no other matrix rule judges it. Downward, a Portal dispatches to Orchestrators and may READ through Indexes and Repository facades but never reaches Store/Registry/Query or an Adapter, while an Observer forwards to one Orchestrator or Supervisor over a message-bus Adapter. A View stays a passive presenter. A Supervisor reaches data only through workflows, and a component depending on a live Actor must also depend on a Supervisor that supervises it.",
|
|
10808
|
+
codes: [
|
|
10809
|
+
{ code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
|
|
10810
|
+
{ code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
|
|
10811
|
+
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
|
|
10812
|
+
{ code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on anything but Actors, Orchestrators, Adapters or other Supervisors \u2014 it reaches data only through workflows" },
|
|
10813
|
+
{ code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise, without also depending on a Supervisor that supervises it \u2014 a live Actor is reached by id through its Supervisor" }
|
|
10814
|
+
],
|
|
10815
|
+
check(ctx) {
|
|
10816
|
+
for (const edge of ctx.dependencyEdges().matrix) {
|
|
10817
|
+
const comp = edge.from;
|
|
10818
|
+
const depComp = edge.to;
|
|
10819
|
+
const storeHint = depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : "";
|
|
10820
|
+
if (depComp.componentType === "Portal" || depComp.componentType === "Observer") {
|
|
10821
|
+
ctx.addIssue(
|
|
10822
|
+
"error",
|
|
10823
|
+
"ARCHITECTURE_VIOLATION_PORTAL_DEP",
|
|
10824
|
+
`Architectural violation: Component "${comp.id}" cannot depend on ${depComp.componentType} component "${depComp.id}". ${depComp.componentType}s are top-level entry points/subscribers and cannot be dependencies.`,
|
|
10825
|
+
comp.id,
|
|
10826
|
+
edge.draftContext
|
|
10827
|
+
);
|
|
10828
|
+
continue;
|
|
10829
|
+
}
|
|
10830
|
+
if (comp.componentType === "Portal" || comp.componentType === "Observer") {
|
|
10831
|
+
const forbiddenTypes = comp.componentType === "Portal" ? ["Store", "Registry", "Adapter", "Query"] : ["Store", "Registry", "Repository", "Index", "Query"];
|
|
10832
|
+
if (forbiddenTypes.includes(depComp.componentType)) {
|
|
10395
10833
|
ctx.addIssue(
|
|
10396
10834
|
"error",
|
|
10397
|
-
"
|
|
10398
|
-
`Architectural violation:
|
|
10835
|
+
"ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP",
|
|
10836
|
+
`Architectural violation: ${comp.componentType} component "${comp.id}" cannot depend directly on "${depComp.componentType}" component "${depComp.id}". ${comp.componentType}s coordinate through Orchestrators (and Supervisors); they do not reach the data layer directly.` + storeHint,
|
|
10399
10837
|
comp.id,
|
|
10400
|
-
|
|
10838
|
+
edge.draftContext
|
|
10401
10839
|
);
|
|
10402
10840
|
}
|
|
10403
|
-
|
|
10841
|
+
}
|
|
10842
|
+
if (comp.componentType === "View" && (["Store", "Registry", "Index", "Query", "Adapter", "Repository"].includes(depComp.componentType) || depComp.componentType === "Orchestrator" && !isPureLogic(depComp))) {
|
|
10843
|
+
ctx.addIssue(
|
|
10844
|
+
"error",
|
|
10845
|
+
"ARCHITECTURE_VIOLATION_VIEW_DEP",
|
|
10846
|
+
`Architectural violation: View component "${comp.id}" cannot depend on ${stereotypeOf(depComp)} component "${depComp.id}". Views must remain passive UI blocks that use pure logic at most.` + storeHint,
|
|
10847
|
+
comp.id,
|
|
10848
|
+
edge.draftContext
|
|
10849
|
+
);
|
|
10850
|
+
}
|
|
10851
|
+
if (comp.componentType === "Supervisor" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
|
|
10852
|
+
ctx.addIssue(
|
|
10853
|
+
"error",
|
|
10854
|
+
"ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
|
|
10855
|
+
`Architectural violation: Supervisor "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Supervisor reaches data only through workflows \u2014 depend on the Orchestrator, Actor or Adapter that does that work instead.` + storeHint,
|
|
10856
|
+
comp.id,
|
|
10857
|
+
edge.draftContext
|
|
10858
|
+
);
|
|
10859
|
+
}
|
|
10860
|
+
if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
|
|
10861
|
+
const reachedThroughSupervisor = comp.dependsOn.some((id) => {
|
|
10862
|
+
const supervisor = ctx.componentMap.get(id);
|
|
10863
|
+
return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
|
|
10864
|
+
});
|
|
10865
|
+
if (!reachedThroughSupervisor) {
|
|
10866
|
+
const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
|
|
10867
|
+
const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
|
|
10404
10868
|
ctx.addIssue(
|
|
10405
10869
|
"error",
|
|
10406
|
-
"
|
|
10407
|
-
`Architectural violation:
|
|
10870
|
+
"ACTOR_REACHED_WITHOUT_SUPERVISOR",
|
|
10871
|
+
`Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
|
|
10408
10872
|
comp.id,
|
|
10409
|
-
|
|
10873
|
+
edge.draftContext
|
|
10410
10874
|
);
|
|
10411
10875
|
}
|
|
10412
|
-
if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
|
|
10413
|
-
const reachedThroughSupervisor = comp.dependsOn.some((id) => {
|
|
10414
|
-
const supervisor = ctx.componentMap.get(id);
|
|
10415
|
-
return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
|
|
10416
|
-
});
|
|
10417
|
-
if (!reachedThroughSupervisor) {
|
|
10418
|
-
const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
|
|
10419
|
-
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`;
|
|
10420
|
-
ctx.addIssue(
|
|
10421
|
-
"error",
|
|
10422
|
-
"ACTOR_REACHED_WITHOUT_SUPERVISOR",
|
|
10423
|
-
`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}.`,
|
|
10424
|
-
comp.id,
|
|
10425
|
-
edgeDraft
|
|
10426
|
-
);
|
|
10427
|
-
}
|
|
10428
|
-
}
|
|
10429
10876
|
}
|
|
10430
10877
|
}
|
|
10878
|
+
}
|
|
10879
|
+
};
|
|
10880
|
+
}
|
|
10881
|
+
});
|
|
10882
|
+
|
|
10883
|
+
// src/core/rules/doctrine/portal-write-shortcut.ts
|
|
10884
|
+
var portalWriteShortcutRule;
|
|
10885
|
+
var init_portal_write_shortcut = __esm({
|
|
10886
|
+
"src/core/rules/doctrine/portal-write-shortcut.ts"() {
|
|
10887
|
+
"use strict";
|
|
10888
|
+
portalWriteShortcutRule = {
|
|
10889
|
+
name: "portal-write-shortcut",
|
|
10890
|
+
description: "A Portal narrative call step or dispatch-table binding that reaches a write-effect method on a Repository or Index directly is the write shortcut: the Portal\u2192data-facade edge is licensed for reads only, and a write routes through an Orchestrator that owns the workflow. A dispatch step reaches its server only through a table binding, so judging every binding judges each dispatch step that takes it, once, where the route is declared. Methods that carry no effect tag are not judged.",
|
|
10891
|
+
codes: [
|
|
10892
|
+
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
|
|
10893
|
+
],
|
|
10894
|
+
check(ctx) {
|
|
10431
10895
|
for (const impl of ctx.implementations) {
|
|
10432
10896
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10433
10897
|
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
@@ -10472,31 +10936,23 @@ var init_stereotype_dependencies = __esm({
|
|
|
10472
10936
|
}
|
|
10473
10937
|
});
|
|
10474
10938
|
|
|
10475
|
-
// src/core/rules/doctrine/pattern-
|
|
10476
|
-
var
|
|
10477
|
-
var
|
|
10478
|
-
"src/core/rules/doctrine/pattern-
|
|
10939
|
+
// src/core/rules/doctrine/pattern-membership.ts
|
|
10940
|
+
var patternMembershipRule;
|
|
10941
|
+
var init_pattern_membership = __esm({
|
|
10942
|
+
"src/core/rules/doctrine/pattern-membership.ts"() {
|
|
10479
10943
|
"use strict";
|
|
10480
10944
|
init_models();
|
|
10481
|
-
|
|
10482
|
-
name: "pattern-
|
|
10483
|
-
description: "
|
|
10945
|
+
patternMembershipRule = {
|
|
10946
|
+
name: "pattern-membership",
|
|
10947
|
+
description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern to claim it.",
|
|
10484
10948
|
codes: [
|
|
10485
10949
|
{ code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
|
|
10486
10950
|
{ code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block using owns" },
|
|
10487
10951
|
{ code: "INVALID_OWNED_MEMBER", defaultSeverity: "error", summary: "owns names a non-existent component" },
|
|
10488
|
-
{ code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
|
|
10489
|
-
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
|
|
10490
|
-
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
|
|
10491
|
-
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
10492
|
-
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
|
|
10493
|
-
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
10494
|
-
{ 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" },
|
|
10495
|
-
{ 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" },
|
|
10496
|
-
{ code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
|
|
10952
|
+
{ code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
|
|
10953
|
+
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
|
|
10497
10954
|
],
|
|
10498
10955
|
check(ctx) {
|
|
10499
|
-
const ownedBy = /* @__PURE__ */ new Map();
|
|
10500
10956
|
for (const comp of ctx.components) {
|
|
10501
10957
|
if (isRetired(comp)) continue;
|
|
10502
10958
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
@@ -10507,6 +10963,12 @@ var init_pattern_ownership = __esm({
|
|
|
10507
10963
|
if (!pattern && comp.owns.length > 0) {
|
|
10508
10964
|
ctx.addIssue("error", "BLOCK_OWNS_MEMBERS", `Building block "${comp.id}" (${comp.componentType}) cannot own members; only patterns (${Array.from(PATTERN_TYPES).join("/")}) use "owns".`, comp.id, isDraftCtx);
|
|
10509
10965
|
}
|
|
10966
|
+
}
|
|
10967
|
+
const ownership = ctx.ownershipIndex();
|
|
10968
|
+
for (const comp of ctx.components) {
|
|
10969
|
+
if (isRetired(comp)) continue;
|
|
10970
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10971
|
+
const pattern = isPattern(comp);
|
|
10510
10972
|
for (const memberId of comp.owns) {
|
|
10511
10973
|
const member = ctx.componentMap.get(memberId);
|
|
10512
10974
|
if (!member) {
|
|
@@ -10518,26 +10980,50 @@ var init_pattern_ownership = __esm({
|
|
|
10518
10980
|
ctx.addIssue("error", "PATTERN_OWNS_PATTERN", `Pattern "${comp.id}" owns "${memberId}", which is itself a pattern. Patterns own only building blocks \u2014 compose patterns at the subsystem (L1) level.`, comp.id, isDraftCtx);
|
|
10519
10981
|
continue;
|
|
10520
10982
|
}
|
|
10521
|
-
const firstOwner =
|
|
10983
|
+
const firstOwner = ownership.ownerOf(memberId);
|
|
10522
10984
|
if (firstOwner && firstOwner !== comp.id) {
|
|
10523
10985
|
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${firstOwner}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
|
|
10524
10986
|
}
|
|
10525
|
-
if (!firstOwner) ownedBy.set(memberId, comp.id);
|
|
10526
10987
|
}
|
|
10527
|
-
|
|
10988
|
+
}
|
|
10989
|
+
}
|
|
10990
|
+
};
|
|
10991
|
+
}
|
|
10992
|
+
});
|
|
10993
|
+
|
|
10994
|
+
// src/core/rules/doctrine/pattern-containment.ts
|
|
10995
|
+
var REPOSITORY_MEMBERS, patternContainmentRule;
|
|
10996
|
+
var init_pattern_containment = __esm({
|
|
10997
|
+
"src/core/rules/doctrine/pattern-containment.ts"() {
|
|
10998
|
+
"use strict";
|
|
10999
|
+
init_models();
|
|
11000
|
+
REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
|
|
11001
|
+
patternContainmentRule = {
|
|
11002
|
+
name: "pattern-containment",
|
|
11003
|
+
description: "Holds each pattern to the containment its definition prescribes. A Repository may own only Store, Registry, Index, Query and (optionally) Adapter, judged member by member. A FeatureComponent owns exactly one Orchestrator (the logic side) and one or more Views (its faces \u2014 list, detail, form \u2014 sharing the one logic component), and nothing else. A RouterComponent owns exactly one Portal as its facade and at least one other child to route to. A counting pattern that still owns a retired member is not judged until that member is migrated: its counts change with the migration, and STEREOTYPE_RETIRED is the one finding.",
|
|
11004
|
+
codes: [
|
|
11005
|
+
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
|
|
11006
|
+
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
11007
|
+
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" }
|
|
11008
|
+
],
|
|
11009
|
+
check(ctx) {
|
|
11010
|
+
for (const comp of ctx.components) {
|
|
11011
|
+
if (comp.componentType !== "Repository" || isRetired(comp)) continue;
|
|
11012
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11013
|
+
for (const memberId of comp.owns) {
|
|
10528
11014
|
const member = ctx.componentMap.get(memberId);
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
continue;
|
|
10532
|
-
}
|
|
10533
|
-
if (comp.componentType === "Repository") {
|
|
10534
|
-
const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
|
|
10535
|
-
for (const memberId of comp.owns) {
|
|
10536
|
-
const member = ctx.componentMap.get(memberId);
|
|
10537
|
-
if (!member || isRetired(member) || allowed.has(member.componentType)) continue;
|
|
10538
|
-
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);
|
|
10539
|
-
}
|
|
11015
|
+
if (!member || isRetired(member) || REPOSITORY_MEMBERS.has(member.componentType)) continue;
|
|
11016
|
+
ctx.addIssue("error", "REPOSITORY_CONTAINMENT", `Repository "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Repository may own only Store, Registry, Index, Query, and (optionally) Adapter.`, comp.id, isDraftCtx);
|
|
10540
11017
|
}
|
|
11018
|
+
}
|
|
11019
|
+
for (const comp of ctx.components) {
|
|
11020
|
+
if (comp.componentType !== "FeatureComponent" && comp.componentType !== "RouterComponent") continue;
|
|
11021
|
+
if (isRetired(comp)) continue;
|
|
11022
|
+
if (comp.owns.some((memberId) => {
|
|
11023
|
+
const member = ctx.componentMap.get(memberId);
|
|
11024
|
+
return member !== void 0 && isRetired(member);
|
|
11025
|
+
})) continue;
|
|
11026
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10541
11027
|
if (comp.componentType === "FeatureComponent") {
|
|
10542
11028
|
let orchestrators = 0;
|
|
10543
11029
|
let views = 0;
|
|
@@ -10568,45 +11054,83 @@ var init_pattern_ownership = __esm({
|
|
|
10568
11054
|
}
|
|
10569
11055
|
}
|
|
10570
11056
|
}
|
|
11057
|
+
}
|
|
11058
|
+
};
|
|
11059
|
+
}
|
|
11060
|
+
});
|
|
11061
|
+
|
|
11062
|
+
// src/core/rules/doctrine/unowned-blocks.ts
|
|
11063
|
+
var unownedBlocksRule;
|
|
11064
|
+
var init_unowned_blocks = __esm({
|
|
11065
|
+
"src/core/rules/doctrine/unowned-blocks.ts"() {
|
|
11066
|
+
"use strict";
|
|
11067
|
+
unownedBlocksRule = {
|
|
11068
|
+
name: "unowned-blocks",
|
|
11069
|
+
description: `Judges the data blocks no pattern owns. A Store may stand alone deliberately \u2014 the sanctioned lightweight form for genuinely simple state, acknowledged with a lint.allow \u2014 while the recommended shape stays a Repository; what must never happen is folding the state into a consuming component, where no spec, diagram or conformance check can see it again. A Registry standing alone with no Store to write to is either mistyped (the "file-backed Registry" idiom, a fused persistent store that belongs typed Store) or orphaned. A Query has no standalone form at all: it computes reads over its own Repository's Store.`,
|
|
11070
|
+
codes: [
|
|
11071
|
+
{ code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
|
|
11072
|
+
{ code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" },
|
|
11073
|
+
{ code: "UNOWNED_QUERY", defaultSeverity: "error", summary: "Query not owned by a Repository \u2014 a Query computes reads over its own Repository's Store" }
|
|
11074
|
+
],
|
|
11075
|
+
check(ctx) {
|
|
11076
|
+
const ownership = ctx.ownershipIndex();
|
|
10571
11077
|
for (const comp of ctx.components) {
|
|
10572
|
-
if (
|
|
10573
|
-
ctx.
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
);
|
|
11078
|
+
if (ownership.ownedMembers.has(comp.id)) continue;
|
|
11079
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11080
|
+
if (comp.componentType === "Store") {
|
|
11081
|
+
ctx.addIssue(
|
|
11082
|
+
"warning",
|
|
11083
|
+
"UNOWNED_STORE",
|
|
11084
|
+
`Store "${comp.id}" is not owned by any pattern. The recommended shape for held state is a Repository (owns: ["${comp.id}", its Registry, its Index]) with consumers on the facade. For genuinely simple state a deliberately standalone Store is the sanctioned lightweight form \u2014 keep it visible as this Store, reachable from the workflow layer (Orchestrator/Supervisor/Actor), and acknowledge it with a lint.allow reason. Never take the third path of merging the state into a consuming component: state hidden inside a logic block disappears from the architecture permanently.`,
|
|
11085
|
+
comp.id,
|
|
11086
|
+
isDraftCtx
|
|
11087
|
+
);
|
|
11088
|
+
}
|
|
11089
|
+
if (comp.componentType === "Registry" && !comp.dependsOn.some((depId) => ctx.componentMap.get(depId)?.componentType === "Store")) {
|
|
11090
|
+
ctx.addIssue(
|
|
11091
|
+
"warning",
|
|
11092
|
+
"REGISTRY_WITHOUT_STORE",
|
|
11093
|
+
`Registry "${comp.id}" stands alone with no Store to write to. A Registry is the write path to a Store \u2014 if this component itself holds the persisted state (a file-backed record/config store), retype it as a Store with the honest durability (read-through for no-RAM-copy file I/O) so the durability machinery can see it; otherwise wire its Store, or lint.allow with a reason.`,
|
|
11094
|
+
comp.id,
|
|
11095
|
+
isDraftCtx
|
|
11096
|
+
);
|
|
11097
|
+
}
|
|
11098
|
+
if (comp.componentType === "Query") {
|
|
11099
|
+
ctx.addIssue(
|
|
11100
|
+
"error",
|
|
11101
|
+
"UNOWNED_QUERY",
|
|
11102
|
+
`Query "${comp.id}" is not owned by any pattern. A Query computes reads over its own Repository's Store \u2014 make it a member of the Repository whose Store it reads (owns: ["${comp.id}", \u2026]).`,
|
|
11103
|
+
comp.id,
|
|
11104
|
+
isDraftCtx
|
|
11105
|
+
);
|
|
11106
|
+
}
|
|
10602
11107
|
}
|
|
11108
|
+
}
|
|
11109
|
+
};
|
|
11110
|
+
}
|
|
11111
|
+
});
|
|
11112
|
+
|
|
11113
|
+
// src/core/rules/doctrine/member-visibility.ts
|
|
11114
|
+
var memberVisibilityRule;
|
|
11115
|
+
var init_member_visibility = __esm({
|
|
11116
|
+
"src/core/rules/doctrine/member-visibility.ts"() {
|
|
11117
|
+
"use strict";
|
|
11118
|
+
init_models();
|
|
11119
|
+
memberVisibilityRule = {
|
|
11120
|
+
name: "member-visibility",
|
|
11121
|
+
description: "A component may depend on a block within its own group (it is the owning pattern, or a sibling member of the same pattern), on any pattern facade, or on a standalone block \u2014 never on a block privately owned by ANOTHER pattern, which must be reached through that pattern's facade. A retired depending component is skipped, as everywhere in this family.",
|
|
11122
|
+
codes: [
|
|
11123
|
+
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
|
|
11124
|
+
],
|
|
11125
|
+
check(ctx) {
|
|
11126
|
+
const ownership = ctx.ownershipIndex();
|
|
10603
11127
|
for (const comp of ctx.components) {
|
|
10604
11128
|
if (isRetired(comp)) continue;
|
|
10605
11129
|
for (const depId of comp.dependsOn) {
|
|
10606
|
-
const owner =
|
|
11130
|
+
const owner = ownership.ownerOf(depId);
|
|
10607
11131
|
if (!owner) continue;
|
|
10608
11132
|
if (owner === comp.id) continue;
|
|
10609
|
-
if (
|
|
11133
|
+
if (ownership.ownerOf(comp.id) === owner) continue;
|
|
10610
11134
|
ctx.addIssue("error", "VISIBILITY_VIOLATION", `Component "${comp.id}" depends on "${depId}", which is privately owned by pattern "${owner}". Depend on the facade "${owner}" instead.`, comp.id, ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(depId));
|
|
10611
11135
|
}
|
|
10612
11136
|
}
|
|
@@ -10661,28 +11185,28 @@ var init_facade_forwarding = __esm({
|
|
|
10661
11185
|
}
|
|
10662
11186
|
});
|
|
10663
11187
|
|
|
10664
|
-
// src/core/rules/extension/
|
|
10665
|
-
var
|
|
10666
|
-
var
|
|
10667
|
-
"src/core/rules/extension/
|
|
11188
|
+
// src/core/rules/extension/profile-registration.ts
|
|
11189
|
+
var PROJECT_KINDS2, profileRegistrationRule;
|
|
11190
|
+
var init_profile_registration = __esm({
|
|
11191
|
+
"src/core/rules/extension/profile-registration.ts"() {
|
|
10668
11192
|
"use strict";
|
|
10669
11193
|
init_types();
|
|
10670
|
-
BACKEND_LIKE = /* @__PURE__ */ new Set(["backend", "lowlevel-os", "game-ecs", "realtime-embedded", "plc-cyclic"]);
|
|
10671
|
-
FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
|
|
10672
11194
|
PROJECT_KINDS2 = new Set(PROJECT_KINDS);
|
|
10673
|
-
|
|
10674
|
-
name: "
|
|
10675
|
-
description: "
|
|
11195
|
+
profileRegistrationRule = {
|
|
11196
|
+
name: "profile-registration",
|
|
11197
|
+
description: "Every profile name in play \u2014 the project's own projectType and each subsystem's declared profile \u2014 must be a built-in profile, a composite project kind, or one a loaded extension pack registers. An unregistered name is flagged rather than guessed at: no doctrine is governing it.",
|
|
10676
11198
|
codes: [
|
|
10677
|
-
{ code: "FRONTEND_STEREOTYPE_IN_BACKEND", defaultSeverity: "error", summary: "Frontend stereotype in a backend-oriented subsystem" },
|
|
10678
|
-
{ code: "PLC_CYCLIC_CONCURRENCY_VIOLATION", defaultSeverity: "error", summary: "Concurrent stereotype in a PLC-cyclic profile" },
|
|
10679
|
-
{ code: "BACKEND_STEREOTYPE_IN_FRONTEND", defaultSeverity: "warning", summary: "Backend runtime stereotype in a frontend subsystem" },
|
|
10680
|
-
{ code: "PROFILE_FORBIDDEN_STEREOTYPE", defaultSeverity: "error", summary: "Stereotype forbidden by the governing pack-defined profile" },
|
|
10681
|
-
{ code: "PROFILE_DISCOURAGED_STEREOTYPE", defaultSeverity: "warning", summary: "Stereotype discouraged by the governing pack-defined profile" },
|
|
10682
11199
|
{ code: "UNKNOWN_PROFILE", defaultSeverity: "warning", summary: "Profile name is neither built-in nor registered by an extension pack" }
|
|
10683
11200
|
],
|
|
10684
11201
|
check(ctx) {
|
|
10685
11202
|
const registered = /* @__PURE__ */ new Set([...BUILTIN_PROFILES, ...Object.keys(ctx.ext.profiles)]);
|
|
11203
|
+
if (!registered.has(ctx.projectType) && !PROJECT_KINDS2.has(ctx.projectType)) {
|
|
11204
|
+
ctx.addIssue(
|
|
11205
|
+
"warning",
|
|
11206
|
+
"UNKNOWN_PROFILE",
|
|
11207
|
+
`projectType "${ctx.projectType}" (project.yaml) is neither a built-in profile/kind nor registered by a loaded extension pack. Components default to backend doctrine.`
|
|
11208
|
+
);
|
|
11209
|
+
}
|
|
10686
11210
|
for (const sub of ctx.subsystems) {
|
|
10687
11211
|
if (sub.profile && !registered.has(sub.profile)) {
|
|
10688
11212
|
ctx.addIssue(
|
|
@@ -10693,13 +11217,27 @@ var init_architectural_profiles = __esm({
|
|
|
10693
11217
|
);
|
|
10694
11218
|
}
|
|
10695
11219
|
}
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
11220
|
+
}
|
|
11221
|
+
};
|
|
11222
|
+
}
|
|
11223
|
+
});
|
|
11224
|
+
|
|
11225
|
+
// src/core/rules/extension/profile-stereotype-fencing.ts
|
|
11226
|
+
var BACKEND_LIKE, FRONTEND_LIKE, profileStereotypeFencingRule;
|
|
11227
|
+
var init_profile_stereotype_fencing = __esm({
|
|
11228
|
+
"src/core/rules/extension/profile-stereotype-fencing.ts"() {
|
|
11229
|
+
"use strict";
|
|
11230
|
+
BACKEND_LIKE = /* @__PURE__ */ new Set(["backend", "lowlevel-os", "game-ecs", "realtime-embedded", "plc-cyclic"]);
|
|
11231
|
+
FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
|
|
11232
|
+
profileStereotypeFencingRule = {
|
|
11233
|
+
name: "profile-stereotype-fencing",
|
|
11234
|
+
description: "The built-in profile families fence which stereotypes may run under them: View/FeatureComponent/RouterComponent only in a frontend-like profile; Actor/Supervisor forbidden in plc-cyclic, whose logic runs single-threaded inside one execution scan cycle; Actor/Supervisor in a frontend-like profile warned. A pack profile opts in by declaring its family; an unregistered profile is neutral, with no doctrine to guess.",
|
|
11235
|
+
codes: [
|
|
11236
|
+
{ code: "FRONTEND_STEREOTYPE_IN_BACKEND", defaultSeverity: "error", summary: "Frontend stereotype in a backend-oriented subsystem" },
|
|
11237
|
+
{ code: "PLC_CYCLIC_CONCURRENCY_VIOLATION", defaultSeverity: "error", summary: "Concurrent stereotype in a PLC-cyclic profile" },
|
|
11238
|
+
{ code: "BACKEND_STEREOTYPE_IN_FRONTEND", defaultSeverity: "warning", summary: "Backend runtime stereotype in a frontend subsystem" }
|
|
11239
|
+
],
|
|
11240
|
+
check(ctx) {
|
|
10703
11241
|
for (const comp of ctx.components) {
|
|
10704
11242
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10705
11243
|
const profile = ctx.getComponentProfile(comp.id);
|
|
@@ -10739,28 +11277,50 @@ var init_architectural_profiles = __esm({
|
|
|
10739
11277
|
);
|
|
10740
11278
|
}
|
|
10741
11279
|
}
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
11280
|
+
}
|
|
11281
|
+
}
|
|
11282
|
+
};
|
|
11283
|
+
}
|
|
11284
|
+
});
|
|
11285
|
+
|
|
11286
|
+
// src/core/rules/extension/pack-profile-stereotypes.ts
|
|
11287
|
+
var packProfileStereotypesRule;
|
|
11288
|
+
var init_pack_profile_stereotypes = __esm({
|
|
11289
|
+
"src/core/rules/extension/pack-profile-stereotypes.ts"() {
|
|
11290
|
+
"use strict";
|
|
11291
|
+
packProfileStereotypesRule = {
|
|
11292
|
+
name: "pack-profile-stereotypes",
|
|
11293
|
+
description: "A pack-registered profile carries its own stereotype doctrine on top of the built-in family fencing: the stereotypes it lists as forbidden are errors and the ones it discourages are warnings, each reported with the reason the pack states for it.",
|
|
11294
|
+
codes: [
|
|
11295
|
+
{ code: "PROFILE_FORBIDDEN_STEREOTYPE", defaultSeverity: "error", summary: "Stereotype forbidden by the governing pack-defined profile" },
|
|
11296
|
+
{ code: "PROFILE_DISCOURAGED_STEREOTYPE", defaultSeverity: "warning", summary: "Stereotype discouraged by the governing pack-defined profile" }
|
|
11297
|
+
],
|
|
11298
|
+
check(ctx) {
|
|
11299
|
+
for (const comp of ctx.components) {
|
|
11300
|
+
const profile = ctx.getComponentProfile(comp.id);
|
|
11301
|
+
const packDef = ctx.ext.profiles[profile];
|
|
11302
|
+
if (!packDef) continue;
|
|
11303
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11304
|
+
for (const rule of packDef.forbiddenStereotypes) {
|
|
11305
|
+
if (rule.types.includes(comp.componentType)) {
|
|
11306
|
+
ctx.addIssue(
|
|
11307
|
+
"error",
|
|
11308
|
+
"PROFILE_FORBIDDEN_STEREOTYPE",
|
|
11309
|
+
`Component "${comp.id}" is a ${comp.componentType}, forbidden by profile "${profile}": ${rule.reason}`,
|
|
11310
|
+
comp.id,
|
|
11311
|
+
isDraftCtx
|
|
11312
|
+
);
|
|
10753
11313
|
}
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
11314
|
+
}
|
|
11315
|
+
for (const rule of packDef.discouragedStereotypes) {
|
|
11316
|
+
if (rule.types.includes(comp.componentType)) {
|
|
11317
|
+
ctx.addIssue(
|
|
11318
|
+
"warning",
|
|
11319
|
+
"PROFILE_DISCOURAGED_STEREOTYPE",
|
|
11320
|
+
`Component "${comp.id}" is a ${comp.componentType}, discouraged by profile "${profile}": ${rule.reason}`,
|
|
11321
|
+
comp.id,
|
|
11322
|
+
isDraftCtx
|
|
11323
|
+
);
|
|
10764
11324
|
}
|
|
10765
11325
|
}
|
|
10766
11326
|
}
|
|
@@ -11097,22 +11657,20 @@ var init_dependency_cycles = __esm({
|
|
|
11097
11657
|
}
|
|
11098
11658
|
});
|
|
11099
11659
|
|
|
11100
|
-
// src/core/rules/wiring/dispatch-
|
|
11101
|
-
var
|
|
11102
|
-
var
|
|
11103
|
-
"src/core/rules/wiring/dispatch-
|
|
11660
|
+
// src/core/rules/wiring/dispatch-table-bindings.ts
|
|
11661
|
+
var dispatchTableBindingsRule;
|
|
11662
|
+
var init_dispatch_table_bindings = __esm({
|
|
11663
|
+
"src/core/rules/wiring/dispatch-table-bindings.ts"() {
|
|
11104
11664
|
"use strict";
|
|
11105
|
-
|
|
11106
|
-
name: "dispatch-
|
|
11107
|
-
description: "
|
|
11665
|
+
dispatchTableBindingsRule = {
|
|
11666
|
+
name: "dispatch-table-bindings",
|
|
11667
|
+
description: "A dispatch table belongs to a Portal, binds each capability once, and binds it to an existing component.method inside the portal's own subsystem (the portal dispatches inward) that the portal declares under dependsOn or owns \u2014 the table is a real runtime invocation path.",
|
|
11108
11668
|
codes: [
|
|
11109
11669
|
{ code: "DISPATCH_ON_NON_PORTAL", defaultSeverity: "error", summary: "Dispatch table declared on a component that is not a Portal" },
|
|
11110
11670
|
{ code: "DUPLICATE_CAPABILITY", defaultSeverity: "error", summary: "Capability bound more than once in one dispatch table" },
|
|
11111
11671
|
{ code: "UNSERVED_CAPABILITY", defaultSeverity: "error", summary: "Capability has no existing server (bad table binding, or dispatch step routing a capability the target Portal does not serve)" },
|
|
11112
11672
|
{ code: "DISPATCH_CROSS_SUBSYSTEM", defaultSeverity: "error", summary: "Dispatch binding targets a component outside the portal's subsystem" },
|
|
11113
|
-
{ code: "UNDECLARED_DISPATCH_TARGET", defaultSeverity: "error", summary: "Dispatch binding targets a component the portal does not depend on or own" }
|
|
11114
|
-
{ code: "MALFORMED_DISPATCH_STEP", defaultSeverity: "error", summary: "Dispatch step missing its capability (targetComponent presence is the contracts rule's finding)" },
|
|
11115
|
-
{ code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Dispatch step asserts a guarantee the bound capability method does not declare" }
|
|
11673
|
+
{ code: "UNDECLARED_DISPATCH_TARGET", defaultSeverity: "error", summary: "Dispatch binding targets a component the portal does not depend on or own" }
|
|
11116
11674
|
],
|
|
11117
11675
|
check(ctx) {
|
|
11118
11676
|
for (const comp of ctx.components) {
|
|
@@ -11179,6 +11737,25 @@ var init_dispatch_tables = __esm({
|
|
|
11179
11737
|
}
|
|
11180
11738
|
}
|
|
11181
11739
|
}
|
|
11740
|
+
}
|
|
11741
|
+
};
|
|
11742
|
+
}
|
|
11743
|
+
});
|
|
11744
|
+
|
|
11745
|
+
// src/core/rules/wiring/dispatch-step-routing.ts
|
|
11746
|
+
var dispatchStepRoutingRule;
|
|
11747
|
+
var init_dispatch_step_routing = __esm({
|
|
11748
|
+
"src/core/rules/wiring/dispatch-step-routing.ts"() {
|
|
11749
|
+
"use strict";
|
|
11750
|
+
dispatchStepRoutingRule = {
|
|
11751
|
+
name: "dispatch-step-routing",
|
|
11752
|
+
description: "A dispatch narrative step must name the capability it routes and route it through a Portal whose dispatch table actually serves that capability; a guarantee the step asserts must be declared by the contract method the capability resolves to.",
|
|
11753
|
+
codes: [
|
|
11754
|
+
{ code: "MALFORMED_DISPATCH_STEP", defaultSeverity: "error", summary: "Dispatch step missing its capability (targetComponent presence is narrative-target-references' finding)" },
|
|
11755
|
+
{ code: "UNSERVED_CAPABILITY", defaultSeverity: "error", summary: "Capability has no existing server (bad table binding, or dispatch step routing a capability the target Portal does not serve)" },
|
|
11756
|
+
{ code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Dispatch step asserts a guarantee the bound capability method does not declare" }
|
|
11757
|
+
],
|
|
11758
|
+
check(ctx) {
|
|
11182
11759
|
for (const impl of ctx.implementations) {
|
|
11183
11760
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
11184
11761
|
for (const implMethod of impl.methods) {
|
|
@@ -11933,35 +12510,25 @@ var init_event_topology = __esm({
|
|
|
11933
12510
|
}
|
|
11934
12511
|
});
|
|
11935
12512
|
|
|
11936
|
-
// src/core/rules/conformance/
|
|
11937
|
-
var quoteList,
|
|
11938
|
-
var
|
|
11939
|
-
"src/core/rules/conformance/
|
|
12513
|
+
// src/core/rules/conformance/source-file-linkage.ts
|
|
12514
|
+
var quoteList, sourceFileLinkageRule;
|
|
12515
|
+
var init_source_file_linkage = __esm({
|
|
12516
|
+
"src/core/rules/conformance/source-file-linkage.ts"() {
|
|
11940
12517
|
"use strict";
|
|
11941
12518
|
init_models();
|
|
11942
12519
|
quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
|
|
11943
|
-
|
|
11944
|
-
name: "
|
|
11945
|
-
description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real file inside the project root, and
|
|
12520
|
+
sourceFileLinkageRule = {
|
|
12521
|
+
name: "source-file-linkage",
|
|
12522
|
+
description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real, readable file inside the project root, and an implementation that names no file at all, or leaves a contract method without one, is linked to no code. A file that escapes the root, is missing or cannot be analyzed is reported once and blocks only the methods realized in it. A run in which TypeScript/JavaScript files were analyzed below exact grade reports that once (CONFORMANCE_DEGRADED), since dependency conformance then skips them. Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
|
|
11946
12523
|
codes: [
|
|
11947
12524
|
{ code: "MISSING_SOURCE_PATH", defaultSeverity: "warning", summary: "An implementation names no source file at all, or a contract method is left without one \u2014 structural conformance cannot link it to code" },
|
|
11948
12525
|
{ code: "MISSING_SOURCE_FILE", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names does not resolve to a file on disk" },
|
|
11949
12526
|
{ code: "SOURCE_PATH_ESCAPES_ROOT", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names is absolute or escapes the project root (containment refusal)" },
|
|
11950
|
-
{ 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" },
|
|
11951
|
-
{ 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)" },
|
|
11952
12527
|
{ code: "CONFORMANCE_ANALYSIS_SKIPPED", defaultSeverity: "warning", summary: "A source file an implementation or one of its methods names could not be analyzed (binary/unreadable) \u2014 realization of the methods in it was not checked" },
|
|
11953
12528
|
{ code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
|
|
11954
12529
|
],
|
|
11955
12530
|
check(ctx) {
|
|
11956
|
-
const
|
|
11957
|
-
for (const facts of ctx.codeModel.files) {
|
|
11958
|
-
lookups.set(pathKey(facts.path), {
|
|
11959
|
-
declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
|
|
11960
|
-
anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
|
|
11961
|
-
findingAnchors: new Set(facts.anchoredNames),
|
|
11962
|
-
facts
|
|
11963
|
-
});
|
|
11964
|
-
}
|
|
12531
|
+
const code = ctx.codeIndex();
|
|
11965
12532
|
const degradedTsFiles = ctx.codeModel.files.filter(
|
|
11966
12533
|
(f) => f.status === "analyzed" && (f.language === "typescript" || f.language === "javascript") && f.analysisGrade !== "exact"
|
|
11967
12534
|
);
|
|
@@ -11979,8 +12546,7 @@ var init_structural_conformance = __esm({
|
|
|
11979
12546
|
if (!component) continue;
|
|
11980
12547
|
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
11981
12548
|
const draft = ctx.isImplementationDraft(impl);
|
|
11982
|
-
const
|
|
11983
|
-
const unlinked = contract.methods.filter((method2) => !methodSourceFile(methodImplOf(method2.name) ?? {}, impl.sourcePath)).map((method2) => method2.name);
|
|
12549
|
+
const unlinked = contract.methods.filter((method2) => !methodSourceFile(impl.methods.find((m) => m.name === method2.name) ?? {}, impl.sourcePath)).map((method2) => method2.name);
|
|
11984
12550
|
const noFileAtAll = implementationSourceFiles(impl).length === 0;
|
|
11985
12551
|
if (unlinked.length > 0 || noFileAtAll) {
|
|
11986
12552
|
const hasExternalSource = (component.externalLinks ?? []).some((l) => l.type === "implementation");
|
|
@@ -12010,7 +12576,7 @@ var init_structural_conformance = __esm({
|
|
|
12010
12576
|
const key = pathKey(file);
|
|
12011
12577
|
if (reported.has(key)) continue;
|
|
12012
12578
|
reported.add(key);
|
|
12013
|
-
const facts =
|
|
12579
|
+
const facts = code.factsAt(key);
|
|
12014
12580
|
if (!facts || facts.status === "analyzed") continue;
|
|
12015
12581
|
const isImplementationFile = !!impl.sourcePath && pathKey(impl.sourcePath) === key;
|
|
12016
12582
|
const users = isImplementationFile ? [] : impl.methods.filter((m) => m.sourcePath && pathKey(m.sourcePath) === key).map((m) => m.name);
|
|
@@ -12041,25 +12607,45 @@ var init_structural_conformance = __esm({
|
|
|
12041
12607
|
);
|
|
12042
12608
|
}
|
|
12043
12609
|
}
|
|
12610
|
+
}
|
|
12611
|
+
}
|
|
12612
|
+
};
|
|
12613
|
+
}
|
|
12614
|
+
});
|
|
12615
|
+
|
|
12616
|
+
// src/core/rules/conformance/method-realization.ts
|
|
12617
|
+
var methodRealizationRule;
|
|
12618
|
+
var init_method_realization = __esm({
|
|
12619
|
+
"src/core/rules/conformance/method-realization.ts"() {
|
|
12620
|
+
"use strict";
|
|
12621
|
+
init_models();
|
|
12622
|
+
methodRealizationRule = {
|
|
12623
|
+
name: "method-realization",
|
|
12624
|
+
description: "Code\u2194spec Level 1: every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier \u2014 declared | anchored | off; Portals default to anchored, everything else to declared, and a per-method `symbol` maps an intent-language name onto the code name. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Methods whose file escapes the root, is missing or could not be analyzed are left to source-file-linkage, and implementations under chained subsystems (projectPath) validate standalone in their own project run.",
|
|
12625
|
+
codes: [
|
|
12626
|
+
{ code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" }
|
|
12627
|
+
],
|
|
12628
|
+
check(ctx) {
|
|
12629
|
+
const code = ctx.codeIndex();
|
|
12630
|
+
for (const impl of ctx.implementations) {
|
|
12631
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12632
|
+
if (!contract) continue;
|
|
12633
|
+
const component = ctx.componentMap.get(contract.component);
|
|
12634
|
+
if (!component) continue;
|
|
12635
|
+
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
12636
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
12044
12637
|
const specTier = impl.conformance ?? defaultConformanceTier(component);
|
|
12045
|
-
const
|
|
12046
|
-
const methodImpl =
|
|
12638
|
+
for (const method2 of contract.methods) {
|
|
12639
|
+
const methodImpl = impl.methods.find((m) => m.name === method2.name);
|
|
12047
12640
|
const tier = methodImpl?.conformance ?? specTier;
|
|
12048
|
-
if (tier === "off")
|
|
12641
|
+
if (tier === "off") continue;
|
|
12049
12642
|
const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
|
|
12050
|
-
if (!file)
|
|
12051
|
-
const
|
|
12052
|
-
if (!
|
|
12053
|
-
return { file, tier, lookup };
|
|
12054
|
-
};
|
|
12055
|
-
for (const method2 of contract.methods) {
|
|
12056
|
-
const checked = checkedFile(method2.name);
|
|
12057
|
-
if (!checked) continue;
|
|
12058
|
-
const { file, tier, lookup } = checked;
|
|
12059
|
-
const methodImpl = methodImplOf(method2.name);
|
|
12643
|
+
if (!file) continue;
|
|
12644
|
+
const facts = code.factsAt(file);
|
|
12645
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
12060
12646
|
const symbol = methodImpl?.symbol ?? method2.name;
|
|
12061
|
-
const inDeclared =
|
|
12062
|
-
const inAnchored = inDeclared ||
|
|
12647
|
+
const inDeclared = code.declarationsAt(file).has(symbol);
|
|
12648
|
+
const inAnchored = inDeclared || code.anchorsAt(file).has(symbol);
|
|
12063
12649
|
const realized = tier === "declared" ? inDeclared : inAnchored;
|
|
12064
12650
|
if (realized) continue;
|
|
12065
12651
|
const label = methodImpl?.symbol ? `"${method2.name}" (symbol "${symbol}")` : `"${method2.name}"`;
|
|
@@ -12067,22 +12653,55 @@ var init_structural_conformance = __esm({
|
|
|
12067
12653
|
ctx.addIssue(
|
|
12068
12654
|
"warning",
|
|
12069
12655
|
"UNREALIZED_METHOD",
|
|
12070
|
-
`Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${
|
|
12656
|
+
`Method ${label} of contract "${impl.contract}" is not realized in "${file}" at the "${tier}" tier (analysis grade: ${facts.analysisGrade}).${weakHint}`,
|
|
12071
12657
|
impl.id,
|
|
12072
12658
|
draft
|
|
12073
12659
|
);
|
|
12074
12660
|
}
|
|
12661
|
+
}
|
|
12662
|
+
}
|
|
12663
|
+
};
|
|
12664
|
+
}
|
|
12665
|
+
});
|
|
12666
|
+
|
|
12667
|
+
// src/core/rules/conformance/finding-realization.ts
|
|
12668
|
+
var findingRealizationRule;
|
|
12669
|
+
var init_finding_realization = __esm({
|
|
12670
|
+
"src/core/rules/conformance/finding-realization.ts"() {
|
|
12671
|
+
"use strict";
|
|
12672
|
+
init_models();
|
|
12673
|
+
findingRealizationRule = {
|
|
12674
|
+
name: "finding-realization",
|
|
12675
|
+
description: "Code\u2194spec Level 1: every finding code an L3 contract method declares must be anchored in the method's own source file (the method's sourcePath, else the implementation's) \u2014 as a string literal or, at exact grade, a property-access name, so a code reported through a constants object counts. Below exact grade any identifier or word counts, and the grade rides on the finding. Respects the conformance dial (off skips), leaves unresolvable files to source-file-linkage, and skips implementations under chained subsystems (projectPath), which validate standalone in their own project run.",
|
|
12676
|
+
codes: [
|
|
12677
|
+
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" }
|
|
12678
|
+
],
|
|
12679
|
+
check(ctx) {
|
|
12680
|
+
const code = ctx.codeIndex();
|
|
12681
|
+
for (const impl of ctx.implementations) {
|
|
12682
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12683
|
+
if (!contract) continue;
|
|
12684
|
+
const component = ctx.componentMap.get(contract.component);
|
|
12685
|
+
if (!component) continue;
|
|
12686
|
+
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
12687
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
12688
|
+
const specTier = impl.conformance ?? defaultConformanceTier(component);
|
|
12075
12689
|
for (const method2 of contract.methods) {
|
|
12076
12690
|
if (!method2.findings?.length) continue;
|
|
12077
|
-
const
|
|
12078
|
-
|
|
12079
|
-
|
|
12691
|
+
const methodImpl = impl.methods.find((m) => m.name === method2.name);
|
|
12692
|
+
const tier = methodImpl?.conformance ?? specTier;
|
|
12693
|
+
if (tier === "off") continue;
|
|
12694
|
+
const file = methodSourceFile(methodImpl ?? {}, impl.sourcePath);
|
|
12695
|
+
if (!file) continue;
|
|
12696
|
+
const facts = code.factsAt(file);
|
|
12697
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
12698
|
+
const findingAnchors = code.findingAnchorsAt(file);
|
|
12080
12699
|
for (const finding of method2.findings) {
|
|
12081
|
-
if (
|
|
12700
|
+
if (findingAnchors.has(finding.code)) continue;
|
|
12082
12701
|
ctx.addIssue(
|
|
12083
12702
|
"warning",
|
|
12084
12703
|
"UNREALIZED_FINDING",
|
|
12085
|
-
`Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${
|
|
12704
|
+
`Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
|
|
12086
12705
|
impl.id,
|
|
12087
12706
|
draft
|
|
12088
12707
|
);
|
|
@@ -12126,8 +12745,7 @@ var init_call_conformance = __esm({
|
|
|
12126
12745
|
{ code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step whose target method name (or symbol) never appears among the realized function's callees in the method's source file (exact grade, set membership)" }
|
|
12127
12746
|
],
|
|
12128
12747
|
check(ctx) {
|
|
12129
|
-
const
|
|
12130
|
-
for (const f of ctx.codeModel.files) factsByPath.set(pathKey(f.path), f);
|
|
12748
|
+
const code = ctx.codeIndex();
|
|
12131
12749
|
for (const impl of ctx.implementations) {
|
|
12132
12750
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12133
12751
|
if (!contract) continue;
|
|
@@ -12142,7 +12760,7 @@ var init_call_conformance = __esm({
|
|
|
12142
12760
|
if (!implMethod.narrative.length) continue;
|
|
12143
12761
|
const file = methodSourceFile(implMethod, impl.sourcePath);
|
|
12144
12762
|
if (!file) continue;
|
|
12145
|
-
const facts =
|
|
12763
|
+
const facts = code.factsAt(file);
|
|
12146
12764
|
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
12147
12765
|
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
12148
12766
|
const callees = closedCallees(facts, fnSymbol);
|
|
@@ -12196,6 +12814,7 @@ var init_hidden_state = __esm({
|
|
|
12196
12814
|
{ code: "HIDDEN_STATE", defaultSeverity: "warning", summary: "Module-scope mutable binding in a file mapped only to stateless logic (Orchestrators) \u2014 held state hiding outside a Store, Supervisor or Actor" }
|
|
12197
12815
|
],
|
|
12198
12816
|
check(ctx) {
|
|
12817
|
+
const code = ctx.codeIndex();
|
|
12199
12818
|
const byPath = /* @__PURE__ */ new Map();
|
|
12200
12819
|
for (const impl of ctx.implementations) {
|
|
12201
12820
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
@@ -12217,11 +12836,11 @@ var init_hidden_state = __esm({
|
|
|
12217
12836
|
byPath.set(key, list2);
|
|
12218
12837
|
}
|
|
12219
12838
|
}
|
|
12220
|
-
for (const
|
|
12221
|
-
|
|
12839
|
+
for (const path70 of code.exactPaths) {
|
|
12840
|
+
const facts = code.factsAt(path70);
|
|
12222
12841
|
const bindings = facts.topLevelMutableBindings ?? [];
|
|
12223
12842
|
if (bindings.length === 0) continue;
|
|
12224
|
-
const mapped = byPath.get(
|
|
12843
|
+
const mapped = byPath.get(path70) ?? [];
|
|
12225
12844
|
if (mapped.length === 0) continue;
|
|
12226
12845
|
if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
|
|
12227
12846
|
const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
|
|
@@ -12253,54 +12872,21 @@ var init_dependency_conformance = __esm({
|
|
|
12253
12872
|
{ code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
|
|
12254
12873
|
],
|
|
12255
12874
|
check(ctx) {
|
|
12256
|
-
const
|
|
12257
|
-
const
|
|
12258
|
-
const
|
|
12259
|
-
const
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
if (!
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
let node = nodes.get(p);
|
|
12272
|
-
if (!node) {
|
|
12273
|
-
node = { path: p, components: [], anchorImplId: impl.id, imports: facts.imports, reexports: facts.reexports, draft: false };
|
|
12274
|
-
nodes.set(p, node);
|
|
12275
|
-
}
|
|
12276
|
-
if (!node.components.some((c) => c.id === component.id)) node.components.push(component);
|
|
12277
|
-
node.draft = node.draft || ctx.isImplementationDraft(impl);
|
|
12278
|
-
if (!filesByComponent.has(component.id)) filesByComponent.set(component.id, /* @__PURE__ */ new Set());
|
|
12279
|
-
filesByComponent.get(component.id).add(p);
|
|
12280
|
-
mapped = true;
|
|
12281
|
-
}
|
|
12282
|
-
if (mapped) {
|
|
12283
|
-
if (!implsByComponent.has(component.id)) implsByComponent.set(component.id, []);
|
|
12284
|
-
implsByComponent.get(component.id).push(impl);
|
|
12285
|
-
}
|
|
12286
|
-
}
|
|
12287
|
-
const mappedPaths = new Set(nodes.keys());
|
|
12288
|
-
const edges = /* @__PURE__ */ new Map();
|
|
12289
|
-
const realizationEdges = /* @__PURE__ */ new Map();
|
|
12290
|
-
for (const node of nodes.values()) {
|
|
12291
|
-
const targets = /* @__PURE__ */ new Set();
|
|
12292
|
-
for (const spec of node.imports) {
|
|
12293
|
-
const resolved = resolveImport(node.path, spec, mappedPaths);
|
|
12294
|
-
if (resolved && resolved !== node.path) targets.add(resolved);
|
|
12295
|
-
}
|
|
12296
|
-
edges.set(node.path, targets);
|
|
12297
|
-
const realized = new Set(targets);
|
|
12298
|
-
for (const spec of node.reexports) {
|
|
12299
|
-
const resolved = resolveImport(node.path, spec, mappedPaths);
|
|
12300
|
-
if (resolved && resolved !== node.path) realized.add(resolved);
|
|
12301
|
-
}
|
|
12302
|
-
realizationEdges.set(node.path, realized);
|
|
12303
|
-
}
|
|
12875
|
+
const code = ctx.codeIndex();
|
|
12876
|
+
const realization = ctx.realizationIndex();
|
|
12877
|
+
const isExact = (p) => code.exactPaths.has(p);
|
|
12878
|
+
const mappedPaths = new Set(realization.paths.filter(isExact));
|
|
12879
|
+
const graph = ctx.importGraph(mappedPaths);
|
|
12880
|
+
const exactFiles = /* @__PURE__ */ new Map();
|
|
12881
|
+
const filesOf = (compId) => {
|
|
12882
|
+
let files = exactFiles.get(compId);
|
|
12883
|
+
if (!files) {
|
|
12884
|
+
files = realization.filesOf(compId).filter(isExact);
|
|
12885
|
+
exactFiles.set(compId, files);
|
|
12886
|
+
}
|
|
12887
|
+
return files;
|
|
12888
|
+
};
|
|
12889
|
+
const mappedImplsOf = (compId) => realization.implementationsOf(compId).filter((impl) => implementationSourceFiles(impl).some((f) => isExact(pathKey(f))));
|
|
12304
12890
|
const dependsOrOwns = (from, toId) => from.dependsOn.includes(toId) || (from.owns ?? []).includes(toId);
|
|
12305
12891
|
const ownerOf = /* @__PURE__ */ new Map();
|
|
12306
12892
|
for (const c of ctx.components) {
|
|
@@ -12311,9 +12897,9 @@ var init_dependency_conformance = __esm({
|
|
|
12311
12897
|
if (!published || published.size === 0) return false;
|
|
12312
12898
|
return from.dependsOn.some((d) => published.has(d));
|
|
12313
12899
|
};
|
|
12314
|
-
const edgeJustified = (
|
|
12315
|
-
for (const cf of
|
|
12316
|
-
for (const cg of
|
|
12900
|
+
const edgeJustified = (from, to) => {
|
|
12901
|
+
for (const cf of from) {
|
|
12902
|
+
for (const cg of to) {
|
|
12317
12903
|
if (cf.id === cg.id) return true;
|
|
12318
12904
|
if (dependsOrOwns(cf, cg.id)) return true;
|
|
12319
12905
|
if (cf.subsystem === cg.subsystem && dependsOrOwns(cg, cf.id) && (cg.componentType === "Portal" || cg.componentType === "Observer")) return true;
|
|
@@ -12327,60 +12913,35 @@ var init_dependency_conformance = __esm({
|
|
|
12327
12913
|
}
|
|
12328
12914
|
return false;
|
|
12329
12915
|
};
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12916
|
+
const draftAt = (path70) => realization.implementationsAt(path70).some((impl) => ctx.isImplementationDraft(impl));
|
|
12917
|
+
for (const fromPath of mappedPaths) {
|
|
12918
|
+
const fromComponents = realization.componentsAt(fromPath);
|
|
12919
|
+
for (const toPath of graph.importsOf(fromPath)) {
|
|
12920
|
+
const toComponents = realization.componentsAt(toPath);
|
|
12921
|
+
if (edgeJustified(fromComponents, toComponents)) continue;
|
|
12335
12922
|
ctx.addIssue(
|
|
12336
12923
|
"warning",
|
|
12337
12924
|
"UNDECLARED_DEPENDENCY",
|
|
12338
|
-
`"${fromPath}" (realizing ${
|
|
12339
|
-
|
|
12340
|
-
|
|
12925
|
+
`"${fromPath}" (realizing ${fromComponents.map((c) => c.id).join(", ")}) imports "${toPath}" (realizing ${toComponents.map((c) => c.id).join(", ")}) but no declared dependsOn/owns edge justifies it \u2014 declare the collaboration on the component that actually uses it, or route the cross-subsystem hop through the target's published surface.`,
|
|
12926
|
+
realization.implementationsAt(fromPath)[0]?.id,
|
|
12927
|
+
draftAt(fromPath) || draftAt(toPath)
|
|
12341
12928
|
);
|
|
12342
12929
|
}
|
|
12343
12930
|
}
|
|
12344
|
-
const hasImportBetween = (fromFiles, toFiles, allowReverse) => {
|
|
12345
|
-
for (const f of fromFiles) {
|
|
12346
|
-
const targets = realizationEdges.get(f);
|
|
12347
|
-
if (!targets) continue;
|
|
12348
|
-
for (const t of toFiles) if (targets.has(t)) return true;
|
|
12349
|
-
}
|
|
12350
|
-
if (allowReverse) {
|
|
12351
|
-
for (const t of toFiles) {
|
|
12352
|
-
const reverse = realizationEdges.get(t);
|
|
12353
|
-
if (!reverse) continue;
|
|
12354
|
-
for (const f of fromFiles) if (reverse.has(f)) return true;
|
|
12355
|
-
}
|
|
12356
|
-
}
|
|
12357
|
-
return false;
|
|
12358
|
-
};
|
|
12359
12931
|
for (const component of ctx.components) {
|
|
12360
|
-
const fromFiles =
|
|
12361
|
-
if (
|
|
12932
|
+
const fromFiles = filesOf(component.id);
|
|
12933
|
+
if (fromFiles.length === 0) continue;
|
|
12362
12934
|
const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
|
|
12363
12935
|
const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
|
|
12364
12936
|
for (const targetId of declaredTargets) {
|
|
12365
12937
|
const target = ctx.componentMap.get(targetId);
|
|
12366
12938
|
if (!target) continue;
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
for (const f of fromFiles) subsystemFiles.delete(f);
|
|
12374
|
-
if (subsystemFiles.size === 0) continue;
|
|
12375
|
-
if (hasImportBetween(fromFiles, subsystemFiles, isMountingDeclarer)) continue;
|
|
12376
|
-
} else {
|
|
12377
|
-
const toFiles = filesByComponent.get(targetId);
|
|
12378
|
-
if (!toFiles) continue;
|
|
12379
|
-
if ([...fromFiles].some((f) => toFiles.has(f))) continue;
|
|
12380
|
-
if (hasImportBetween(fromFiles, toFiles, isMountingDeclarer)) continue;
|
|
12381
|
-
}
|
|
12382
|
-
const impls = implsByComponent.get(component.id) ?? [];
|
|
12383
|
-
const anchor = impls[0];
|
|
12939
|
+
const crossSubsystem = component.subsystem !== target.subsystem;
|
|
12940
|
+
const toFiles = crossSubsystem ? realization.filesIn(target.subsystem).filter((p) => isExact(p) && !fromFiles.includes(p)) : filesOf(targetId);
|
|
12941
|
+
if (toFiles.length === 0) continue;
|
|
12942
|
+
if (fromFiles.some((f) => toFiles.includes(f))) continue;
|
|
12943
|
+
if (graph.connects(fromFiles, toFiles, isMountingDeclarer)) continue;
|
|
12944
|
+
const impls = mappedImplsOf(component.id);
|
|
12384
12945
|
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
12385
12946
|
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
12386
12947
|
const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
|
|
@@ -12388,8 +12949,8 @@ var init_dependency_conformance = __esm({
|
|
|
12388
12949
|
ctx.addIssue(
|
|
12389
12950
|
"warning",
|
|
12390
12951
|
"UNREALIZED_DEPENDENCY",
|
|
12391
|
-
`Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${
|
|
12392
|
-
|
|
12952
|
+
`Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${fromFiles.join(", ")} \u219B ${crossSubsystem ? `subsystem ${target.subsystem}` : filesOf(targetId).join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
|
|
12953
|
+
impls[0]?.id ?? component.id,
|
|
12393
12954
|
draft
|
|
12394
12955
|
);
|
|
12395
12956
|
}
|
|
@@ -12415,50 +12976,24 @@ var init_integration_conformance = __esm({
|
|
|
12415
12976
|
{ code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
|
|
12416
12977
|
],
|
|
12417
12978
|
check(ctx) {
|
|
12418
|
-
const
|
|
12419
|
-
const
|
|
12420
|
-
const
|
|
12421
|
-
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
12422
|
-
const implsByComponent = /* @__PURE__ */ new Map();
|
|
12423
|
-
const adopted = /* @__PURE__ */ new Set();
|
|
12979
|
+
const code = ctx.codeIndex();
|
|
12980
|
+
const realization = ctx.realizationIndex();
|
|
12981
|
+
const graph = ctx.importGraph();
|
|
12424
12982
|
const exists = (p) => {
|
|
12425
|
-
const status2 =
|
|
12983
|
+
const status2 = code.factsAt(p)?.status;
|
|
12426
12984
|
return status2 === "analyzed" || status2 === "unreadable";
|
|
12427
12985
|
};
|
|
12986
|
+
const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
|
|
12987
|
+
const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
|
|
12988
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
12428
12989
|
for (const impl of ctx.implementations) {
|
|
12990
|
+
if (!impl.simPath) continue;
|
|
12429
12991
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12430
12992
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
12431
12993
|
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
const p = pathKey(file);
|
|
12436
|
-
if (!exists(p)) continue;
|
|
12437
|
-
if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
|
|
12438
|
-
filesByComponent.get(comp.id).add(p);
|
|
12439
|
-
if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
|
|
12440
|
-
filesBySubsystem.get(comp.subsystem).add(p);
|
|
12441
|
-
}
|
|
12442
|
-
if (impl.simPath) adopted.add(comp.subsystem);
|
|
12443
|
-
}
|
|
12444
|
-
const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
|
|
12445
|
-
const reachFrom = (start2) => {
|
|
12446
|
-
const seen = /* @__PURE__ */ new Set([start2]);
|
|
12447
|
-
const stack = [start2];
|
|
12448
|
-
while (stack.length) {
|
|
12449
|
-
const from = stack.pop();
|
|
12450
|
-
const facts = factsByPath.get(from);
|
|
12451
|
-
if (!facts || facts.status !== "analyzed") continue;
|
|
12452
|
-
for (const spec of [...facts.imports, ...facts.reexports]) {
|
|
12453
|
-
const to = resolveImport(from, spec, allPaths);
|
|
12454
|
-
if (to && !seen.has(to)) {
|
|
12455
|
-
seen.add(to);
|
|
12456
|
-
stack.push(to);
|
|
12457
|
-
}
|
|
12458
|
-
}
|
|
12459
|
-
}
|
|
12460
|
-
return seen;
|
|
12461
|
-
};
|
|
12994
|
+
adopted.add(comp.subsystem);
|
|
12995
|
+
}
|
|
12996
|
+
const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
|
|
12462
12997
|
for (const impl of ctx.implementations) {
|
|
12463
12998
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12464
12999
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
@@ -12478,7 +13013,7 @@ var init_integration_conformance = __esm({
|
|
|
12478
13013
|
continue;
|
|
12479
13014
|
}
|
|
12480
13015
|
const simPath = pathKey(impl.simPath);
|
|
12481
|
-
const facts =
|
|
13016
|
+
const facts = code.factsAt(simPath);
|
|
12482
13017
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
12483
13018
|
if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
|
|
12484
13019
|
const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
|
|
@@ -12492,19 +13027,19 @@ var init_integration_conformance = __esm({
|
|
|
12492
13027
|
continue;
|
|
12493
13028
|
}
|
|
12494
13029
|
if (facts.analysisGrade !== "exact") continue;
|
|
12495
|
-
const reach = reachFrom(simPath);
|
|
12496
|
-
const ownFiles =
|
|
13030
|
+
const reach = graph.reachFrom(simPath);
|
|
13031
|
+
const ownFiles = modulesOf(comp.id);
|
|
12497
13032
|
const missing = [];
|
|
12498
|
-
if (ownFiles.
|
|
12499
|
-
missing.push(`the component's own module (${
|
|
13033
|
+
if (ownFiles.length > 0 && !ownFiles.some((f) => reach.has(f))) {
|
|
13034
|
+
missing.push(`the component's own module (${ownFiles.join(" | ")})`);
|
|
12500
13035
|
}
|
|
12501
13036
|
for (const dep of deps) {
|
|
12502
13037
|
if (isTechBoundary(dep)) continue;
|
|
12503
13038
|
const depComp = ctx.componentMap.get(dep);
|
|
12504
|
-
const depFiles = depComp.subsystem !== comp.subsystem ?
|
|
12505
|
-
if (
|
|
12506
|
-
if (!
|
|
12507
|
-
const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${
|
|
13039
|
+
const depFiles = depComp.subsystem !== comp.subsystem ? modulesIn(depComp.subsystem) : modulesOf(dep);
|
|
13040
|
+
if (depFiles.length === 0) continue;
|
|
13041
|
+
if (!depFiles.some((f) => reach.has(f))) {
|
|
13042
|
+
const label = depComp.subsystem !== comp.subsystem ? `"${dep}" (any module of subsystem "${depComp.subsystem}")` : `"${dep}" (${depFiles.join(" | ")})`;
|
|
12508
13043
|
missing.push(label);
|
|
12509
13044
|
}
|
|
12510
13045
|
}
|
|
@@ -13658,12 +14193,19 @@ var init_repository = __esm({
|
|
|
13658
14193
|
init_hierarchy_integrity();
|
|
13659
14194
|
init_namespace_hygiene();
|
|
13660
14195
|
init_roundtrip_serialization();
|
|
13661
|
-
|
|
14196
|
+
init_type_declarations();
|
|
14197
|
+
init_field_type_references();
|
|
14198
|
+
init_signature_type_references();
|
|
13662
14199
|
init_public_surface();
|
|
13663
14200
|
init_lint_allows();
|
|
13664
|
-
|
|
14201
|
+
init_contract_symmetry();
|
|
14202
|
+
init_narrative_target_references();
|
|
14203
|
+
init_cross_tree_references();
|
|
14204
|
+
init_surface_reference_backing();
|
|
13665
14205
|
init_guarantee_tokens();
|
|
13666
|
-
|
|
14206
|
+
init_narrative_step_config();
|
|
14207
|
+
init_narrative_reachability();
|
|
14208
|
+
init_narrative_jump_edges();
|
|
13667
14209
|
init_narrative_antipatterns();
|
|
13668
14210
|
init_narrative_detail();
|
|
13669
14211
|
init_portal_fields();
|
|
@@ -13672,17 +14214,27 @@ var init_repository = __esm({
|
|
|
13672
14214
|
init_retired_stereotypes();
|
|
13673
14215
|
init_portal_endpoints();
|
|
13674
14216
|
init_portal_call_auth();
|
|
13675
|
-
|
|
13676
|
-
|
|
14217
|
+
init_subsystem_boundary_dependencies();
|
|
14218
|
+
init_logic_dependency_class();
|
|
14219
|
+
init_data_block_dependencies();
|
|
14220
|
+
init_entrypoint_dependencies();
|
|
14221
|
+
init_portal_write_shortcut();
|
|
14222
|
+
init_pattern_membership();
|
|
14223
|
+
init_pattern_containment();
|
|
14224
|
+
init_unowned_blocks();
|
|
14225
|
+
init_member_visibility();
|
|
13677
14226
|
init_facade_forwarding();
|
|
13678
|
-
|
|
14227
|
+
init_profile_registration();
|
|
14228
|
+
init_profile_stereotype_fencing();
|
|
14229
|
+
init_pack_profile_stereotypes();
|
|
13679
14230
|
init_pattern_references();
|
|
13680
14231
|
init_component_variants();
|
|
13681
14232
|
init_declarative_assertions();
|
|
13682
14233
|
init_pack_resolution();
|
|
13683
14234
|
init_pack_reproducibility();
|
|
13684
14235
|
init_dependency_cycles();
|
|
13685
|
-
|
|
14236
|
+
init_dispatch_table_bindings();
|
|
14237
|
+
init_dispatch_step_routing();
|
|
13686
14238
|
init_lifecycle_entrypoints();
|
|
13687
14239
|
init_unused_detection();
|
|
13688
14240
|
init_durability_round_trip();
|
|
@@ -13690,7 +14242,9 @@ var init_repository = __esm({
|
|
|
13690
14242
|
init_prose_claims();
|
|
13691
14243
|
init_invariant_backing();
|
|
13692
14244
|
init_event_topology();
|
|
13693
|
-
|
|
14245
|
+
init_source_file_linkage();
|
|
14246
|
+
init_method_realization();
|
|
14247
|
+
init_finding_realization();
|
|
13694
14248
|
init_call_conformance();
|
|
13695
14249
|
init_hidden_state();
|
|
13696
14250
|
init_dependency_conformance();
|
|
@@ -13709,12 +14263,30 @@ var init_repository = __esm({
|
|
|
13709
14263
|
// explain many downstream findings, so surface them early in the list.
|
|
13710
14264
|
namespaceHygieneRule,
|
|
13711
14265
|
roundtripRule,
|
|
13712
|
-
|
|
13713
|
-
|
|
14266
|
+
// The type vocabulary in three questions: what a type declares about
|
|
14267
|
+
// itself, then the identifiers its fields name, then the ones its
|
|
14268
|
+
// contracts' signatures name.
|
|
14269
|
+
typeDeclarationsRule,
|
|
14270
|
+
fieldTypeReferencesRule,
|
|
14271
|
+
signatureTypeReferencesRule,
|
|
14272
|
+
// Contracts and the targets narratives name, in four questions with one
|
|
14273
|
+
// owner each: does the implementation mirror its contract, does a target
|
|
14274
|
+
// inside this tree resolve, does a target that leaves it pin to exactly one
|
|
14275
|
+
// declared surface, and does that surface back what the step asks of it.
|
|
14276
|
+
contractSymmetryRule,
|
|
14277
|
+
narrativeTargetReferencesRule,
|
|
14278
|
+
crossTreeReferencesRule,
|
|
14279
|
+
surfaceReferenceBackingRule,
|
|
13714
14280
|
// Vocabulary check right after contracts: an unknown token explains why the
|
|
13715
14281
|
// consistency findings around it are absent, so surface them together.
|
|
13716
14282
|
guaranteeTokensRule,
|
|
13717
|
-
|
|
14283
|
+
// Narrative control flow in three questions: does each step carry the
|
|
14284
|
+
// config its type requires, can every step be reached (and do the regions
|
|
14285
|
+
// nest), and where do the jump edges land. The last two run behind the
|
|
14286
|
+
// first's soundness verdict.
|
|
14287
|
+
narrativeStepConfigRule,
|
|
14288
|
+
narrativeReachabilityRule,
|
|
14289
|
+
narrativeJumpEdgesRule,
|
|
13718
14290
|
// Antipatterns right after flow soundness: they analyze the same step
|
|
13719
14291
|
// graphs and only make sense once the graphs are structurally valid.
|
|
13720
14292
|
narrativeAntipatternsRule,
|
|
@@ -13727,11 +14299,32 @@ var init_repository = __esm({
|
|
|
13727
14299
|
// Cross-call auth: a narrative call into an authed Portal must name its
|
|
13728
14300
|
// credential source (rides with the portal family).
|
|
13729
14301
|
portalCallAuthRule,
|
|
13730
|
-
|
|
13731
|
-
|
|
14302
|
+
// Dependencies in five questions, one owner each: where an edge is
|
|
14303
|
+
// allowed to LAND (the boundary, and what an unresolved id means), then
|
|
14304
|
+
// the intra-subsystem matrix by the layer that answers for it — an
|
|
14305
|
+
// Orchestrator's declared class, the data blocks, the entry points and
|
|
14306
|
+
// the process layer — and finally the Portal read-face guard, the one
|
|
14307
|
+
// that reads narratives and dispatch tables rather than dependsOn.
|
|
14308
|
+
subsystemBoundaryDepsRule,
|
|
14309
|
+
logicDependencyClassRule,
|
|
14310
|
+
dataBlockDepsRule,
|
|
14311
|
+
entrypointDepsRule,
|
|
14312
|
+
portalWriteShortcutRule,
|
|
14313
|
+
// Patterns in four questions: who may own and what a claim must name,
|
|
14314
|
+
// what each pattern must contain, which data blocks are left standing
|
|
14315
|
+
// alone, and who may see a private member.
|
|
14316
|
+
patternMembershipRule,
|
|
14317
|
+
patternContainmentRule,
|
|
14318
|
+
unownedBlocksRule,
|
|
14319
|
+
memberVisibilityRule,
|
|
13732
14320
|
// Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
|
|
13733
14321
|
facadeForwardingRule,
|
|
13734
|
-
|
|
14322
|
+
// Profiles in three questions, three owners: is the name real (the
|
|
14323
|
+
// project's config), does the built-in family doctrine allow this
|
|
14324
|
+
// stereotype (wairon), does the pack's declared doctrine allow it (the pack).
|
|
14325
|
+
profileRegistrationRule,
|
|
14326
|
+
profileStereotypeFencingRule,
|
|
14327
|
+
packProfileStereotypesRule,
|
|
13735
14328
|
patternReferencesRule,
|
|
13736
14329
|
variantReferencesRule,
|
|
13737
14330
|
// Pack-instantiated declarative doctrine rides with the pack-reference
|
|
@@ -13742,7 +14335,8 @@ var init_repository = __esm({
|
|
|
13742
14335
|
// Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
|
|
13743
14336
|
// reader sees the broken edge finding next to the unused-detection fallout
|
|
13744
14337
|
// it explains.
|
|
13745
|
-
|
|
14338
|
+
dispatchTableBindingsRule,
|
|
14339
|
+
dispatchStepRoutingRule,
|
|
13746
14340
|
lifecycleRule,
|
|
13747
14341
|
reachabilityRule,
|
|
13748
14342
|
// The declaration (spec-scoped, refused at the write boundary) before the
|
|
@@ -13761,9 +14355,14 @@ var init_repository = __esm({
|
|
|
13761
14355
|
// Pub/sub completeness: emitted topics need subscribers and vice versa.
|
|
13762
14356
|
eventTopologyRule,
|
|
13763
14357
|
// Code↔spec: structural conformance consumes the injected CodeModel (built
|
|
13764
|
-
// by the source analysis adapter next to the surface snapshots)
|
|
13765
|
-
//
|
|
13766
|
-
|
|
14358
|
+
// by the source analysis adapter next to the surface snapshots) and asks it
|
|
14359
|
+
// three questions — does the spec name files that exist, does the file
|
|
14360
|
+
// contain the method, does it report the codes the method declares;
|
|
14361
|
+
// dependency conformance lifts its import edges onto the declared
|
|
14362
|
+
// dependsOn/owns graph.
|
|
14363
|
+
sourceFileLinkageRule,
|
|
14364
|
+
methodRealizationRule,
|
|
14365
|
+
findingRealizationRule,
|
|
13767
14366
|
// Level 3 opener: narrative call steps must be realized as callees of the
|
|
13768
14367
|
// realized function (set membership, exact grade).
|
|
13769
14368
|
callConformanceRule,
|
|
@@ -14484,11 +15083,15 @@ function buildRuleContext(opts) {
|
|
|
14484
15083
|
}
|
|
14485
15084
|
return { kind: "unresolved" };
|
|
14486
15085
|
};
|
|
15086
|
+
const contractMethods = /* @__PURE__ */ new Map();
|
|
14487
15087
|
const interfaceMethodsOf = (compId) => {
|
|
15088
|
+
const cached = contractMethods.get(compId);
|
|
15089
|
+
if (cached) return cached;
|
|
14488
15090
|
const out = [];
|
|
14489
15091
|
for (const intf of interfacesByComponent.get(compId) ?? []) {
|
|
14490
15092
|
out.push(...intf.methods);
|
|
14491
15093
|
}
|
|
15094
|
+
contractMethods.set(compId, out);
|
|
14492
15095
|
return out;
|
|
14493
15096
|
};
|
|
14494
15097
|
const profileDefFor = (subsystemId) => {
|
|
@@ -14584,7 +15187,26 @@ function buildRuleContext(opts) {
|
|
|
14584
15187
|
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
14585
15188
|
});
|
|
14586
15189
|
};
|
|
14587
|
-
|
|
15190
|
+
let codeIndexMemo;
|
|
15191
|
+
const codeIndex = () => codeIndexMemo ??= buildCodeIndex(ctx.codeModel);
|
|
15192
|
+
let realizationMemo;
|
|
15193
|
+
const realizationIndex = () => realizationMemo ??= buildRealizationIndex(ctx);
|
|
15194
|
+
const importGraphs = /* @__PURE__ */ new Map();
|
|
15195
|
+
const importGraph = (paths) => {
|
|
15196
|
+
const universe = paths ?? codeIndex().paths;
|
|
15197
|
+
const key = `${universe.size}\0${[...universe].join("\0")}`;
|
|
15198
|
+
let graph = importGraphs.get(key);
|
|
15199
|
+
if (!graph) {
|
|
15200
|
+
graph = buildImportGraph(codeIndex(), universe);
|
|
15201
|
+
importGraphs.set(key, graph);
|
|
15202
|
+
}
|
|
15203
|
+
return graph;
|
|
15204
|
+
};
|
|
15205
|
+
let ownershipMemo;
|
|
15206
|
+
const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
|
|
15207
|
+
let dependencyEdgesMemo;
|
|
15208
|
+
const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
|
|
15209
|
+
const ctx = {
|
|
14588
15210
|
system,
|
|
14589
15211
|
subsystems,
|
|
14590
15212
|
components,
|
|
@@ -14613,6 +15235,11 @@ function buildRuleContext(opts) {
|
|
|
14613
15235
|
isCollapsedCrossTreeRef,
|
|
14614
15236
|
resolveSurfaceRef,
|
|
14615
15237
|
interfaceMethodsOf,
|
|
15238
|
+
codeIndex,
|
|
15239
|
+
realizationIndex,
|
|
15240
|
+
importGraph,
|
|
15241
|
+
ownershipIndex,
|
|
15242
|
+
dependencyEdges,
|
|
14616
15243
|
complexityConfigFor,
|
|
14617
15244
|
documentationConfigFor,
|
|
14618
15245
|
namingConfigFor,
|
|
@@ -14627,6 +15254,7 @@ function buildRuleContext(opts) {
|
|
|
14627
15254
|
knownIssueCodes: opts.knownIssueCodes,
|
|
14628
15255
|
addIssue
|
|
14629
15256
|
};
|
|
15257
|
+
return ctx;
|
|
14630
15258
|
}
|
|
14631
15259
|
var DEPTH_RANK, DEPTH_GATED_CODES, COMPLETENESS_RULES;
|
|
14632
15260
|
var init_rules = __esm({
|
|
@@ -14635,6 +15263,7 @@ var init_rules = __esm({
|
|
|
14635
15263
|
init_models();
|
|
14636
15264
|
init_extensions();
|
|
14637
15265
|
init_types();
|
|
15266
|
+
init_read_model();
|
|
14638
15267
|
init_repository();
|
|
14639
15268
|
init_lint_allows();
|
|
14640
15269
|
init_source_analysis();
|
|
@@ -26116,7 +26745,7 @@ NOTICE:
|
|
|
26116
26745
|
simPath: import_zod11.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
|
|
26117
26746
|
technologies: import_zod11.z.array(import_zod11.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
|
|
26118
26747
|
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
26119
|
-
conformance: conformanceEnum.optional().describe("Spec-level
|
|
26748
|
+
conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
|
|
26120
26749
|
methods: import_zod11.z.array(import_zod11.z.object(implMethodShape)).optional().describe("Method implementations containing L5 narratives")
|
|
26121
26750
|
};
|
|
26122
26751
|
const implInputFields = Object.keys(implInput);
|