@sigloch/contracts 6.3.0 → 10.0.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/ao-rules.d.ts +12 -12
- package/dist/se/ao-rules.js +88 -225
- package/dist/se/conformance-rules.d.ts +28 -0
- package/dist/se/conformance-rules.js +40 -17
- package/dist/se/cr-quality-rules.js +75 -58
- package/dist/se/evaluate-all.d.ts +4 -2
- package/dist/se/evaluate-all.js +40 -24
- package/dist/se/fchain-quality-rules.d.ts +0 -1
- package/dist/se/fchain-quality-rules.js +0 -44
- package/dist/se/flat-graph.d.ts +9 -1
- package/dist/se/flat-graph.js +15 -6
- package/dist/se/format-e-parser.d.ts +14 -2
- package/dist/se/format-e-parser.js +33 -9
- package/dist/se/grammar-snapshot.d.ts +10 -7
- package/dist/se/grammar-snapshot.js +172 -27
- package/dist/se/index.d.ts +4 -3
- package/dist/se/index.js +4 -3
- package/dist/se/meta-model.d.ts +99 -7
- package/dist/se/meta-model.js +155 -14
- package/dist/se/metric-rules.d.ts +20 -1
- package/dist/se/metric-rules.js +97 -58
- package/dist/se/module-crossings.d.ts +102 -0
- package/dist/se/module-crossings.js +196 -0
- package/dist/se/near-duplicate-rules.d.ts +16 -24
- package/dist/se/near-duplicate-rules.js +21 -92
- package/dist/se/ontology.d.ts +32 -47
- package/dist/se/ontology.js +56 -13
- package/dist/se/policy.d.ts +6 -0
- package/dist/se/policy.js +53 -2
- package/dist/se/quality-rules.d.ts +18 -0
- package/dist/se/quality-rules.js +68 -12
- package/dist/se/readiness.d.ts +7 -5
- package/dist/se/readiness.js +40 -22
- package/dist/se/rules.d.ts +6 -7
- package/dist/se/rules.js +358 -128
- package/dist/se/schema-quality-rules.d.ts +0 -1
- package/dist/se/schema-quality-rules.js +7 -28
- package/dist/se/similarity.d.ts +61 -0
- package/dist/se/similarity.js +116 -0
- package/dist/se/uc-quality-rules.js +31 -12
- package/package.json +3 -2
package/dist/se/rules.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod/v4';
|
|
7
7
|
import { ElementType, TraceType, TestRefsSchema, RealRefSchema } from './ontology.js';
|
|
8
|
-
import {
|
|
8
|
+
import { traceRejection, BOUNDED_PATTERNS, REQUIRED_PATTERNS, maxOccurs } from './meta-model.js';
|
|
9
9
|
import { indexOf } from './graph-index.js';
|
|
10
|
+
import { crossingContractCount, subtreeFuncs } from './module-crossings.js';
|
|
10
11
|
export const RuleSeverity = z.enum(['error', 'warning', 'info']);
|
|
11
12
|
/** Candidate target for resolving a violation (e.g. a REQ to satisfy, a TEST to link). */
|
|
12
13
|
export const ViolationCandidate = z.object({
|
|
@@ -25,6 +26,31 @@ export const ViolationContext = z.object({
|
|
|
25
26
|
})).optional(),
|
|
26
27
|
parent_module: z.string().optional(),
|
|
27
28
|
current_description: z.string().optional(),
|
|
29
|
+
/**
|
|
30
|
+
* CR-SM-304: die WEITEREN Elemente, die derselbe Befund betrifft — heute gesetzt von CR-R03,
|
|
31
|
+
* das einen Befund je Konflikt-MENGE meldet und ihn am kanonisch ersten Ziel verankert. Ohne
|
|
32
|
+
* dieses Feld verschwanden die uebrigen Ziele lautlos: der Leser erfuhr von genau einem der
|
|
33
|
+
* Knoten, an denen sich zwei offene CRs begegnen. Ein Befund darf zusammenfassen, aber nichts
|
|
34
|
+
* unterschlagen.
|
|
35
|
+
*/
|
|
36
|
+
also_affects: z.array(z.string()).optional(),
|
|
37
|
+
/**
|
|
38
|
+
* CR-SM-288: die gemessene Zahl der Regel an diesem Element — und das Budget, gegen das sie
|
|
39
|
+
* geschwellt wurde. Zusammen ergeben sie die **Verstoßmasse** `d = max(0, value - threshold)`,
|
|
40
|
+
* die CR-SM-287 lexikographisch rankt. Ohne diese zwei Felder muesste ein Ranker den
|
|
41
|
+
* Meldungstext parsen; CR-SM-280 hat genau diese Texte umgeschrieben, ohne dass sich ein
|
|
42
|
+
* Urteil aenderte.
|
|
43
|
+
*
|
|
44
|
+
* `threshold` ist der **groesste Wert, der noch INNERHALB des Budgets liegt** — nicht der
|
|
45
|
+
* Vergleichswert der Regel. Nur so gilt ueber alle Regeln einheitlich `d > 0 <=> warning`,
|
|
46
|
+
* obwohl die einen bei `>` und die anderen bei `>=` melden (RD-04/MT-01 exklusiv,
|
|
47
|
+
* BW-02/CR-01/MT-02 inklusiv). Ein `info`-Befund innerhalb des Budgets hat damit `d = 0`.
|
|
48
|
+
*
|
|
49
|
+
* Nur die fuenf messenden Regeln setzen sie (RD-04, BW-02, CR-01, MT-01, MT-02); eine Regel
|
|
50
|
+
* ohne Zahl laesst sie weg, statt eine zu erfinden.
|
|
51
|
+
*/
|
|
52
|
+
value: z.number().optional(),
|
|
53
|
+
threshold: z.number().optional(),
|
|
28
54
|
});
|
|
29
55
|
export const RuleViolation = z.object({
|
|
30
56
|
rule_id: z.string(),
|
|
@@ -206,36 +232,6 @@ function funcMustSatisfyReq(graph) {
|
|
|
206
232
|
// ---------------------------------------------------------------------------
|
|
207
233
|
// R-03: ASIL isolation
|
|
208
234
|
// ---------------------------------------------------------------------------
|
|
209
|
-
function asilIsolation(graph) {
|
|
210
|
-
const idx = indexOf(graph);
|
|
211
|
-
const violations = [];
|
|
212
|
-
const modules = idx.elementsOfType('MOD');
|
|
213
|
-
for (const mod of modules) {
|
|
214
|
-
const allocated = graph.traces
|
|
215
|
-
.filter(t => t.target === mod.id && t.type === 'allocate')
|
|
216
|
-
.map(t => idx.byId.get(t.source))
|
|
217
|
-
.filter((e) => !!e);
|
|
218
|
-
const hasD = allocated.some(e => e.asil === 'D');
|
|
219
|
-
const hasQM = allocated.some(e => e.asil === 'QM');
|
|
220
|
-
if (hasD && hasQM) {
|
|
221
|
-
const dFuncs = allocated.filter(e => e.asil === 'D');
|
|
222
|
-
const qmFuncs = allocated.filter(e => e.asil === 'QM');
|
|
223
|
-
violations.push({
|
|
224
|
-
rule_id: 'R-03',
|
|
225
|
-
severity: 'error',
|
|
226
|
-
element_id: mod.id,
|
|
227
|
-
message: `${mod.id} mixes ASIL-D and ASIL-QM functions`,
|
|
228
|
-
fix_hint: 'Separate ASIL-D functions into isolated module',
|
|
229
|
-
context: {
|
|
230
|
-
element_type: mod.type,
|
|
231
|
-
element_name: mod.name,
|
|
232
|
-
candidate_targets: toCandidates([...dFuncs, ...qmFuncs]),
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return violations;
|
|
238
|
-
}
|
|
239
235
|
// ---------------------------------------------------------------------------
|
|
240
236
|
// R-04: Modulgroesse GEGEN Kreuzungen (nicht „max module size")
|
|
241
237
|
//
|
|
@@ -245,6 +241,13 @@ function asilIsolation(graph) {
|
|
|
245
241
|
// Urteilsschwelle, die sich weder per grep noch aus dem Regelnamen ablesen laesst, ist nicht
|
|
246
242
|
// ueberpruefbar. Jetzt sind es `policy.moduleSize.{coupled,large,crossings}`.
|
|
247
243
|
// `null` → messen, nicht urteilen: die Regel schweigt.
|
|
244
|
+
//
|
|
245
|
+
// CR-SM-276: „Kreuzung" heisst hier dasselbe wie bei CR-01 — ein Vertrag auf dem io-Pfad
|
|
246
|
+
// `FUNC -io-> FLOW -io-> FUNC` UEBER die Modulgrenze (`module-crossings.ts`). Vorher zaehlte
|
|
247
|
+
// die Regel jede io-Kante einer Modul-FUNC „mit einem Endpunkt ausserhalb der FUNC-Menge" —
|
|
248
|
+
// FLOWs liegen nie in dieser Menge, also zaehlte auch jeder modulINTERNE Fluss mit. Das war der
|
|
249
|
+
// io-Grad des Moduls und fuer jedes Modul mit Datenfluss trivial > `crossings`; damit war R-04
|
|
250
|
+
// faktisch wieder „max module size", der Name, den CR-SM-236 gerade verworfen hatte.
|
|
248
251
|
// ---------------------------------------------------------------------------
|
|
249
252
|
function maxModuleSize(graph, policy) {
|
|
250
253
|
const idx = indexOf(graph);
|
|
@@ -254,25 +257,24 @@ function maxModuleSize(graph, policy) {
|
|
|
254
257
|
return violations;
|
|
255
258
|
const modules = idx.elementsOfType('MOD');
|
|
256
259
|
for (const mod of modules) {
|
|
257
|
-
|
|
260
|
+
// CR-SM-282: die Groesse ist die des TEILBAUMS, nicht der direkten Allokation. Ein
|
|
261
|
+
// Eltern-MOD (`MOD -compose-> MOD`) hat keine direkt allozierten FUNCs; `funcCount`
|
|
262
|
+
// war dort 0 und die Regel schwieg an genau der Whitebox, in die man hineinklickt.
|
|
263
|
+
// Fuer ein Blatt-MOD ist die Menge zeichengleich mit der bisherigen (nur `FUNC
|
|
264
|
+
// -allocate-> MOD` existiert als Pattern) — Regressions-Invariante.
|
|
265
|
+
const allocatedIds = [...subtreeFuncs(graph, mod.id)];
|
|
258
266
|
const allocated = allocatedIds.map(id => idx.byId.get(id)).filter((e) => !!e);
|
|
259
267
|
const funcCount = allocated.length;
|
|
260
268
|
if (funcCount <= steps.coupled)
|
|
261
269
|
continue;
|
|
262
|
-
//
|
|
263
|
-
const
|
|
264
|
-
// CR-SM-264: nur die io-Kanten, nicht alle Traces je MOD.
|
|
265
|
-
const crossings = idx.tracesOfType('io').filter(t => {
|
|
266
|
-
const srcIn = funcIds.has(t.source);
|
|
267
|
-
const tgtIn = funcIds.has(t.target);
|
|
268
|
-
return (srcIn && !tgtIn) || (!srcIn && tgtIn);
|
|
269
|
-
}).length;
|
|
270
|
+
// Verschiedene Vertraege (SCHEMA) auf io-Pfaden ueber DIESEN Modulrand — CR-SM-276.
|
|
271
|
+
const crossings = crossingContractCount(graph, mod.id);
|
|
270
272
|
if (funcCount > steps.large && crossings === 0) {
|
|
271
273
|
violations.push({
|
|
272
274
|
rule_id: 'R-04',
|
|
273
275
|
severity: 'info',
|
|
274
276
|
element_id: mod.id,
|
|
275
|
-
message: `${mod.id} has ${funcCount} functions but 0
|
|
277
|
+
message: `${mod.id} has ${funcCount} functions but 0 contracts crossing its module boundary (cohesive, just large)`,
|
|
276
278
|
fix_hint: `Size alone is not the finding: > ${steps.large} functions without crossing flows is cohesive. Split only if crossings appear`,
|
|
277
279
|
context: { element_type: mod.type, element_name: mod.name, candidate_targets: toCandidates(allocated) },
|
|
278
280
|
});
|
|
@@ -282,7 +284,7 @@ function maxModuleSize(graph, policy) {
|
|
|
282
284
|
rule_id: 'R-04',
|
|
283
285
|
severity: 'warning',
|
|
284
286
|
element_id: mod.id,
|
|
285
|
-
message: `${mod.id} has ${funcCount} functions and ${crossings} crossing
|
|
287
|
+
message: `${mod.id} has ${funcCount} functions and ${crossings} distinct contract(s) crossing its module boundary (split recommended)`,
|
|
286
288
|
fix_hint: `Split module into smaller units to reduce coupling (> ${steps.large} functions AND crossing flows)`,
|
|
287
289
|
context: { element_type: mod.type, element_name: mod.name, candidate_targets: toCandidates(allocated) },
|
|
288
290
|
});
|
|
@@ -292,7 +294,7 @@ function maxModuleSize(graph, policy) {
|
|
|
292
294
|
rule_id: 'R-04',
|
|
293
295
|
severity: 'warning',
|
|
294
296
|
element_id: mod.id,
|
|
295
|
-
message: `${mod.id} has ${funcCount} functions and ${crossings} crossing
|
|
297
|
+
message: `${mod.id} has ${funcCount} functions and ${crossings} distinct contract(s) crossing its module boundary (high coupling)`,
|
|
296
298
|
fix_hint: 'Reduce crossing flows or split module',
|
|
297
299
|
context: { element_type: mod.type, element_name: mod.name, candidate_targets: toCandidates(allocated) },
|
|
298
300
|
});
|
|
@@ -333,8 +335,9 @@ function testMustVerifyReq(graph) {
|
|
|
333
335
|
// ---------------------------------------------------------------------------
|
|
334
336
|
function traceConsistency(graph) {
|
|
335
337
|
const idx = indexOf(graph);
|
|
338
|
+
// CR-SM-266 D5: kein `category !== 'audit'`-Filter mehr — das Attribut ist weg, und mit ihm
|
|
339
|
+
// die Moeglichkeit, eine Kante per Selbstauskunft aus der Referenzintegritaet zu nehmen.
|
|
336
340
|
return graph.traces
|
|
337
|
-
.filter(t => t.category !== 'audit')
|
|
338
341
|
.filter(t => {
|
|
339
342
|
const sourceExists = idx.byId.has(t.source);
|
|
340
343
|
const targetExists = idx.byId.has(t.target);
|
|
@@ -361,7 +364,9 @@ function flowCompleteness(graph) {
|
|
|
361
364
|
const idx = indexOf(graph);
|
|
362
365
|
const flows = idx.elementsOfType('FLOW');
|
|
363
366
|
// Producers/consumers of a FLOW are FUNCs or ACTORs (io endpoints on the
|
|
364
|
-
// upstream/downstream side)
|
|
367
|
+
// upstream/downstream side). CR-SM-266 D4: UC ist KEIN Konsument mehr — das Pattern
|
|
368
|
+
// `FLOW -io-> UC` ist entfallen, ein FLOW erreicht einen UC nur noch ueber ein Kettenglied.
|
|
369
|
+
// Der fix_hint hat den UC vorher noch angeboten und haette in eine R-18-Ablehnung gefuehrt.
|
|
365
370
|
const sources = graph.elements.filter(e => e.type === 'FUNC' || e.type === 'ACTOR'); // Mehrtyp: ein Durchlauf ist hier billiger als vier Index-Listen zu mischen (Reihenfolge!)
|
|
366
371
|
const io = idx.tracesOfType('io');
|
|
367
372
|
const violations = [];
|
|
@@ -383,7 +388,7 @@ function flowCompleteness(graph) {
|
|
|
383
388
|
severity: 'warning',
|
|
384
389
|
element_id: f.id,
|
|
385
390
|
message: `${f.id} has no consumer (outgoing io)`,
|
|
386
|
-
fix_hint: 'Link a FUNC
|
|
391
|
+
fix_hint: 'Link a FUNC or ACTOR as the target via io trace',
|
|
387
392
|
context: ctx,
|
|
388
393
|
});
|
|
389
394
|
}
|
|
@@ -407,25 +412,81 @@ const CIRCULAR_TRACE_TYPES = new Set(['compose', 'allocate', 'relation']);
|
|
|
407
412
|
function noDirectCircular(graph) {
|
|
408
413
|
const idx = indexOf(graph);
|
|
409
414
|
const violations = [];
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
415
|
+
// CR-SM-285: ECHTE Zyklenerkennung statt Rueckkanten-Test.
|
|
416
|
+
//
|
|
417
|
+
// Bis hierher pruefte die Regel nur, ob eine Kante DIREKT zurueckläuft (A->B und B->A). Ein
|
|
418
|
+
// Dreierzyklus A->B->C->A ging vollstaendig durch das Gate, mit null Fehlern — bei einem
|
|
419
|
+
// Regelnamen, der Azyklizitaet verspricht. Das ist die gefaehrlichere Haelfte: ein Absturz
|
|
420
|
+
// meldet sich, eine Pruefung, die ihren Gegenstand nicht erreicht, BESTAETIGT.
|
|
421
|
+
//
|
|
422
|
+
// Warum es zaehlt: jeder Rollup der Familie setzt Azyklizitaet voraus — `chainOf`/`funcChainOf`
|
|
423
|
+
// in `module-crossings.ts` (CR-SM-282/-283) tragen `seen`-Guards gegen genau diese Endlosschleife,
|
|
424
|
+
// `moduleMetrics`, die RD-04-Breitenzaehlung und gves Container-Sicht ebenso. Unter einem Zyklus
|
|
425
|
+
// ist ein Rollup nicht falsch, sondern UNDEFINIERT. Das R-18-Bein aus CR-SM-283 deckt es NICHT:
|
|
426
|
+
// bei A->B->C->A hat jeder Knoten genau EINEN compose-Elternteil, die Baum-Eigenschaft ist formal
|
|
427
|
+
// erfuellt und die Struktur trotzdem kein Baum.
|
|
428
|
+
//
|
|
429
|
+
// Gemessen ueber neun Familiengraphen: 0 Zyklen jeder Laenge, also 0 -> 0 Befunde. Die Schaerfung
|
|
430
|
+
// laesst keinen bestehenden Graphen fallen; sie schliesst eine Luecke, bevor sie jemand findet.
|
|
431
|
+
//
|
|
432
|
+
// EIN Befund je Zyklus, nicht je Kante (Klasse CR-SM-242): bei einem Ring der Laenge n sind nicht
|
|
433
|
+
// n Kanten falsch, es ist EINE Struktur. Verankert am kleinsten Knoten des Rings — determiniert,
|
|
434
|
+
// unabhaengig davon, wo die DFS eingestiegen ist (Gate 5).
|
|
435
|
+
for (const type of [...CIRCULAR_TRACE_TYPES].sort()) {
|
|
436
|
+
const adj = new Map();
|
|
437
|
+
for (const t of graph.traces) {
|
|
438
|
+
if (t.type !== type)
|
|
439
|
+
continue;
|
|
440
|
+
const list = adj.get(t.source);
|
|
441
|
+
if (list)
|
|
442
|
+
list.push(t.target);
|
|
443
|
+
else
|
|
444
|
+
adj.set(t.source, [t.target]);
|
|
445
|
+
}
|
|
446
|
+
for (const list of adj.values())
|
|
447
|
+
list.sort();
|
|
448
|
+
const WHITE = 0, GREY = 1, BLACK = 2;
|
|
449
|
+
const color = new Map();
|
|
450
|
+
const stack = [];
|
|
451
|
+
const found = new Map(); // kanonischer Schluessel -> Ring in Reihenfolge
|
|
452
|
+
const visit = (u) => {
|
|
453
|
+
color.set(u, GREY);
|
|
454
|
+
stack.push(u);
|
|
455
|
+
for (const v of adj.get(u) ?? []) {
|
|
456
|
+
if (color.get(v) === GREY) {
|
|
457
|
+
const ring = stack.slice(stack.indexOf(v));
|
|
458
|
+
// Kanonisch: am kleinsten Knoten rotiert, damit derselbe Ring aus jedem Einstieg
|
|
459
|
+
// denselben Schluessel und dieselbe Meldung ergibt.
|
|
460
|
+
let min = 0;
|
|
461
|
+
for (let i = 1; i < ring.length; i++)
|
|
462
|
+
if (ring[i] < ring[min])
|
|
463
|
+
min = i;
|
|
464
|
+
const rotated = [...ring.slice(min), ...ring.slice(0, min)];
|
|
465
|
+
found.set(rotated.join('|'), rotated);
|
|
466
|
+
}
|
|
467
|
+
else if (color.get(v) === undefined) {
|
|
468
|
+
visit(v);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
color.set(u, BLACK);
|
|
472
|
+
stack.pop();
|
|
473
|
+
};
|
|
474
|
+
for (const u of [...adj.keys()].sort())
|
|
475
|
+
if (color.get(u) === undefined)
|
|
476
|
+
visit(u);
|
|
477
|
+
for (const ring of [...found.keys()].sort().map(k => found.get(k))) {
|
|
478
|
+
const el = idx.byId.get(ring[0]);
|
|
479
|
+
violations.push({
|
|
480
|
+
rule_id: 'R-12',
|
|
481
|
+
severity: 'warning',
|
|
482
|
+
element_id: ring[0],
|
|
483
|
+
message: ring.length === 2
|
|
484
|
+
? `Circular ${type} between ${ring[0]} and ${ring[1]}`
|
|
485
|
+
: `Circular ${type} over ${ring.length} elements: ${[...ring, ring[0]].join(' -> ')}`,
|
|
486
|
+
fix_hint: `Break the ${type} ring — every rollup (module boundary, decomposition breadth, container view) is undefined while it exists`,
|
|
487
|
+
context: { element_type: el?.type, element_name: el?.name },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
429
490
|
}
|
|
430
491
|
return violations;
|
|
431
492
|
}
|
|
@@ -507,16 +568,46 @@ function noPrematureDecomposition(graph) {
|
|
|
507
568
|
// RD-04: Decomposition breadth (CR-SM-221)
|
|
508
569
|
// ---------------------------------------------------------------------------
|
|
509
570
|
/**
|
|
510
|
-
* Max children on one decomposition level. The 7
|
|
571
|
+
* Max children on one decomposition level. The 7±2 convention existed only as
|
|
511
572
|
* prose ("darüber func-of-func"); below 7 is a guideline, not a violation, so only
|
|
512
573
|
* the upper bound is a rule.
|
|
513
574
|
*
|
|
514
575
|
* Counted per (parent, kind) — a MOD that both holds 12 FUNCs and composes 12
|
|
515
576
|
* sub-MODs has two breadth problems, not one. (The spike reference merged both into
|
|
516
577
|
* a single counter on the MOD id and would have reported 24 under one kind.)
|
|
578
|
+
*
|
|
579
|
+
* CR-SM-282, zwei Aenderungen:
|
|
580
|
+
*
|
|
581
|
+
* 1. **Die Schwelle steht in `policy.decompositionBreadth`**, nicht mehr inline. `null`
|
|
582
|
+
* schaltet die Regel ab (messen statt urteilen). Der Default war hier 11 (verhaltensgleich
|
|
583
|
+
* zum vorherigen Literal) und ist seit CR-SM-296 **9** — die Obergrenze von 7±2.
|
|
584
|
+
* 2. **Der FUNC-Wurzelwald zaehlt mit.** Die Regel zaehlt Kinder je *Parent*; die Wurzeln des
|
|
585
|
+
* `FUNC -compose-> FUNC`-Waldes haben keinen — die oberste Funktionsebene war damit fuer
|
|
586
|
+
* RD-04 unsichtbar. Gemessen: moneyflow hat 306 Wurzel-FUNCs und RD-04 meldete **0**.
|
|
587
|
+
* Die MOD-Seite hatte das Problem nie, weil `SYS -compose-> MOD` eine echte Kante ist und
|
|
588
|
+
* unten schon als `sub-MOD` gezaehlt wird; die FUNC-Seite hat keine solche Kante
|
|
589
|
+
* (`se:top-level`: „the top FUNC set is a PROJECTION, not an edge").
|
|
590
|
+
*
|
|
591
|
+
* Anker ist der SYS-Knoten — determiniert, weil genau einer existiert; gibt es nicht genau
|
|
592
|
+
* einen, schweigt dieser Zweig statt zu raten (R-17 deckt den Fall ab). Kein zweiter Weg zur
|
|
593
|
+
* Wurzelmenge: es ist dieselbe Definition wie in `se:top-level` (FUNC ohne compose-Elternteil).
|
|
517
594
|
*/
|
|
518
|
-
|
|
519
|
-
|
|
595
|
+
// CR-SM-296: das Bein `FUNC -allocate-> MOD` ist ENTFALLEN und an R-04 abgegeben.
|
|
596
|
+
//
|
|
597
|
+
// Es zaehlte die allozierten FUNCs je Modul — also die MODULGROESSE, und die misst R-04 auch.
|
|
598
|
+
// Beide feuerten am selben Modul im selben Batch, mit zwei Schwellen aus zwei Policy-Feldern:
|
|
599
|
+
// RD-04 > 11 allozierte FUNC-Kinder policy.decompositionBreadth
|
|
600
|
+
// R-04 > 12 FUNCs (bzw. 8-12 mit Kreuzungen) policy.moduleSize
|
|
601
|
+
// Ein Sachverhalt, zwei Regeln, zwei Zahlen — und welche recht hat, stand nirgends. Schlimmer:
|
|
602
|
+
// dieselbe Ursache ging doppelt in den readiness-Nenner. R-04 ist die reichere Aussage (Groesse
|
|
603
|
+
// GEGEN Kreuzungen, drei Urteile) und liest ueber `moduleCrossings` ohnehin dieselben Daten.
|
|
604
|
+
//
|
|
605
|
+
// RD-04 bleibt fuer ZERLEGUNGSBREITE zustaendig: `FUNC compose FUNC`, `SYS/MOD compose MOD` und
|
|
606
|
+
// der FUNC-Wurzelwald am SYS (CR-SM-282). Je Frage eine Regel, je Frage eine Schwelle.
|
|
607
|
+
function decompositionBreadth(graph, policy) {
|
|
608
|
+
const max = policy.decompositionBreadth;
|
|
609
|
+
if (max === null)
|
|
610
|
+
return [];
|
|
520
611
|
const idx = indexOf(graph);
|
|
521
612
|
// CR-SM-264: `typeOf` und `byId` baute diese Regel je Aufruf selbst — der Index hat beide.
|
|
522
613
|
const typeOf = idx;
|
|
@@ -536,48 +627,36 @@ function decompositionBreadth(graph) {
|
|
|
536
627
|
if (t.type === 'compose' && src === 'FUNC' && tgt === 'FUNC') {
|
|
537
628
|
bump(t.source, 'sub-FUNC');
|
|
538
629
|
}
|
|
539
|
-
else if (t.type === 'allocate' && tgt === 'MOD' && src === 'FUNC') {
|
|
540
|
-
bump(t.target, 'allocated FUNC');
|
|
541
|
-
}
|
|
542
630
|
else if (t.type === 'compose' && (src === 'SYS' || src === 'MOD') && tgt === 'MOD') {
|
|
543
631
|
bump(t.source, 'sub-MOD');
|
|
544
632
|
}
|
|
545
633
|
}
|
|
634
|
+
// CR-SM-282: der FUNC-Wurzelwald als eigene Blackbox, verankert am SYS-Knoten.
|
|
635
|
+
const composedFuncs = new Set(graph.traces
|
|
636
|
+
.filter(t => t.type === 'compose' && typeOf.typeOf(t.source) === 'FUNC' && typeOf.typeOf(t.target) === 'FUNC')
|
|
637
|
+
.map(t => t.target));
|
|
638
|
+
const systems = idx.elementsOfType('SYS');
|
|
639
|
+
if (systems.length === 1) {
|
|
640
|
+
const rootFuncs = idx.elementsOfType('FUNC').filter(f => !composedFuncs.has(f.id)).length;
|
|
641
|
+
if (rootFuncs > 0)
|
|
642
|
+
counts.set(`${systems[0].id}\u0000root FUNC`, { parentId: systems[0].id, kind: 'root FUNC', n: rootFuncs });
|
|
643
|
+
}
|
|
546
644
|
return [...counts.values()]
|
|
547
|
-
.filter(c => c.n >
|
|
645
|
+
.filter(c => c.n > max.warning)
|
|
548
646
|
.map(c => {
|
|
549
647
|
const parent = byId.get(c.parentId);
|
|
550
648
|
return {
|
|
551
649
|
rule_id: 'RD-04',
|
|
552
650
|
severity: 'warning',
|
|
553
651
|
element_id: c.parentId,
|
|
554
|
-
message: `${c.parentId} has ${c.n} ${c.kind} children on one level (>${
|
|
652
|
+
message: `${c.parentId} has ${c.n} ${c.kind} children on one level (>${max.warning})`,
|
|
555
653
|
fix_hint: 'Introduce an intermediate level (func-of-func / sub-MOD)',
|
|
556
|
-
|
|
654
|
+
// CR-SM-288: `> max.warning` ist exklusiv, also ist `max.warning` selbst noch im Budget.
|
|
655
|
+
context: { element_type: parent?.type, element_name: parent?.name, value: c.n, threshold: max.warning },
|
|
557
656
|
};
|
|
558
657
|
});
|
|
559
658
|
}
|
|
560
659
|
// ---------------------------------------------------------------------------
|
|
561
|
-
// R-14: UC must have at least 1 compose trace (→ FCHAIN or REQ) (CR-117)
|
|
562
|
-
// ---------------------------------------------------------------------------
|
|
563
|
-
function ucMustHaveCompose(graph) {
|
|
564
|
-
const idx = indexOf(graph);
|
|
565
|
-
const ucs = idx.elementsOfType('UC');
|
|
566
|
-
return ucs
|
|
567
|
-
.filter(uc => idx.out(uc.id, 'compose').length === 0)
|
|
568
|
-
.map(uc => ({
|
|
569
|
-
rule_id: 'R-14',
|
|
570
|
-
severity: 'warning',
|
|
571
|
-
element_id: uc.id,
|
|
572
|
-
message: `${uc.id} has no compose traces (no scenarios or requirements)`,
|
|
573
|
-
fix_hint: 'Add FCHAIN or REQ via compose trace',
|
|
574
|
-
context: {
|
|
575
|
-
element_type: uc.type,
|
|
576
|
-
element_name: uc.name,
|
|
577
|
-
},
|
|
578
|
-
}));
|
|
579
|
-
}
|
|
580
|
-
// ---------------------------------------------------------------------------
|
|
581
660
|
// R-15: FCHAIN completeness — a chain needs functions BELOW it and a use case
|
|
582
661
|
// ABOVE it (CR-117, second leg CR-SM-249).
|
|
583
662
|
//
|
|
@@ -660,7 +739,7 @@ function sysMustHaveCompose(graph) {
|
|
|
660
739
|
severity: 'warning',
|
|
661
740
|
element_id: sys.id,
|
|
662
741
|
message: `${sys.id} has no compose traces (empty system)`,
|
|
663
|
-
fix_hint: 'Add
|
|
742
|
+
fix_hint: 'Add at least one compose trace — the system decomposes into nothing',
|
|
664
743
|
context: {
|
|
665
744
|
element_type: sys.type,
|
|
666
745
|
element_name: sys.name,
|
|
@@ -721,23 +800,192 @@ function validTracePattern(graph) {
|
|
|
721
800
|
const idx = indexOf(graph);
|
|
722
801
|
// CR-SM-264: die Typ-Map kommt aus dem Index statt je Aufruf neu gebaut zu werden.
|
|
723
802
|
const typeById = { get: (id) => idx.typeOf(id) };
|
|
803
|
+
// CR-SM-266 D5: der `category !== 'audit'`-Filter ist WEG. Er war die Ausnahme fuer das
|
|
804
|
+
// einzige audit-Pattern (`SESSION -produces-> *`); mit dessen Entfernung waere er zum
|
|
805
|
+
// Schlupfloch geworden — eine Kante mit `category: 'audit'` haette die Matrix komplett
|
|
806
|
+
// umgangen. Das Attribut existiert nicht mehr (ontology.ts), hier faellt der Leser mit.
|
|
724
807
|
return graph.traces
|
|
725
|
-
.
|
|
726
|
-
.filter(t => {
|
|
808
|
+
.flatMap((t) => {
|
|
727
809
|
const src = typeById.get(t.source);
|
|
728
810
|
const tgt = typeById.get(t.target);
|
|
729
811
|
if (!src || !tgt)
|
|
730
|
-
return
|
|
731
|
-
|
|
812
|
+
return []; // dangling endpoint → R-08, not R-18
|
|
813
|
+
// CR-SM-266 B: die where-Patterns lesen die Kinds des betroffenen Endes mit.
|
|
814
|
+
// CR-SM-280: und der GRUND der Ablehnung kommt mit heraus, statt zu `false` zu kollabieren.
|
|
815
|
+
const why = traceRejection({
|
|
816
|
+
source: src, target: tgt, type: t.type, label: t.label,
|
|
817
|
+
sourceKinds: idx.byId.get(t.source)?.kinds,
|
|
818
|
+
targetKinds: idx.byId.get(t.target)?.kinds,
|
|
819
|
+
});
|
|
820
|
+
if (!why)
|
|
821
|
+
return [];
|
|
822
|
+
const { detail, hint } = explainRejection(why, t, src, tgt);
|
|
823
|
+
return [{
|
|
824
|
+
rule_id: 'R-18',
|
|
825
|
+
severity: 'error',
|
|
826
|
+
element_id: t.source,
|
|
827
|
+
message: `Invalid trace ${t.source} -${t.type}-> ${t.target}: ${detail}`,
|
|
828
|
+
fix_hint: hint,
|
|
829
|
+
}];
|
|
732
830
|
})
|
|
733
|
-
.
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
831
|
+
.concat(cardinalityViolations(graph))
|
|
832
|
+
.concat(singleComposeParent(graph));
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* CR-SM-280: aus dem Ablehnungsgrund den Satz bauen, den der Leser braucht.
|
|
836
|
+
*
|
|
837
|
+
* Der `no-pattern`-Zweig traegt den Wortlaut von vorher UNVERAENDERT — dort war die alte
|
|
838
|
+
* Meldung richtig. Nur die beiden `where`-Zweige sind neu, und sie unterscheiden zwei
|
|
839
|
+
* Ursachen, die vorher zu einer verschmolzen waren: `kinds` fehlt (die BQ-07-Migration) vs.
|
|
840
|
+
* `kinds` steht da und passt nicht (Kante heben oder Kind korrigieren). Wer das nicht
|
|
841
|
+
* unterscheiden kann, sucht am falschen Ende.
|
|
842
|
+
*/
|
|
843
|
+
function explainRejection(why, t, src, tgt) {
|
|
844
|
+
if (why.reason === 'no-pattern') {
|
|
845
|
+
return {
|
|
846
|
+
detail: `${src} → ${tgt} is not a valid ${t.type} pattern`,
|
|
847
|
+
hint: 'Use a trace type whose TRACE_PATTERNS allows this source/target element-type pair',
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
const { where } = why.pattern;
|
|
851
|
+
// Der Knoten wird NUR ueber seine Id benannt, nicht zusaetzlich ueber seinen Typ: der
|
|
852
|
+
// ElementUid-Kanon (`<TYPE>-<slug>`, ONTOLOGY 8.0.0) traegt den Typ bereits im Namen, und
|
|
853
|
+
// "REQ REQ-f2-riegel" liest sich wie ein Fehler im Werkzeug.
|
|
854
|
+
const end = where.on === 'source' ? t.source : t.target;
|
|
855
|
+
const allowed = [...where.allowed].sort().join(', ');
|
|
856
|
+
const admits = `the ${src} → ${tgt} ${t.type} pattern admits only ${where.field} {${allowed}}`;
|
|
857
|
+
return why.reason === 'kinds-undeclared'
|
|
858
|
+
? {
|
|
859
|
+
detail: `${admits}, and ${end} declares no ${where.field}`,
|
|
860
|
+
hint: `Declare '${where.field}' on ${end} — undeclared is not "any" (CR-SM-266 B); it must be a subset of {${allowed}}`,
|
|
861
|
+
}
|
|
862
|
+
: {
|
|
863
|
+
detail: `${admits}, but ${end} declares {${[...why.declared].sort().join(', ')}}`,
|
|
864
|
+
hint: `Correct '${where.field}' on ${end}, or use a source type whose ${t.type} pattern admits them (FCHAIN carries every kind)`,
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* CR-SM-283 — das VIERTE BEIN von R-18: die `compose`-Baeume sind Baeume.
|
|
869
|
+
*
|
|
870
|
+
* `TRACE_PATTERNS` begrenzt heute nur die KINDER einer Kante, nie die ELTERN
|
|
871
|
+
* (`se:top-level`: „the cardinalities cap children, not parents"). Ein Element kann damit zwei
|
|
872
|
+
* `compose`-Elternteile bekommen, und `graph_mutate` legt so eine Kante anstandslos an.
|
|
873
|
+
*
|
|
874
|
+
* Warum das hart sein muss, obwohl es NIE vorgekommen ist (0 Verstoesse ueber fuenf
|
|
875
|
+
* Familiengraphen): jeder Rollup dieser Familie ist unter Verletzung nicht falsch, sondern
|
|
876
|
+
* UNDEFINIERT — `moduleCrossings.byModule`/`byFunc` (CR-SM-282/-283), die Breiten-Zaehlung von
|
|
877
|
+
* RD-04, die Container-Sicht von gve. Ein mehrdeutiges Rollup liefert keine schlechtere Zahl,
|
|
878
|
+
* sondern keine. Das ist der Unterschied zu einer Regel, die „etwas prueft, das nie vorkommt".
|
|
879
|
+
*
|
|
880
|
+
* Kein eigener Regel-Eintrag, aus demselben Grund wie beim zweiten und dritten Bein: die
|
|
881
|
+
* Aussage ist dieselbe — „diese Kante ist nach dem Meta-Modell nicht zulaessig" —, und der
|
|
882
|
+
* naechste Schritt ist derselbe: eine der beiden Kanten weg.
|
|
883
|
+
*
|
|
884
|
+
* EIN Befund je KIND, nicht je ueberzaehliger Kante: bei n Eltern sind nicht n-1 Kanten
|
|
885
|
+
* „falsch", es ist EINE Entscheidung offen (welcher Elternteil), und n-1 Befunde trieben den
|
|
886
|
+
* Zaehler ueber den Nenner-Beitrag (die Fehlmessung aus CR-SM-242).
|
|
887
|
+
*/
|
|
888
|
+
function singleComposeParent(graph) {
|
|
889
|
+
const idx = indexOf(graph);
|
|
890
|
+
const parents = new Map();
|
|
891
|
+
for (const t of graph.traces) {
|
|
892
|
+
if (t.type !== 'compose')
|
|
893
|
+
continue;
|
|
894
|
+
const src = idx.typeOf(t.source);
|
|
895
|
+
const tgt = idx.typeOf(t.target);
|
|
896
|
+
if (!((src === 'FUNC' && tgt === 'FUNC') || (src === 'MOD' && tgt === 'MOD')))
|
|
897
|
+
continue;
|
|
898
|
+
let set = parents.get(t.target);
|
|
899
|
+
if (!set) {
|
|
900
|
+
set = new Set();
|
|
901
|
+
parents.set(t.target, set);
|
|
902
|
+
}
|
|
903
|
+
set.add(t.source);
|
|
904
|
+
}
|
|
905
|
+
return [...parents.entries()]
|
|
906
|
+
.filter(([, ps]) => ps.size > 1)
|
|
907
|
+
// Kanonisch statt in Trace-Reihenfolge — sonst haengt die Befund-Sequenz an der
|
|
908
|
+
// Eingabereihenfolge (Reihenfolgen-Invariante, se-grammar-invariant.test.ts).
|
|
909
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
910
|
+
.map(([child, ps]) => {
|
|
911
|
+
const el = idx.byId.get(child);
|
|
912
|
+
const sorted = [...ps].sort();
|
|
913
|
+
return {
|
|
914
|
+
rule_id: 'R-18',
|
|
915
|
+
severity: 'error',
|
|
916
|
+
element_id: child,
|
|
917
|
+
message: `${child} has ${sorted.length} compose parents (${sorted.join(', ')}): the compose trees must stay trees`,
|
|
918
|
+
fix_hint: `Keep exactly one compose parent for ${child} and delete the other edge(s) — every rollup (module boundary, decomposition breadth, container view) is undefined while there are two`,
|
|
919
|
+
context: { element_type: el?.type, element_name: el?.name },
|
|
920
|
+
};
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* CR-SM-266b — das ZWEITE BEIN von R-18: die Kardinalitaets-Obergrenzen.
|
|
925
|
+
* CR-SM-271 Teil 2 — das DRITTE: die durchgesetzten Untergrenzen (heute genau
|
|
926
|
+
* `FLOW -relation-> SCHEMA [1..1]`; SC-04 ist damit ersatzlos entfallen).
|
|
927
|
+
*
|
|
928
|
+
* Bewusst dieselbe `rule_id` und keine eigene, nach dem Muster von R-10 (Produzent/Konsument)
|
|
929
|
+
* und R-15 seit CR-SM-249: die Aussage ist dieselbe — "diese Kante ist nach dem Meta-Modell
|
|
930
|
+
* nicht zulaessig" —, nur ist die Bedingung hier eine ueber die MENGE statt ueber das Paar.
|
|
931
|
+
* Eine eigene ID kostete dauerhaft einen Nenner-Anteil, eine Katalogzeile, eine
|
|
932
|
+
* readiness-Zuordnung und einen Golden-File-Eintrag (Gate 6), und ein Leser muesste sie von
|
|
933
|
+
* R-18 unterscheiden, obwohl der naechste Schritt derselbe ist: Kante weg.
|
|
934
|
+
*
|
|
935
|
+
* EIN Befund je Quellknoten, nicht je ueberzaehliger Kante. Bei n Allokationen sind nicht
|
|
936
|
+
* n-1 Kanten "falsch" — es ist EINE Entscheidung offen (welches Modul), und n-1 Befunde
|
|
937
|
+
* wuerden den Zaehler ueber den Nenner-Beitrag treiben, die Fehlmessung aus CR-SM-242.
|
|
938
|
+
*/
|
|
939
|
+
function cardinalityViolations(graph) {
|
|
940
|
+
const idx = indexOf(graph);
|
|
941
|
+
const violations = [];
|
|
942
|
+
for (const p of BOUNDED_PATTERNS) {
|
|
943
|
+
const max = maxOccurs(p.cardinality);
|
|
944
|
+
for (const el of idx.elementsOfType(p.source)) {
|
|
945
|
+
const matching = idx.out(el.id, p.type).filter(t => idx.typeOf(t.target) === p.target);
|
|
946
|
+
if (matching.length <= max)
|
|
947
|
+
continue;
|
|
948
|
+
violations.push({
|
|
949
|
+
rule_id: 'R-18',
|
|
950
|
+
severity: 'error',
|
|
951
|
+
element_id: el.id,
|
|
952
|
+
message: `${el.id} has ${matching.length} ${p.type} traces to ${p.target} — ` +
|
|
953
|
+
`the meta-model allows at most ${max} (${matching.map(t => t.target).join(', ')})`,
|
|
954
|
+
fix_hint: `Keep exactly one ${p.type} trace from ${el.id} to a ${p.target} and delete the others`,
|
|
955
|
+
context: {
|
|
956
|
+
element_type: el.type,
|
|
957
|
+
element_name: el.name,
|
|
958
|
+
existing_traces: matching.map(t => ({ source: t.source, target: t.target, type: t.type })),
|
|
959
|
+
},
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
// Untergrenzen (CR-SM-271 Teil 2): ein FLOW ohne SCHEMA ist untypisiert, nicht unfertig —
|
|
964
|
+
// der frueher von SC-04 (warning) gemeldete Fall ist jetzt Grammatik und meldet als error.
|
|
965
|
+
// EIN Befund je Quellknoten, mit candidate_targets im Kontext (wie SC-04 sie trug), damit
|
|
966
|
+
// ein Agent die Kante aus dem Payload heraus ergaenzen kann.
|
|
967
|
+
for (const p of REQUIRED_PATTERNS) {
|
|
968
|
+
const candidates = idx.elementsOfType(p.target);
|
|
969
|
+
for (const el of idx.elementsOfType(p.source)) {
|
|
970
|
+
const matching = idx.out(el.id, p.type).filter(t => idx.typeOf(t.target) === p.target);
|
|
971
|
+
if (matching.length > 0)
|
|
972
|
+
continue;
|
|
973
|
+
violations.push({
|
|
974
|
+
rule_id: 'R-18',
|
|
975
|
+
severity: 'error',
|
|
976
|
+
element_id: el.id,
|
|
977
|
+
message: `${el.id} has no ${p.type} trace to a ${p.target} — the meta-model requires exactly one`,
|
|
978
|
+
fix_hint: `Add a ${p.type} trace from ${el.id} to the ${p.target} that defines its contract` +
|
|
979
|
+
` (create the ${p.target} in the same batch if it does not exist yet)`,
|
|
980
|
+
context: {
|
|
981
|
+
element_type: el.type,
|
|
982
|
+
element_name: el.name,
|
|
983
|
+
candidate_targets: candidates.map(c => ({ id: c.id, type: c.type, name: c.name })),
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return violations;
|
|
741
989
|
}
|
|
742
990
|
// ---------------------------------------------------------------------------
|
|
743
991
|
// R-19: Runnable TEST binding (CR-GC-205 Item 4) — a TEST that is not explicitly
|
|
@@ -1081,30 +1329,15 @@ function schemaMustHaveSchemaRef(graph) {
|
|
|
1081
1329
|
// Symbol is optional on a realRef (a geometry file has none) — presence = a valid
|
|
1082
1330
|
// realRef with a file. RESOLUTION (file on disk) is a consumer/RC concern.
|
|
1083
1331
|
// ---------------------------------------------------------------------------
|
|
1084
|
-
function physicalModMustHaveRealRef(graph) {
|
|
1085
|
-
const idx = indexOf(graph);
|
|
1086
|
-
return idx.elementsOfType('MOD')
|
|
1087
|
-
.filter(e => e.attributes?.kind === 'physical')
|
|
1088
|
-
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
1089
|
-
.filter(e => !RealRefSchema.safeParse(e.attributes?.realRef).success)
|
|
1090
|
-
.map(mod => ({
|
|
1091
|
-
rule_id: 'R-27',
|
|
1092
|
-
severity: 'warning',
|
|
1093
|
-
element_id: mod.id,
|
|
1094
|
-
message: `${mod.id} is a physical MOD without a valid realRef (CAD/geometry) binding`,
|
|
1095
|
-
fix_hint: 'Add attributes.realRef {file, symbol?, lang?} pointing at the CAD/geometry artefact, or set attributes.concept:true / attributes.external:true',
|
|
1096
|
-
context: { element_type: mod.type, element_name: mod.name },
|
|
1097
|
-
}));
|
|
1098
|
-
}
|
|
1099
1332
|
// ---------------------------------------------------------------------------
|
|
1100
1333
|
// R-28 (Ebenen-Präsenz) ist mit CR-SM-247 ersatzlos entfallen — nicht weil sie falsch war,
|
|
1101
1334
|
// sondern weil sie seit CR-GC-366 doppelt ist. Sie schloss das Vacuous-Complete-Loch (mit 0/1
|
|
1102
1335
|
// FUNC feuert keine Pro-Element-Regel, alles liest "fertig"); beide Hälften trägt heute je eine
|
|
1103
1336
|
// präzisere Regel: kein FLOW → R-31 (io-Verdrahtung, je FUNC statt einmal je Graph) + IO-01,
|
|
1104
|
-
// kein SCHEMA → SC-04 (je FLOW)
|
|
1337
|
+
// kein SCHEMA → damals SC-04 (je FLOW), seit CR-SM-271 das R-18-Untergrenzen-Bein. Probe
|
|
1338
|
+
// (SYS+UC+FCHAIN+3 FUNC, ohne FLOW/SCHEMA): 28 Befunde aus
|
|
1105
1339
|
// 18 Regeln, ohne R-28 bleiben 17 — das Loch bleibt zu. Gemessen feuerte sie 1× / 0× an den
|
|
1106
|
-
// Selbstmodellen, dort neben 14× IO-01 und 10× R-31.
|
|
1107
|
-
// SCHEMA-Absenz rutscht von PDR nach CDR, weil SC-04 erst feuert, wenn FLOWs existieren.
|
|
1340
|
+
// Selbstmodellen, dort neben 14× IO-01 und 10× R-31.
|
|
1108
1341
|
// ---------------------------------------------------------------------------
|
|
1109
1342
|
// ---------------------------------------------------------------------------
|
|
1110
1343
|
// R-30: jedes BLATT gehoert in eine Wirkkette (CR-GC-366, Grundgesamtheit
|
|
@@ -1211,10 +1444,8 @@ export const V3_RULES = [
|
|
|
1211
1444
|
{ id: 'R-02', name: 'FUNC must satisfy REQ', severity: 'warning', evaluate: funcMustSatisfyReq, domain: ['FUNC'] },
|
|
1212
1445
|
// CR-SM-243: domain ist MOD, nicht FUNC — die Regel iteriert Module und meldet am Modul,
|
|
1213
1446
|
// das ASIL-D und QM mischt. Die FUNCs sind der Anlass des Urteils, nicht seine Traeger.
|
|
1214
|
-
{ id: 'R-03', name: 'ASIL isolation', severity: 'error', evaluate: asilIsolation, domain: ['MOD'] },
|
|
1215
1447
|
{ id: 'R-04', name: 'Module size relative to crossing flows', severity: 'warning', evaluate: maxModuleSize, domain: ['MOD'] },
|
|
1216
1448
|
{ id: 'R-05', name: 'TEST must verify REQ', severity: 'warning', evaluate: testMustVerifyReq, domain: ['TEST'] },
|
|
1217
|
-
{ id: 'R-14', name: 'UC must have compose', severity: 'warning', evaluate: ucMustHaveCompose, domain: ['UC'] },
|
|
1218
1449
|
{ id: 'R-15', name: 'FCHAIN completeness', severity: 'warning', evaluate: fchainCompleteness, domain: ['FCHAIN'] },
|
|
1219
1450
|
{ id: 'R-16', name: 'ACTOR must have io', severity: 'warning', evaluate: actorMustHaveTrace, domain: ['ACTOR'] },
|
|
1220
1451
|
{ id: 'R-17', name: 'SYS must have compose', severity: 'warning', evaluate: sysMustHaveCompose, domain: ['SYS'] },
|
|
@@ -1232,7 +1463,6 @@ export const V3_RULES = [
|
|
|
1232
1463
|
{ id: 'R-22', name: 'FUNC must be allocated to MOD', severity: 'warning', evaluate: funcMustBeAllocated, domain: ['FUNC'] },
|
|
1233
1464
|
{ id: 'R-23', name: 'MOD must have allocated FUNC', severity: 'warning', evaluate: modMustHaveAllocatedFunc, domain: ['MOD'] },
|
|
1234
1465
|
{ id: 'R-26', name: 'SCHEMA must have realRef', severity: 'warning', evaluate: schemaMustHaveSchemaRef, domain: ['SCHEMA'] },
|
|
1235
|
-
{ id: 'R-27', name: 'Physical MOD must have realRef', severity: 'warning', evaluate: physicalModMustHaveRealRef, domain: ['MOD'] },
|
|
1236
1466
|
{ id: 'RD-01', name: 'Unresolved requirement', severity: 'warning', evaluate: unresolvedRequirement, domain: ['REQ'] },
|
|
1237
1467
|
{ id: 'RD-02', name: 'Decomposition consistency', severity: 'warning', evaluate: decompositionConsistency, domain: ['REQ'] },
|
|
1238
1468
|
{ id: 'RD-03', name: 'No premature decomposition', severity: 'info', evaluate: noPrematureDecomposition, domain: ['REQ'] },
|