@sigloch/contracts 10.10.0 → 10.13.0
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/se/conformance-rules.d.ts +6 -1
- package/dist/se/conformance-rules.js +227 -49
- package/dist/se/cr-quality-rules.d.ts +5 -0
- package/dist/se/cr-quality-rules.js +78 -1
- package/dist/se/evaluate-all.d.ts +8 -1
- package/dist/se/evaluate-all.js +32 -29
- package/dist/se/fchain-quality-rules.d.ts +2 -1
- package/dist/se/fchain-quality-rules.js +132 -0
- package/dist/se/fmea-rules.js +22 -12
- package/dist/se/function-criticality.d.ts +8 -22
- package/dist/se/function-criticality.js +71 -1
- package/dist/se/grammar-snapshot.d.ts +9 -9
- package/dist/se/grammar-snapshot.js +34 -18
- package/dist/se/index.d.ts +3 -3
- package/dist/se/index.js +3 -3
- package/dist/se/meta-model.js +27 -16
- package/dist/se/metric-rules.d.ts +20 -61
- package/dist/se/metric-rules.js +90 -1
- package/dist/se/ontology.d.ts +69 -21
- package/dist/se/ontology.js +68 -16
- package/dist/se/readiness.d.ts +14 -0
- package/dist/se/readiness.js +33 -7
- package/dist/se/rule-help.js +28 -20
- package/dist/se/rules.d.ts +16 -0
- package/dist/se/rules.js +63 -64
- package/dist/se/uc-quality-rules.d.ts +0 -2
- package/dist/se/uc-quality-rules.js +2 -40
- package/dist/se/view-rules.js +5 -5
- package/package.json +1 -1
|
@@ -24,6 +24,7 @@ export declare const FileFactsSchema: z.ZodObject<{
|
|
|
24
24
|
testCases: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
25
25
|
importedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
26
26
|
parsedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
27
|
+
zodSymbols: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
27
28
|
}, z.core.$strip>;
|
|
28
29
|
export type FileFacts = z.infer<typeof FileFactsSchema>;
|
|
29
30
|
/** One file→file import edge (repo-relative paths) — the module-drift evidence (CR-212). */
|
|
@@ -44,6 +45,7 @@ export declare const CodeFactsSchema: z.ZodObject<{
|
|
|
44
45
|
testCases: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
45
46
|
importedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
46
47
|
parsedSymbols: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
48
|
+
zodSymbols: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
47
49
|
}, z.core.$strip>>;
|
|
48
50
|
importEdges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
49
51
|
from: z.ZodString;
|
|
@@ -54,6 +56,10 @@ export declare const CodeFactsSchema: z.ZodObject<{
|
|
|
54
56
|
open: "open";
|
|
55
57
|
done: "done";
|
|
56
58
|
}>>>;
|
|
59
|
+
fileScope: z.ZodOptional<z.ZodEnum<{
|
|
60
|
+
all: "all";
|
|
61
|
+
referenced: "referenced";
|
|
62
|
+
}>>;
|
|
57
63
|
}, z.core.$strip>;
|
|
58
64
|
export type CodeFacts = z.infer<typeof CodeFactsSchema>;
|
|
59
65
|
/** A conformance rule: pure over (graph, facts) — never touches I/O itself. */
|
|
@@ -103,7 +109,6 @@ export interface ImportCoverage {
|
|
|
103
109
|
unassigned: string[];
|
|
104
110
|
}
|
|
105
111
|
export declare function importCoverage(graph: OntologyGraph, facts: CodeFacts): ImportCoverage;
|
|
106
|
-
/** All RC conformance rules — evaluated by executors that can supply CodeFacts. */
|
|
107
112
|
export declare const CODE_CONFORMANCE_RULES: ConformanceRuleDefinition[];
|
|
108
113
|
/** Run all RC rules against a graph + extracted code facts. */
|
|
109
114
|
export declare function evaluateConformanceRules(graph: OntologyGraph, facts: CodeFacts): RuleViolation[];
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* @sigloch/contracts/se — single source of truth for SE validation rules.
|
|
16
16
|
*/
|
|
17
17
|
import { z } from 'zod/v4';
|
|
18
|
-
import {
|
|
18
|
+
import { readTestRefs, readRealRef } from './ontology.js';
|
|
19
19
|
import { CLOSED_STATUS } from './cr-quality-rules.js';
|
|
20
20
|
/** Parser facts about one source file (extracted by the executor). */
|
|
21
21
|
export const FileFactsSchema = z.object({
|
|
@@ -29,6 +29,12 @@ export const FileFactsSchema = z.object({
|
|
|
29
29
|
importedSymbols: z.array(z.string()).default([]),
|
|
30
30
|
/** Symbols X invoked as `X.parse(` / `X.safeParse(` — the schema is actually used (CR-211). */
|
|
31
31
|
parsedSymbols: z.array(z.string()).default([]),
|
|
32
|
+
/**
|
|
33
|
+
* Exported consts whose initializer is a Zod schema (`export const X = z.…`) — for RC-08
|
|
34
|
+
* (CR-SM-358). Optional, ABSENT = silence: the extractor did not look, which is not the
|
|
35
|
+
* same as "this file declares no schema" (same asymmetry as `declaredDependencies`).
|
|
36
|
+
*/
|
|
37
|
+
zodSymbols: z.array(z.string()).optional(),
|
|
32
38
|
});
|
|
33
39
|
/** One file→file import edge (repo-relative paths) — the module-drift evidence (CR-212). */
|
|
34
40
|
export const ImportEdgeSchema = z.object({
|
|
@@ -67,6 +73,13 @@ export const CodeFactsSchema = z.object({
|
|
|
67
73
|
* `docs/cr/` was never looked at, it does not "have no CRs".
|
|
68
74
|
*/
|
|
69
75
|
crFiles: z.record(z.string(), z.enum(['open', 'done'])).optional(),
|
|
76
|
+
/**
|
|
77
|
+
* Which files `files` covers (CR-SM-358): `referenced` = only files a realRef/testRefs
|
|
78
|
+
* entry names (the contract above), `all` = every source file of the repo. RC-09 asks
|
|
79
|
+
* "who ELSE parses this contract" and can only answer it over `all` — a partial scan
|
|
80
|
+
* would report the absence of what it never looked at. ABSENT = `referenced`.
|
|
81
|
+
*/
|
|
82
|
+
fileScope: z.enum(['referenced', 'all']).optional(),
|
|
70
83
|
});
|
|
71
84
|
const missingFile = (facts, file) => facts.files[file]?.exists !== true;
|
|
72
85
|
// RC-01: every valid FUNC realRef must resolve — file on disk, symbol declared in
|
|
@@ -81,17 +94,17 @@ function codeRefMustResolve(graph, facts) {
|
|
|
81
94
|
continue;
|
|
82
95
|
if (el.attributes?.concept === true || el.attributes?.external === true)
|
|
83
96
|
continue;
|
|
84
|
-
const parsed =
|
|
85
|
-
if (
|
|
97
|
+
const parsed = readRealRef(el.attributes);
|
|
98
|
+
if (parsed.state !== 'bound')
|
|
86
99
|
continue; // no/invalid binding → R-20 territory
|
|
87
|
-
const ref = parsed.
|
|
100
|
+
const ref = parsed.value;
|
|
88
101
|
if (missingFile(facts, ref.file)) {
|
|
89
102
|
violations.push({
|
|
90
103
|
rule_id: 'RC-01',
|
|
91
104
|
severity: 'warning',
|
|
92
105
|
element_id: el.id,
|
|
93
106
|
message: `${el.id} realRef.file '${ref.file}' does not exist on disk`,
|
|
94
|
-
fix_hint:
|
|
107
|
+
fix_hint: `Re-bind the FUNC to the current source tree via graph_mutate Format-E \`~ ${el.id} @realRef {"file":…,"symbol":…}\` — or restore the moved/renamed file`,
|
|
95
108
|
context: { element_type: el.type, element_name: el.name },
|
|
96
109
|
});
|
|
97
110
|
continue;
|
|
@@ -125,17 +138,17 @@ function testRefMustResolve(graph, facts) {
|
|
|
125
138
|
continue;
|
|
126
139
|
if (el.attributes?.concept === true)
|
|
127
140
|
continue;
|
|
128
|
-
const parsed =
|
|
129
|
-
if (
|
|
141
|
+
const parsed = readTestRefs(el.attributes);
|
|
142
|
+
if (parsed.state !== 'bound')
|
|
130
143
|
continue; // no/invalid binding → R-19 territory
|
|
131
|
-
for (const ref of parsed.
|
|
144
|
+
for (const ref of parsed.value) {
|
|
132
145
|
if (missingFile(facts, ref.file)) {
|
|
133
146
|
violations.push({
|
|
134
147
|
rule_id: 'RC-02',
|
|
135
148
|
severity: 'warning',
|
|
136
149
|
element_id: el.id,
|
|
137
150
|
message: `${el.id} testRefs entry file '${ref.file}' does not exist on disk`,
|
|
138
|
-
fix_hint:
|
|
151
|
+
fix_hint: `The test file was moved or deleted — rebind the TEST via graph_mutate Format-E \`~ ${el.id} @testRefs [{"file":…,"tool":…}]\` (the patch replaces the whole list — carry the other entries), or drop the entry`,
|
|
139
152
|
context: { element_type: el.type, element_name: el.name },
|
|
140
153
|
});
|
|
141
154
|
continue;
|
|
@@ -169,17 +182,17 @@ function schemaRefMustResolve(graph, facts) {
|
|
|
169
182
|
continue;
|
|
170
183
|
if (el.attributes?.concept === true || el.attributes?.external === true)
|
|
171
184
|
continue;
|
|
172
|
-
const parsed =
|
|
173
|
-
if (
|
|
185
|
+
const parsed = readRealRef(el.attributes);
|
|
186
|
+
if (parsed.state !== 'bound')
|
|
174
187
|
continue; // no/invalid binding → R-26 territory
|
|
175
|
-
const ref = parsed.
|
|
188
|
+
const ref = parsed.value;
|
|
176
189
|
if (missingFile(facts, ref.file)) {
|
|
177
190
|
violations.push({
|
|
178
191
|
rule_id: 'RC-03',
|
|
179
192
|
severity: 'warning',
|
|
180
193
|
element_id: el.id,
|
|
181
194
|
message: `${el.id} realRef.file '${ref.file}' does not exist on disk`,
|
|
182
|
-
fix_hint:
|
|
195
|
+
fix_hint: `Re-bind the SCHEMA to the current source tree via graph_mutate Format-E \`~ ${el.id} @realRef {"file":…,"symbol":…}\` — or restore the moved/renamed file`,
|
|
183
196
|
context: { element_type: el.type, element_name: el.name },
|
|
184
197
|
});
|
|
185
198
|
continue;
|
|
@@ -199,6 +212,61 @@ function schemaRefMustResolve(graph, facts) {
|
|
|
199
212
|
}
|
|
200
213
|
return violations;
|
|
201
214
|
}
|
|
215
|
+
// The files the MODEL names as a SCHEMA's interface: realRef files of the FUNCs io-connected
|
|
216
|
+
// (producer or consumer) to a FLOW whose data format is this SCHEMA (FUNC ─io→ FLOW
|
|
217
|
+
// ─relation→ SCHEMA), restricted to files that exist. RC-04 asks whether one of them parses
|
|
218
|
+
// the schema, RC-09 whether anyone ELSE does — one derivation, so both ask the same question.
|
|
219
|
+
function interfaceFiles(graph, schemaId, facts, typeOf) {
|
|
220
|
+
const flowIds = new Set(graph.traces
|
|
221
|
+
.filter(t => t.type === 'relation' && t.target === schemaId && typeOf.get(t.source) === 'FLOW')
|
|
222
|
+
.map(t => t.source));
|
|
223
|
+
if (flowIds.size === 0)
|
|
224
|
+
return [];
|
|
225
|
+
const funcIds = new Set(graph.traces
|
|
226
|
+
.filter(t => t.type === 'io' &&
|
|
227
|
+
((flowIds.has(t.target) && typeOf.get(t.source) === 'FUNC') ||
|
|
228
|
+
(flowIds.has(t.source) && typeOf.get(t.target) === 'FUNC')))
|
|
229
|
+
.map(t => (flowIds.has(t.target) ? t.source : t.target)));
|
|
230
|
+
const files = [];
|
|
231
|
+
for (const fnId of funcIds) {
|
|
232
|
+
const fn = graph.elements.find(e => e.id === fnId);
|
|
233
|
+
const cr = readRealRef(fn?.attributes);
|
|
234
|
+
if (cr.state === 'bound' && !missingFile(facts, cr.value.file))
|
|
235
|
+
files.push(cr.value.file);
|
|
236
|
+
}
|
|
237
|
+
return files;
|
|
238
|
+
}
|
|
239
|
+
// Does `file` parse the schema? It must call `.parse`/`.safeParse` on the symbol AND have it in
|
|
240
|
+
// scope — imported, or declared right there: the definition file does not import its own schema
|
|
241
|
+
// (CR-SM-358, found by the positive control on graphcode's Format-E door, where the modelled
|
|
242
|
+
// translator lives in the schema's own file).
|
|
243
|
+
function parsesAt(facts, file, ref) {
|
|
244
|
+
const f = facts.files[file];
|
|
245
|
+
if (f?.exists !== true || !f.parsedSymbols.includes(ref.symbol))
|
|
246
|
+
return false;
|
|
247
|
+
return f.importedSymbols.includes(ref.symbol) || file === ref.file;
|
|
248
|
+
}
|
|
249
|
+
// Files outside `allowed` that import AND parse the symbol — the parsers the model does not
|
|
250
|
+
// name. Test files (testCases present) are exempt; consumer ratchets cover them. Empty unless
|
|
251
|
+
// the extractor saw every file (`fileScope: 'all'`): a partial scan cannot name who else parses.
|
|
252
|
+
function foreignParsers(facts, symbol, allowed) {
|
|
253
|
+
if (facts.fileScope !== 'all')
|
|
254
|
+
return [];
|
|
255
|
+
return Object.entries(facts.files)
|
|
256
|
+
.filter(([file, f]) => f.exists && !allowed.has(file) && f.testCases.length === 0 &&
|
|
257
|
+
f.importedSymbols.includes(symbol) && f.parsedSymbols.includes(symbol))
|
|
258
|
+
.map(([file]) => file)
|
|
259
|
+
.sort();
|
|
260
|
+
}
|
|
261
|
+
// A SCHEMA whose realRef symbol is declared but, by the extractor's own account, not a Zod
|
|
262
|
+
// schema. False when the extractor did not report `zodSymbols` (silence, not a verdict) or
|
|
263
|
+
// when the symbol is not declared at all (RC-03's finding, not this one).
|
|
264
|
+
function boundToNonZod(facts, ref) {
|
|
265
|
+
const f = facts.files[ref.file];
|
|
266
|
+
if (f?.exists !== true || f.zodSymbols === undefined)
|
|
267
|
+
return false;
|
|
268
|
+
return f.declaredSymbols.includes(ref.symbol) && !f.zodSymbols.includes(ref.symbol);
|
|
269
|
+
}
|
|
202
270
|
// RC-04: a bound SCHEMA that the graph says is realized at an interface must
|
|
203
271
|
// actually be parsed there (CR-211). The graph gives the check LOCATIONS: FUNCs
|
|
204
272
|
// io-connected to a FLOW whose data format IS this SCHEMA (FUNC ─io→ FLOW
|
|
@@ -218,43 +286,29 @@ function schemaRefMustBeUsed(graph, facts) {
|
|
|
218
286
|
continue;
|
|
219
287
|
if (el.attributes?.concept === true)
|
|
220
288
|
continue;
|
|
221
|
-
const parsed =
|
|
222
|
-
if (
|
|
223
|
-
continue;
|
|
224
|
-
const ref = parsed.data;
|
|
225
|
-
// The FLOWs whose data format is this SCHEMA (FLOW ─relation→ SCHEMA).
|
|
226
|
-
const flowIds = new Set(graph.traces
|
|
227
|
-
.filter(t => t.type === 'relation' && t.target === el.id && typeOf.get(t.source) === 'FLOW')
|
|
228
|
-
.map(t => t.source));
|
|
229
|
-
if (flowIds.size === 0)
|
|
289
|
+
const parsed = readRealRef(el.attributes);
|
|
290
|
+
if (parsed.state !== 'bound' || parsed.value.symbol === undefined)
|
|
230
291
|
continue;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
(flowIds.has(t.source) && typeOf.get(t.target) === 'FUNC')))
|
|
236
|
-
.map(t => (flowIds.has(t.target) ? t.source : t.target)));
|
|
237
|
-
// Realized FUNCs among them: a resolvable realRef whose file exists.
|
|
238
|
-
const realizedFiles = [];
|
|
239
|
-
for (const fnId of funcIds) {
|
|
240
|
-
const fn = graph.elements.find(e => e.id === fnId);
|
|
241
|
-
const cr = RealRefSchema.safeParse(fn?.attributes?.realRef);
|
|
242
|
-
if (cr.success && !missingFile(facts, cr.data.file))
|
|
243
|
-
realizedFiles.push(cr.data.file);
|
|
244
|
-
}
|
|
292
|
+
const ref = parsed.value;
|
|
293
|
+
if (boundToNonZod(facts, ref))
|
|
294
|
+
continue; // RC-08 owns this cause (CR-SM-358)
|
|
295
|
+
const realizedFiles = interfaceFiles(graph, el.id, facts, typeOf);
|
|
245
296
|
if (realizedFiles.length === 0)
|
|
246
297
|
continue; // nothing realized to check against
|
|
247
|
-
const usedSomewhere = realizedFiles.some(file =>
|
|
248
|
-
const f = facts.files[file];
|
|
249
|
-
return f.importedSymbols.includes(ref.symbol) && f.parsedSymbols.includes(ref.symbol);
|
|
250
|
-
});
|
|
298
|
+
const usedSomewhere = realizedFiles.some(file => parsesAt(facts, file, ref));
|
|
251
299
|
if (!usedSomewhere) {
|
|
300
|
+
// CR-SM-358: where it IS parsed, if the extractor saw every file — then the finding says
|
|
301
|
+
// where the binding has to point, and RC-09 stays quiet about the same cause.
|
|
302
|
+
const actual = foreignParsers(facts, ref.symbol, new Set([ref.file, ...realizedFiles]));
|
|
252
303
|
violations.push({
|
|
253
304
|
rule_id: 'RC-04',
|
|
254
305
|
severity: 'warning',
|
|
255
306
|
element_id: el.id,
|
|
256
|
-
message: `${el.id} schema '${ref.symbol}' is not parsed in any realized FUNC at its modelled interface
|
|
257
|
-
|
|
307
|
+
message: `${el.id} schema '${ref.symbol}' is not parsed in any realized FUNC at its modelled interface` +
|
|
308
|
+
(actual.length > 0 ? ` — it is parsed in: ${actual.join(', ')}` : ''),
|
|
309
|
+
fix_hint: actual.length > 0
|
|
310
|
+
? `The parse sits in ${actual.join(', ')}: point the FUNC realRef at that file, or move the parse into the modelled FUNC`
|
|
311
|
+
: `Import and call ${ref.symbol}.parse()/.safeParse() in one of the io-connected FUNC's code files — or mark the SCHEMA concept:true if no code realizes it yet`,
|
|
258
312
|
context: { element_type: el.type, element_name: el.name },
|
|
259
313
|
});
|
|
260
314
|
}
|
|
@@ -295,12 +349,12 @@ function buildModResolver(graph) {
|
|
|
295
349
|
for (const el of graph.elements) {
|
|
296
350
|
if (el.type !== 'FUNC')
|
|
297
351
|
continue;
|
|
298
|
-
const cr =
|
|
299
|
-
if (
|
|
352
|
+
const cr = readRealRef(el.attributes);
|
|
353
|
+
if (cr.state !== 'bound')
|
|
300
354
|
continue;
|
|
301
355
|
const modTrace = graph.traces.find(t => t.source === el.id && t.type === 'allocate' && typeOf.get(t.target) === 'MOD');
|
|
302
356
|
if (modTrace)
|
|
303
|
-
fileToMod.set(cr.
|
|
357
|
+
fileToMod.set(cr.value.file, modTrace.target);
|
|
304
358
|
}
|
|
305
359
|
// MOD.path longest-prefix fallback for files with no direct binding.
|
|
306
360
|
const modPaths = graph.elements
|
|
@@ -439,10 +493,10 @@ function externalRefMustNameDependency(graph, facts) {
|
|
|
439
493
|
// `concept: true` has no binding that could rot — a concept node claims nothing about code.
|
|
440
494
|
if (el.attributes?.external !== true || el.attributes?.concept === true)
|
|
441
495
|
continue;
|
|
442
|
-
const parsed =
|
|
443
|
-
if (
|
|
496
|
+
const parsed = readRealRef(el.attributes);
|
|
497
|
+
if (parsed.state !== 'bound')
|
|
444
498
|
continue;
|
|
445
|
-
const match = WORKSPACE_PATH.exec(parsed.
|
|
499
|
+
const match = WORKSPACE_PATH.exec(parsed.value.file);
|
|
446
500
|
if (!match)
|
|
447
501
|
continue; // not the shape this rule can decide
|
|
448
502
|
const pkg = `${WORKSPACE_SCOPE}/${match[1]}`;
|
|
@@ -452,7 +506,7 @@ function externalRefMustNameDependency(graph, facts) {
|
|
|
452
506
|
rule_id: 'RC-06',
|
|
453
507
|
severity: 'warning',
|
|
454
508
|
element_id: el.id,
|
|
455
|
-
message: `${el.id} binds to '${parsed.
|
|
509
|
+
message: `${el.id} binds to '${parsed.value.file}', but '${pkg}' is not a declared dependency`,
|
|
456
510
|
fix_hint: `Add '${pkg}' to dependencies, or re-point the realRef at the package that now owns the symbol`,
|
|
457
511
|
context: { element_type: el.type, element_name: el.name },
|
|
458
512
|
});
|
|
@@ -514,6 +568,127 @@ function crNodeMatchesFile(graph, facts) {
|
|
|
514
568
|
return violations;
|
|
515
569
|
}
|
|
516
570
|
/** All RC conformance rules — evaluated by executors that can supply CodeFacts. */
|
|
571
|
+
// ---------------------------------------------------------------------------
|
|
572
|
+
// RC-08 / RC-09 (CR-SM-358) — the contract has a checker, and only the modelled places use it.
|
|
573
|
+
//
|
|
574
|
+
// Why these two replace ND-01 in substance: parallel paths are not similar to each other
|
|
575
|
+
// (spike CR-GC-637: name similarity 0.000 in 7 of 7 documented pairs); they share a CONTRACT.
|
|
576
|
+
// A second translator of the same contract is found by asking who parses it, not what it
|
|
577
|
+
// looks like. RC-01..RC-07 ask model → code ("does the binding resolve?"); RC-09 is the
|
|
578
|
+
// first rule that asks code → model ("does the model know everyone who reads this?").
|
|
579
|
+
// ---------------------------------------------------------------------------
|
|
580
|
+
// RC-08: a bound SCHEMA must bind a Zod schema, not a type. A type is checked by the
|
|
581
|
+
// compiler and gone at runtime: whatever produces the data cannot prove it, and whoever
|
|
582
|
+
// reads it cannot check it. Data that nobody parses yet only has a reader nobody knows yet.
|
|
583
|
+
function schemaRefMustBeZod(graph, facts) {
|
|
584
|
+
const violations = [];
|
|
585
|
+
for (const el of graph.elements) {
|
|
586
|
+
if (el.type !== 'SCHEMA')
|
|
587
|
+
continue;
|
|
588
|
+
if (el.attributes?.concept === true)
|
|
589
|
+
continue;
|
|
590
|
+
const parsed = readRealRef(el.attributes);
|
|
591
|
+
if (parsed.state !== 'bound' || parsed.value.symbol === undefined)
|
|
592
|
+
continue;
|
|
593
|
+
const ref = parsed.value;
|
|
594
|
+
if (!boundToNonZod(facts, ref))
|
|
595
|
+
continue;
|
|
596
|
+
violations.push({
|
|
597
|
+
rule_id: 'RC-08',
|
|
598
|
+
severity: 'warning',
|
|
599
|
+
element_id: el.id,
|
|
600
|
+
message: `${el.id} realRef '${ref.symbol}' in '${ref.file}' is not a Zod schema — the contract cannot be checked at runtime`,
|
|
601
|
+
fix_hint: `Declare the contract as a Zod schema (export const ${ref.symbol}Schema = z.…) and bind the SCHEMA to it — a producer needs a checkable schema even while no reader is known`,
|
|
602
|
+
context: { element_type: el.type, element_name: el.name },
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return violations;
|
|
606
|
+
}
|
|
607
|
+
// RC-09: a Zod-bound SCHEMA may be parsed only where the model says — in the realRef files
|
|
608
|
+
// of its producers/translators (the same set RC-04 checks) or in its own definition file.
|
|
609
|
+
// Any other file that imports AND parses the symbol is a translator the model does not
|
|
610
|
+
// know: a second path. The definition may live in another package (no facts entry) — only a
|
|
611
|
+
// proven type binding (RC-08) silences the rule. One finding per SCHEMA, listing the files — the count must not grow
|
|
612
|
+
// with usage (Gate 7, class CR-GC-315). Test files (testCases present) are exempt; ratchets
|
|
613
|
+
// in the consumer cover them. Runs only when the extractor saw every file (`fileScope:'all'`).
|
|
614
|
+
function schemaParsedOnlyAtInterface(graph, facts) {
|
|
615
|
+
if (facts.fileScope !== 'all')
|
|
616
|
+
return [];
|
|
617
|
+
// (foreignParsers repeats this guard; checking here skips the graph walk entirely.)
|
|
618
|
+
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
619
|
+
const violations = [];
|
|
620
|
+
for (const el of graph.elements) {
|
|
621
|
+
if (el.type !== 'SCHEMA')
|
|
622
|
+
continue;
|
|
623
|
+
if (el.attributes?.concept === true)
|
|
624
|
+
continue;
|
|
625
|
+
const parsed = readRealRef(el.attributes);
|
|
626
|
+
if (parsed.state !== 'bound' || parsed.value.symbol === undefined)
|
|
627
|
+
continue;
|
|
628
|
+
const ref = parsed.value;
|
|
629
|
+
// Only a PROVEN type binding silences RC-09 (RC-08 owns it). A definition outside this
|
|
630
|
+
// repo — the family's shared contracts live in @sigloch/contracts — has no facts entry,
|
|
631
|
+
// and those are exactly the contracts reused most; a `.parse()` on the symbol is already
|
|
632
|
+
// the evidence that it is a runtime checker.
|
|
633
|
+
if (boundToNonZod(facts, ref))
|
|
634
|
+
continue;
|
|
635
|
+
const iface = interfaceFiles(graph, el.id, facts, typeOf);
|
|
636
|
+
// One cause, one finding (Gate 4): if the model names an interface and NONE of it parses,
|
|
637
|
+
// the parse merely sits elsewhere — that is RC-04's finding (which names the file), not a
|
|
638
|
+
// second path. A second path needs a first one: a modelled parser AND a foreign one.
|
|
639
|
+
const ifaceParses = iface.some(file => parsesAt(facts, file, ref));
|
|
640
|
+
if (iface.length > 0 && !ifaceParses)
|
|
641
|
+
continue;
|
|
642
|
+
const foreign = foreignParsers(facts, ref.symbol, new Set([ref.file, ...iface]));
|
|
643
|
+
if (foreign.length === 0)
|
|
644
|
+
continue;
|
|
645
|
+
violations.push({
|
|
646
|
+
rule_id: 'RC-09',
|
|
647
|
+
severity: 'warning',
|
|
648
|
+
element_id: el.id,
|
|
649
|
+
message: `${el.id} schema '${ref.symbol}' is parsed in ${foreign.length} file(s) the model does not know as its producer or translator: ${foreign.join(', ')}`,
|
|
650
|
+
fix_hint: 'Route the caller through the modelled translator — or, if it is a translator in its own right, model it: FUNC ─io→ FLOW ─relation→ this SCHEMA, with the FUNC realRef on that file',
|
|
651
|
+
context: { element_type: el.type, element_name: el.name },
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
return violations;
|
|
655
|
+
}
|
|
656
|
+
// ---------------------------------------------------------------------------
|
|
657
|
+
// RC-10 (CR-SM-344): a MOD must be resolvable — carry a `path`, or have at least one
|
|
658
|
+
// allocated FUNC with a bound realRef. Those are the only two ways `buildModResolver`
|
|
659
|
+
// maps a file to a MOD; a MOD with neither can never own a file, so RC-05 and the
|
|
660
|
+
// boundary measurement are blind to it BY CONSTRUCTION. Measured at sigllm: 0 RC-05
|
|
661
|
+
// findings at 19 % import coverage, 8 at 81 % — the zero was the absence of a question.
|
|
662
|
+
// The cost in the message comes from `importCoverage` (the same resolution), not from a
|
|
663
|
+
// second count. `concept`/`external` are exempt — the same exemptions as R-20.
|
|
664
|
+
// ABSENT `importEdges` = silence, like RC-05: without an import graph RC-05 has nothing to
|
|
665
|
+
// judge, so there is no blindness to report — and the cost could not be named.
|
|
666
|
+
// ---------------------------------------------------------------------------
|
|
667
|
+
function modMustBeResolvable(graph, facts) {
|
|
668
|
+
if (facts.importEdges === undefined)
|
|
669
|
+
return []; // extractor supplied no import graph — silence
|
|
670
|
+
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
671
|
+
const boundFuncs = new Set(graph.elements.filter(e => e.type === 'FUNC' && readRealRef(e.attributes).state === 'bound').map(e => e.id));
|
|
672
|
+
const resolvedByFunc = new Set(graph.traces
|
|
673
|
+
.filter(t => t.type === 'allocate' && boundFuncs.has(t.source) && typeOf.get(t.target) === 'MOD')
|
|
674
|
+
.map(t => t.target));
|
|
675
|
+
const blind = graph.elements.filter(e => e.type === 'MOD' &&
|
|
676
|
+
e.attributes?.concept !== true && e.attributes?.external !== true &&
|
|
677
|
+
!(typeof e.attributes?.path === 'string' && e.attributes.path.length > 0) &&
|
|
678
|
+
!resolvedByFunc.has(e.id));
|
|
679
|
+
if (blind.length === 0)
|
|
680
|
+
return [];
|
|
681
|
+
const coverage = importCoverage(graph, facts);
|
|
682
|
+
const cost = `${coverage.unassigned.length} of ${coverage.endpoints} import endpoints stay unassigned in this repo`;
|
|
683
|
+
return blind.map(el => ({
|
|
684
|
+
rule_id: 'RC-10',
|
|
685
|
+
severity: 'warning',
|
|
686
|
+
element_id: el.id,
|
|
687
|
+
message: `${el.id} has neither a path nor an allocated FUNC with a realRef — no file can resolve to it, RC-05 is blind here (${cost})`,
|
|
688
|
+
fix_hint: `Set the module's source directory via graph_mutate Format-E \`~ ${el.id} @path <dir>\` — or bind one of its allocated FUNCs (\`~ FUNC-x @realRef {"file":…,"symbol":…}\`); mark it concept:true if no code realizes it yet`,
|
|
689
|
+
context: { element_type: el.type, element_name: el.name },
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
517
692
|
export const CODE_CONFORMANCE_RULES = [
|
|
518
693
|
{ id: 'RC-01', name: 'FUNC realRef resolves to a declared symbol', severity: 'warning', domain: ['FUNC'], evaluate: codeRefMustResolve },
|
|
519
694
|
{ id: 'RC-02', name: 'testRefs entries resolve to runnable tests', severity: 'warning', domain: ['TEST'], evaluate: testRefMustResolve },
|
|
@@ -522,6 +697,9 @@ export const CODE_CONFORMANCE_RULES = [
|
|
|
522
697
|
{ id: 'RC-05', name: 'cross-module import drift', severity: 'warning', domain: ['MOD'], evaluate: importDriftConformance },
|
|
523
698
|
{ id: 'RC-06', name: 'external realRef names a declared dependency', severity: 'warning', domain: ['FUNC', 'MOD', 'SCHEMA'], evaluate: externalRefMustNameDependency },
|
|
524
699
|
{ id: 'RC-07', name: 'CR node agrees with docs/cr', severity: 'warning', domain: ['CR', 'SYS'], evaluate: crNodeMatchesFile },
|
|
700
|
+
{ id: 'RC-08', name: 'SCHEMA realRef is a Zod schema', severity: 'warning', domain: ['SCHEMA'], evaluate: schemaRefMustBeZod },
|
|
701
|
+
{ id: 'RC-09', name: 'SCHEMA is parsed only at its modelled interface', severity: 'warning', domain: ['SCHEMA'], evaluate: schemaParsedOnlyAtInterface },
|
|
702
|
+
{ id: 'RC-10', name: 'MOD has resolvable files', severity: 'warning', domain: ['MOD'], evaluate: modMustBeResolvable },
|
|
525
703
|
];
|
|
526
704
|
/** Run all RC rules against a graph + extracted code facts. */
|
|
527
705
|
export function evaluateConformanceRules(graph, facts) {
|
|
@@ -8,6 +8,11 @@ import type { MetricPolicy } from './policy.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* Abgeschlossen heisst: nicht mehr steuerbar. Alles andere ist Grundgesamtheit.
|
|
10
10
|
* Exportiert fuer RC-07 (CR-SM-329), das dieselbe Frage gegen `docs/cr/done/` stellt.
|
|
11
|
+
*
|
|
12
|
+
* CR-SM-362 (ITEM-2026-536): nur noch `done`. Die Menge kannte `dropped`/`rejected`, der
|
|
13
|
+
* Element-Vertrag (`OntologyElement.status`) nicht — die Regeln rechneten mit Werten, die der
|
|
14
|
+
* Vertrag verbietet. Verworfen ist eine Begruendung im CR-Text, kein eigener Zustand: das
|
|
15
|
+
* Verzeichnis `done/` schliesst ihn, der Knoten spiegelt `done`.
|
|
11
16
|
*/
|
|
12
17
|
export declare const CLOSED_STATUS: ReadonlySet<string>;
|
|
13
18
|
export declare const CR_RULES: RuleDefinition[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readRealRef } from './ontology.js';
|
|
1
2
|
// ---------------------------------------------------------------------------
|
|
2
3
|
// CR-R01: ein offener CR nennt, WAS er aendert (CR-SM-295)
|
|
3
4
|
//
|
|
@@ -36,8 +37,13 @@ const SCOPE_TYPES = new Set(['FUNC', 'MOD', 'SCHEMA', 'REQ', 'UC']);
|
|
|
36
37
|
/**
|
|
37
38
|
* Abgeschlossen heisst: nicht mehr steuerbar. Alles andere ist Grundgesamtheit.
|
|
38
39
|
* Exportiert fuer RC-07 (CR-SM-329), das dieselbe Frage gegen `docs/cr/done/` stellt.
|
|
40
|
+
*
|
|
41
|
+
* CR-SM-362 (ITEM-2026-536): nur noch `done`. Die Menge kannte `dropped`/`rejected`, der
|
|
42
|
+
* Element-Vertrag (`OntologyElement.status`) nicht — die Regeln rechneten mit Werten, die der
|
|
43
|
+
* Vertrag verbietet. Verworfen ist eine Begruendung im CR-Text, kein eigener Zustand: das
|
|
44
|
+
* Verzeichnis `done/` schliesst ihn, der Knoten spiegelt `done`.
|
|
39
45
|
*/
|
|
40
|
-
export const CLOSED_STATUS = new Set(['done'
|
|
46
|
+
export const CLOSED_STATUS = new Set(['done']);
|
|
41
47
|
function crMustTrack(graph) {
|
|
42
48
|
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
43
49
|
const crs = graph.elements.filter(e => {
|
|
@@ -173,6 +179,76 @@ function crShouldHaveMilestone(graph) {
|
|
|
173
179
|
}));
|
|
174
180
|
}
|
|
175
181
|
// ---------------------------------------------------------------------------
|
|
182
|
+
// CR-R05: kein Blatt-REQ ohne Bauauftrag (CR-SM-343) — die REQ-Seite von CR-R01.
|
|
183
|
+
//
|
|
184
|
+
// RD-01 fragt nach einem Traeger, R-01 nach einer Verifikation; ob ein REQ einen BAUAUFTRAG
|
|
185
|
+
// hat, fragte niemand. Gemessen am Fremdlauf sigllm: `se-plan` meldete „20 von 20 geordnet",
|
|
186
|
+
// im selben Graphstand hatten 24 von 64 Blatt-REQ keinen Auftrag — genau die, die alle 16
|
|
187
|
+
// offenen FM-03-Risiken mildern.
|
|
188
|
+
//
|
|
189
|
+
// Beauftragt ist ein Blatt-REQ (kein `compose`→REQ-Kind), wenn ein CR eine `relation` (a)
|
|
190
|
+
// direkt darauf traegt oder (b) auf eine FUNC, die es `satisfy`t. **MOD und SYS zaehlen
|
|
191
|
+
// nicht:** ein Modul ist ein Behaelter — mitgerechnet las sigllm 55/64 statt 40/64 und sah
|
|
192
|
+
// gesund aus. Wer ein REQ an einem MOD-/SYS-Traeger beauftragt, zieht die `relation` direkt
|
|
193
|
+
// auf das REQ. FCHAIN ist kein Weg: `CR -relation-> FCHAIN` ist kein TRACE_PATTERN, eine
|
|
194
|
+
// solche Kante waere R-18 — ein FCHAIN-getragenes REQ braucht also ebenfalls die direkte Kante.
|
|
195
|
+
//
|
|
196
|
+
// Jeder CR zaehlt, auch ein abgeschlossener: gebaut ist beauftragt.
|
|
197
|
+
//
|
|
198
|
+
// **Eine gebaute FUNC deckt ohne CR** (CR-SM-375): traegt die FUNC eine gueltige `realRef`
|
|
199
|
+
// oder ist sie `external`, ist der Bauauftrag erledigt — Bestand vor der CR-Disziplin braucht
|
|
200
|
+
// keinen nachgetragenen Auftrag. `concept: true` ist nicht gebaut, auch mit realRef. Fuer
|
|
201
|
+
// MOD/SYS/FCHAIN aendert das nichts: dort deckt nur die direkte `CR -relation-> REQ`.
|
|
202
|
+
//
|
|
203
|
+
// **Ohne CR-Knoten schweigt die Regel** und zaehlt weder im Zaehler noch im Nenner
|
|
204
|
+
// (`RULE_PRECONDITION`, readiness.ts). Ein Repo ohne Plan im Graphen (`cr: docs`) bekaeme
|
|
205
|
+
// sonst auf jedem REQ einen Befund — die Regel wuerde abgeschaltet und waere danach ueberall
|
|
206
|
+
// still (Fail-open-Klasse ND-01/ND-02 vor CR-SM-286).
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
function funcIsBuilt(e) {
|
|
209
|
+
if (e.attributes?.concept === true)
|
|
210
|
+
return false;
|
|
211
|
+
return e.attributes?.external === true || readRealRef(e.attributes).state === 'bound';
|
|
212
|
+
}
|
|
213
|
+
function leafReqNeedsBuildOrder(graph) {
|
|
214
|
+
const crIds = new Set(graph.elements.filter(e => e.type === 'CR').map(e => e.id));
|
|
215
|
+
if (crIds.size === 0)
|
|
216
|
+
return [];
|
|
217
|
+
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
218
|
+
const ordered = new Set(); // CR -relation-> X, oder eine bereits gebaute FUNC
|
|
219
|
+
for (const t of graph.traces) {
|
|
220
|
+
if (t.type === 'relation' && crIds.has(t.source))
|
|
221
|
+
ordered.add(t.target);
|
|
222
|
+
}
|
|
223
|
+
for (const e of graph.elements) {
|
|
224
|
+
if (e.type === 'FUNC' && funcIsBuilt(e))
|
|
225
|
+
ordered.add(e.id);
|
|
226
|
+
}
|
|
227
|
+
const covered = new Set();
|
|
228
|
+
const parents = new Set();
|
|
229
|
+
for (const t of graph.traces) {
|
|
230
|
+
if (t.type === 'relation' && crIds.has(t.source) && typeOf.get(t.target) === 'REQ')
|
|
231
|
+
covered.add(t.target);
|
|
232
|
+
if (t.type === 'satisfy' && typeOf.get(t.source) === 'FUNC' && ordered.has(t.source))
|
|
233
|
+
covered.add(t.target);
|
|
234
|
+
if (t.type === 'compose' && typeOf.get(t.source) === 'REQ' && typeOf.get(t.target) === 'REQ')
|
|
235
|
+
parents.add(t.source);
|
|
236
|
+
}
|
|
237
|
+
return graph.elements
|
|
238
|
+
.filter(e => e.type === 'REQ' && !parents.has(e.id) && !covered.has(e.id))
|
|
239
|
+
.map(req => ({
|
|
240
|
+
rule_id: 'CR-R05',
|
|
241
|
+
severity: 'warning',
|
|
242
|
+
element_id: req.id,
|
|
243
|
+
message: `${req.id} is a leaf REQ with no build order (no CR relation to it or to a FUNC that satisfies it, and no built FUNC satisfies it)`,
|
|
244
|
+
fix_hint: `Add a CR relation through graph_mutate Format-E \`CR-x -relation-> ${req.id}\` — or to the FUNC that satisfies it; a CR on its MOD/SYS carrier does not count`,
|
|
245
|
+
context: {
|
|
246
|
+
element_type: req.type,
|
|
247
|
+
element_name: req.name,
|
|
248
|
+
},
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
176
252
|
// Exports
|
|
177
253
|
// ---------------------------------------------------------------------------
|
|
178
254
|
export const CR_RULES = [
|
|
@@ -185,6 +261,7 @@ export const CR_RULES = [
|
|
|
185
261
|
// Damit ist sie nach R-08/R-18 die dritte 'all'-Regel — bewusst, nicht vergessen.
|
|
186
262
|
{ id: 'CR-R03', name: 'No concurrent mutation', severity: 'warning', evaluate: noConcurrentMutation, domain: ['all'] },
|
|
187
263
|
{ id: 'MS-03', name: 'CR without milestone', severity: 'info', evaluate: crShouldHaveMilestone, domain: ['CR'] },
|
|
264
|
+
{ id: 'CR-R05', name: 'Leaf REQ has a build order', severity: 'warning', evaluate: leafReqNeedsBuildOrder, domain: ['REQ'] },
|
|
188
265
|
];
|
|
189
266
|
// CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
|
|
190
267
|
// ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
|
|
@@ -30,5 +30,12 @@ export declare function getRuleDefsForProfile(profile: ProfileId): typeof ALL_RU
|
|
|
30
30
|
* CR-SM-233: `policy` ist **Pflicht und ohne Fallback** — ein Aufruf ohne Policy ist ein
|
|
31
31
|
* Typfehler, keine stille 0.7. Wer keine eigene Quelle hat (Konfiguration, Host), nimmt
|
|
32
32
|
* `DEFAULT_METRIC_POLICY` sichtbar an der Aufrufstelle.
|
|
33
|
+
*
|
|
34
|
+
* CR-SM-378: `only` ist ein REGELFILTER, kein zweiter Auswerter — dieselbe Normalisierung,
|
|
35
|
+
* dieselbe Reihenfolge, dieselben `evaluate`-Aufrufe; eine Regel ausserhalb des Filters laeuft nur
|
|
36
|
+
* nicht. Das Ergebnis ist gleich dem Vollergebnis, nachtraeglich auf `only` gefiltert. Grund:
|
|
37
|
+
* der Steuer-Operator (se-engine `bestBySteer`) bewertete jeden Kandidaten mit dem ganzen
|
|
38
|
+
* Katalog, inklusive des quadratischen ND-01 — 8,9 von 17,1 s bei 2000 Knoten fuer Befunde, die
|
|
39
|
+
* er nie las. Ohne `only` laeuft der ganze Katalog.
|
|
33
40
|
*/
|
|
34
|
-
export declare function evaluateAllRules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
|
|
41
|
+
export declare function evaluateAllRules(graph: OntologyGraph, policy: MetricPolicy, only?: ReadonlySet<string>): RuleViolation[];
|
package/dist/se/evaluate-all.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { V3_RULES
|
|
2
|
-
import { SC_RULES
|
|
3
|
-
import { UC_RULES
|
|
4
|
-
import { FC_RULES
|
|
5
|
-
import { MT_RULES
|
|
1
|
+
import { V3_RULES } from './rules.js';
|
|
2
|
+
import { SC_RULES } from './schema-quality-rules.js';
|
|
3
|
+
import { UC_RULES } from './uc-quality-rules.js';
|
|
4
|
+
import { FC_RULES } from './fchain-quality-rules.js';
|
|
5
|
+
import { MT_RULES } from './metric-rules.js';
|
|
6
6
|
import { toEvaluableGraph } from './flat-graph.js';
|
|
7
|
-
import { FM_RULES
|
|
8
|
-
import { VIEW_RULES
|
|
9
|
-
import { CR_RULES
|
|
10
|
-
import { ND_RULES
|
|
11
|
-
import { AO_RULES
|
|
12
|
-
import { BQ_RULES
|
|
13
|
-
import { AF_RULES,
|
|
7
|
+
import { FM_RULES } from './fmea-rules.js';
|
|
8
|
+
import { VIEW_RULES } from './view-rules.js';
|
|
9
|
+
import { CR_RULES } from './cr-quality-rules.js';
|
|
10
|
+
import { ND_RULES } from './near-duplicate-rules.js';
|
|
11
|
+
import { AO_RULES } from './ao-rules.js';
|
|
12
|
+
import { BQ_RULES } from './quality-rules.js';
|
|
13
|
+
import { AF_RULES, TASK_OUTCOME_RULES } from './analysis-freshness-rules.js';
|
|
14
14
|
import { CODE_CONFORMANCE_RULES } from './conformance-rules.js';
|
|
15
15
|
/**
|
|
16
16
|
* CR-SM-285: das Profil haengt am KATALOG, nicht am ID-Praefix.
|
|
@@ -67,14 +67,31 @@ export function getRuleDefsForProfile(profile) {
|
|
|
67
67
|
return ALL_RULE_DEFS;
|
|
68
68
|
return ALL_RULE_DEFS.filter(r => r.profile === profile);
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Die ausgewerteten Kataloge in der Reihenfolge der kanonischen Befund-Sequenz (CR-SM-240).
|
|
72
|
+
* Ohne `CODE_CONFORMANCE_RULES` — die brauchen `CodeFacts` (s. CATALOGS). Die Reihenfolge ist
|
|
73
|
+
* Vertrag: das Golden (`se-rule-output-identity.test.ts`) pinnt sie.
|
|
74
|
+
*/
|
|
75
|
+
const EVALUATED_CATALOGS = [
|
|
76
|
+
V3_RULES, BQ_RULES, UC_RULES, FC_RULES, SC_RULES, ND_RULES, MT_RULES, CR_RULES, AO_RULES,
|
|
77
|
+
FM_RULES, VIEW_RULES, AF_RULES,
|
|
78
|
+
TASK_OUTCOME_RULES, // CR-SM-355
|
|
79
|
+
];
|
|
70
80
|
/**
|
|
71
81
|
* Evaluate all rules against a graph. Single call replaces the individual evaluator calls.
|
|
72
82
|
*
|
|
73
83
|
* CR-SM-233: `policy` ist **Pflicht und ohne Fallback** — ein Aufruf ohne Policy ist ein
|
|
74
84
|
* Typfehler, keine stille 0.7. Wer keine eigene Quelle hat (Konfiguration, Host), nimmt
|
|
75
85
|
* `DEFAULT_METRIC_POLICY` sichtbar an der Aufrufstelle.
|
|
86
|
+
*
|
|
87
|
+
* CR-SM-378: `only` ist ein REGELFILTER, kein zweiter Auswerter — dieselbe Normalisierung,
|
|
88
|
+
* dieselbe Reihenfolge, dieselben `evaluate`-Aufrufe; eine Regel ausserhalb des Filters laeuft nur
|
|
89
|
+
* nicht. Das Ergebnis ist gleich dem Vollergebnis, nachtraeglich auf `only` gefiltert. Grund:
|
|
90
|
+
* der Steuer-Operator (se-engine `bestBySteer`) bewertete jeden Kandidaten mit dem ganzen
|
|
91
|
+
* Katalog, inklusive des quadratischen ND-01 — 8,9 von 17,1 s bei 2000 Knoten fuer Befunde, die
|
|
92
|
+
* er nie las. Ohne `only` laeuft der ganze Katalog.
|
|
76
93
|
*/
|
|
77
|
-
export function evaluateAllRules(graph, policy) {
|
|
94
|
+
export function evaluateAllRules(graph, policy, only) {
|
|
78
95
|
// CR-SM-284: die Hebung der flach committeten SSOT laeuft HIER, nicht beim Aufrufer.
|
|
79
96
|
//
|
|
80
97
|
// `toEvaluableGraph` gibt es seit CR-SM-258 — und sie wurde von keinem Produktionspfad und
|
|
@@ -84,20 +101,6 @@ export function evaluateAllRules(graph, policy) {
|
|
|
84
101
|
//
|
|
85
102
|
// Idempotent und identitaetserhaltend (s. dort), also eine Normalisierung am einzigen Eingang
|
|
86
103
|
// und kein Fallback: ein bereits genesteter Graph geht unveraendert und uneingepackt durch.
|
|
87
|
-
|
|
88
|
-
return [
|
|
89
|
-
...evaluateRules(graph, policy),
|
|
90
|
-
...evaluateBQRules(graph, policy),
|
|
91
|
-
...evaluateUCRules(graph, policy),
|
|
92
|
-
...evaluateFCRules(graph, policy),
|
|
93
|
-
...evaluateSCRules(graph, policy),
|
|
94
|
-
...evaluateNDRules(graph),
|
|
95
|
-
...evaluateMTRules(graph, policy),
|
|
96
|
-
...evaluateCRRules(graph, policy),
|
|
97
|
-
...evaluateAORules(graph, policy),
|
|
98
|
-
...evaluateFMRules(graph, policy),
|
|
99
|
-
...evaluateViewRules(graph, policy),
|
|
100
|
-
...evaluateAFRules(graph),
|
|
101
|
-
...evaluateTaskOutcomeRules(graph), // CR-SM-355
|
|
102
|
-
];
|
|
104
|
+
const evaluable = toEvaluableGraph(graph);
|
|
105
|
+
return EVALUATED_CATALOGS.flatMap((rules) => rules.flatMap((rule) => (only === undefined || only.has(rule.id) ? rule.evaluate(evaluable, policy) : [])));
|
|
103
106
|
}
|