@sigloch/contracts 10.14.0 → 12.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.
Files changed (40) hide show
  1. package/dist/se/analysis-freshness-rules.d.ts +37 -30
  2. package/dist/se/analysis-freshness-rules.js +41 -81
  3. package/dist/se/ao-rules.js +3 -3
  4. package/dist/se/chain-metrics.d.ts +116 -0
  5. package/dist/se/chain-metrics.js +311 -0
  6. package/dist/se/conformance-rules.d.ts +4 -0
  7. package/dist/se/conformance-rules.js +37 -32
  8. package/dist/se/cr-quality-rules.js +4 -76
  9. package/dist/se/evaluate-all.d.ts +7 -3
  10. package/dist/se/evaluate-all.js +3 -4
  11. package/dist/se/fchain-quality-rules.js +4 -4
  12. package/dist/se/fmea-rules.js +4 -4
  13. package/dist/se/format-e-parser.js +1 -1
  14. package/dist/se/grammar-snapshot.d.ts +6 -6
  15. package/dist/se/grammar-snapshot.js +90 -103
  16. package/dist/se/index.d.ts +4 -2
  17. package/dist/se/index.js +6 -2
  18. package/dist/se/metric-rules.d.ts +9 -5
  19. package/dist/se/metric-rules.js +10 -9
  20. package/dist/se/module-crossings.d.ts +10 -0
  21. package/dist/se/module-crossings.js +3 -1
  22. package/dist/se/near-duplicate-rules.d.ts +2 -0
  23. package/dist/se/near-duplicate-rules.js +2 -2
  24. package/dist/se/ontology.d.ts +23 -7
  25. package/dist/se/ontology.js +29 -26
  26. package/dist/se/quality-rules.js +5 -5
  27. package/dist/se/readiness.d.ts +41 -149
  28. package/dist/se/readiness.js +50 -280
  29. package/dist/se/rule-due.d.ts +26 -0
  30. package/dist/se/rule-due.js +72 -0
  31. package/dist/se/rule-help.d.ts +3 -1
  32. package/dist/se/rule-help.js +47 -31
  33. package/dist/se/rules.d.ts +23 -13
  34. package/dist/se/rules.js +112 -72
  35. package/dist/se/schema-quality-rules.d.ts +1 -1
  36. package/dist/se/schema-quality-rules.js +1 -1
  37. package/dist/se/similarity.js +4 -10
  38. package/dist/se/uc-quality-rules.js +4 -4
  39. package/dist/se/view-rules.js +6 -4
  40. package/package.json +1 -1
package/dist/se/rules.js CHANGED
@@ -7,6 +7,7 @@ import { z } from 'zod/v4';
7
7
  import { ElementType, TraceType, readTestRefs, readRealRef } from './ontology.js';
8
8
  import { traceRejection, admittingAlternatives, isValidTrace, BOUNDED_PATTERNS, REQUIRED_PATTERNS, maxOccurs } from './meta-model.js';
9
9
  import { indexOf } from './graph-index.js';
10
+ import { whenDue } from './rule-due.js';
10
11
  import { moduleCrossings, subtreeFuncs } from './module-crossings.js';
11
12
  import { functionCriticality, chainsByFunc } from './function-criticality.js';
12
13
  export const RuleSeverity = z.enum(['error', 'warning', 'info']);
@@ -200,6 +201,10 @@ elements, ref) {
200
201
  }
201
202
  // ---------------------------------------------------------------------------
202
203
  // R-01: Every REQ must have at least one verify trace
204
+ //
205
+ // CR-SM-393: wieder genau das. CR-SM-391 hatte einen zweiten Befund angehaengt (Nicht-Konzept-REQ
206
+ // braucht einen Nicht-Konzept-TEST); mit dem Attribut `concept` ist sein Gegenstand entfallen.
207
+ // Ob der verifizierende TEST an eine Datei gebunden ist, fragt R-19 — ab Beginn der Realisierung.
203
208
  // ---------------------------------------------------------------------------
204
209
  function reqMustHaveVerification(graph) {
205
210
  const idx = indexOf(graph);
@@ -845,19 +850,24 @@ function actorMustHaveTrace(graph) {
845
850
  }));
846
851
  }
847
852
  // ---------------------------------------------------------------------------
848
- // R-17: SYS must have at least 1 compose trace (CR-117)
853
+ // R-17: SYS must have at least one use case (CR-117; neu gefasst CR-SM-396).
854
+ //
855
+ // Die Existenz-Regel der Stufe 2. Bis CR-SM-396 schwieg sie, sobald das SYS IRGENDETWAS komponierte
856
+ // (UC, REQ oder MOD) — ein Code-Import (SYS -compose-> MOD, kein UC) las damit SRR als erreicht,
857
+ // „0 von 0" an der Stufe Anwendungsfall. Verlangt ist der Anwendungsfall selbst: `SYS -compose-> UC`
858
+ // (im Meta-Modell `1..*`). Ein UC, der nicht am System haengt, genuegt nicht.
849
859
  // ---------------------------------------------------------------------------
850
- function sysMustHaveCompose(graph) {
860
+ function sysMustHaveUseCase(graph) {
851
861
  const idx = indexOf(graph);
852
862
  const systems = idx.elementsOfType('SYS');
853
863
  return systems
854
- .filter(sys => idx.out(sys.id, 'compose').length === 0)
864
+ .filter(sys => !idx.out(sys.id, 'compose').some(t => idx.typeOf(t.target) === 'UC'))
855
865
  .map(sys => ({
856
866
  rule_id: 'R-17',
857
867
  severity: 'warning',
858
868
  element_id: sys.id,
859
- message: `${sys.id} has no compose traces (empty system)`,
860
- fix_hint: 'Add at least one compose trace — the system decomposes into nothing',
869
+ message: `${sys.id} has no use case (no SYS -compose-> UC)`,
870
+ fix_hint: `Add at least one UC and a compose trace from ${sys.id} to it — a use case says who uses the system for what`,
861
871
  context: {
862
872
  element_type: sys.type,
863
873
  element_name: sys.name,
@@ -1132,19 +1142,18 @@ function cardinalityViolations(graph) {
1132
1142
  return violations;
1133
1143
  }
1134
1144
  // ---------------------------------------------------------------------------
1135
- // R-19: Runnable TEST binding (CR-GC-205 Item 4) — a TEST that is not explicitly
1136
- // concept-only (attributes.concept === true) should carry a valid testRefs runnable
1137
- // binding so the deduced selective test set is trustworthy. WARNING (a completeness
1138
- // signal like R-05, not a hard gate): a freshly-spec'd TEST is legitimately concept-
1139
- // level until implemented, and the no-phantom-file guarantee is enforced separately
1140
- // by the consumer's export materialization (every testRefs entry file is scaffolded). File
1141
- // EXISTENCE is out of scope here (rules are pure, no I/O); this guards presence/shape
1142
- // only and surfaces an unbound runnable TEST in rules_evaluate / readiness.
1145
+ // R-19: Runnable TEST binding (CR-GC-205 Item 4) — a TEST should carry a valid testRefs
1146
+ // runnable binding so the deduced selective test set is trustworthy. WARNING (a completeness
1147
+ // signal like R-05, not a hard gate). File EXISTENCE is out of scope here (rules are pure, no
1148
+ // I/O — that is RC-02); this guards presence/shape only.
1149
+ //
1150
+ // CR-SM-393: keine Ausnahme je Element mehr (`concept` ist entfallen). „Noch nicht faellig" ist
1151
+ // eine Eigenschaft des Graphen: die Regel ist Stufe 11 und erst faellig, wenn der Bau eroeffnet
1152
+ // ist (`isDue`, CR-SM-395).
1143
1153
  // ---------------------------------------------------------------------------
1144
1154
  function testMustHaveRunnableBinding(graph) {
1145
1155
  const idx = indexOf(graph);
1146
1156
  return idx.elementsOfType('TEST')
1147
- .filter(e => e.attributes?.concept !== true) // concept-only TESTs are exempt
1148
1157
  // CR-SM-231: `testRefs` (Array, min 1) statt `testRef` (Objekt). Semantik unveraendert —
1149
1158
  // Praesenz und Form, keine Datei-Existenz; die pruefst RC-02.
1150
1159
  .filter(e => readTestRefs(e.attributes).state !== 'bound')
@@ -1153,7 +1162,7 @@ function testMustHaveRunnableBinding(graph) {
1153
1162
  severity: 'warning',
1154
1163
  element_id: test.id,
1155
1164
  message: `${test.id} is a runnable TEST without a valid testRefs binding`,
1156
- fix_hint: 'Add attributes.testRefs [{file, case?, tool, level?}, …] with at least one entry, or set attributes.concept:true if it has no run artifact yet',
1165
+ fix_hint: 'Add attributes.testRefs [{file, case?, tool, level?}, …] with at least one entry',
1157
1166
  context: { element_type: test.type, element_name: test.name },
1158
1167
  }));
1159
1168
  }
@@ -1212,8 +1221,8 @@ function testFileExclusivity(graph) {
1212
1221
  }
1213
1222
  // ---------------------------------------------------------------------------
1214
1223
  // R-20: FUNC realRef binding (CR-GC-205 Item 5, extended by CR-210, unified CR-228)
1215
- // — a FUNC that is not explicitly concept-only (attributes.concept === true) or
1216
- // externally realized (attributes.external === true) counts as BOUND when it EITHER
1224
+ // — a FUNC that is not externally realized (attributes.external === true) counts as
1225
+ // BOUND when it EITHER
1217
1226
  // carries a valid realRef {file, symbol?} OR is a blackbox parent whose compose→FUNC
1218
1227
  // children are ALL (recursively) bound — the parent is realized by its children
1219
1228
  // (e.g. FUNC-gesture-capture, gve CR-GVE-150). A partially-bound parent fires
@@ -1230,7 +1239,7 @@ function funcMustHaveCodeBinding(graph) {
1230
1239
  .filter(t => t.source === id && t.type === 'compose' && funcById.has(t.target))
1231
1240
  .map(t => t.target);
1232
1241
  const hasCodeRef = (el) => readRealRef(el.attributes).state === 'bound';
1233
- const isExempt = (el) => el.attributes?.concept === true || el.attributes?.external === true;
1242
+ const isExempt = (el) => el.attributes?.external === true;
1234
1243
  // A FUNC is bound iff it is exempt, has a codeRef, or is a parent whose
1235
1244
  // compose→FUNC children are all bound (recursive, cycle-safe).
1236
1245
  const isBound = (id, stack) => {
@@ -1294,7 +1303,7 @@ function funcMustHaveCodeBinding(graph) {
1294
1303
  severity: 'warning',
1295
1304
  element_id: fn.id,
1296
1305
  message: `${fn.id} is a FUNC without a valid realRef binding`,
1297
- fix_hint: 'Add attributes.realRef {file, symbol, lang?}, or set attributes.concept:true (spec-only) / attributes.external:true (realized in another package)',
1306
+ fix_hint: 'Add attributes.realRef {file, symbol, lang?}, or set attributes.external:true if it is realized in another package',
1298
1307
  context: { element_type: fn.type, element_name: fn.name },
1299
1308
  });
1300
1309
  }
@@ -1496,7 +1505,7 @@ function modMustHaveAllocatedFunc(graph) {
1496
1505
  // REQ→MOD allocate edge as an invalid trace pattern.
1497
1506
  // ---------------------------------------------------------------------------
1498
1507
  // R-26: SCHEMA realRef presence (CR-211, unified CR-228) — a SCHEMA that is not
1499
- // explicitly concept-only or external should carry a valid realRef {file, symbol?}
1508
+ // external should carry a valid realRef {file, symbol?}
1500
1509
  // so its Zod definition is machine-resolvable (RC-03 then checks it resolves, RC-04
1501
1510
  // that it is parsed at the interface). WARNING, not error: currently-unbound SCHEMAs
1502
1511
  // on the reference model must not turn readiness red before any binding exists — the
@@ -1506,54 +1515,45 @@ function modMustHaveAllocatedFunc(graph) {
1506
1515
  function schemaMustHaveSchemaRef(graph) {
1507
1516
  const idx = indexOf(graph);
1508
1517
  return idx.elementsOfType('SCHEMA')
1509
- .filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
1518
+ .filter(e => e.attributes?.external !== true)
1510
1519
  .filter(e => readRealRef(e.attributes).state !== 'bound')
1511
1520
  .map(sc => ({
1512
1521
  rule_id: 'R-26',
1513
1522
  severity: 'warning',
1514
1523
  element_id: sc.id,
1515
1524
  message: `${sc.id} is a SCHEMA without a valid realRef binding`,
1516
- fix_hint: 'Add attributes.realRef {file, symbol, lang?}, or set attributes.concept:true (spec-only) / attributes.external:true (foreign-API contract)',
1525
+ fix_hint: 'Add attributes.realRef {file, symbol, lang?}, or set attributes.external:true if it is a foreign-API contract',
1517
1526
  context: { element_type: sc.type, element_name: sc.name },
1518
1527
  }));
1519
1528
  }
1520
1529
  // ---------------------------------------------------------------------------
1521
- // R-32: Every realized SCHEMA must have a contract TEST (CR-SM-319, ITEM-2026-064).
1522
- // A SCHEMA with a symbol-bearing realRef is a Zod contract in code. RC-04 checks that it is
1523
- // PARSED at its boundary; this rule checks that it is TESTED as a contract — every variant
1524
- // incl. rejection, independent of any scenario — via `TEST -verify-> SCHEMA` (CR-SM-317).
1525
- // Chain tests (R-21) cover meaning end-to-end; they do not cover the contract's variants.
1526
- // Owner's decision 2026-09-11: no exception, no KPI — a rule. concept-only SCHEMAs and
1527
- // SCHEMAs without a symbol realRef are out of scope (presence is R-26's concern).
1530
+ // R-32: Every SCHEMA must have a contract TEST (CR-SM-319, ITEM-2026-064; alle Schemas CR-SM-396).
1531
+ // A SCHEMA is a contract. RC-04 checks that it is PARSED at its boundary; this rule checks that it
1532
+ // is TESTED as a contract — every variant incl. rejection, independent of any scenario — via
1533
+ // `TEST -verify-> SCHEMA` (CR-SM-317). Chain tests (R-21) cover meaning end-to-end; they do not
1534
+ // cover the contract's variants. Owner's decision 2026-09-11: no exception, no KPI — a rule.
1535
+ //
1536
+ // CR-SM-396 (Entscheidung des Autors 2026-10-06): die Regel gilt fuer JEDES Schema. Bis hierher
1537
+ // pruefte sie nur Schemas mit Symbol-`realRef` — als Existenz-Regel der Stufe 9 brauchte sie damit
1538
+ // die Bindung (Stufe 11), und im Entwurf verlangte keine Regel den Vertragstest: TRR las erreicht,
1539
+ // ohne dass ein Schema getestet war. Der Test ist Teil der Spezifikation, nicht des Baus.
1528
1540
  // ---------------------------------------------------------------------------
1529
1541
  function schemaMustHaveContractTest(graph) {
1530
1542
  const idx = indexOf(graph);
1531
1543
  const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
1532
1544
  const tests = idx.elementsOfType('TEST');
1533
1545
  return idx.elementsOfType('SCHEMA')
1534
- .filter(sc => sc.attributes?.concept !== true)
1535
- .filter(sc => { const r = readRealRef(sc.attributes); return r.state === 'bound' && r.value.symbol !== undefined; })
1536
1546
  .filter(sc => !graph.traces.some(t => t.type === 'verify' && t.target === sc.id && typeOf.get(t.source) === 'TEST'))
1537
1547
  .map(sc => ({
1538
1548
  rule_id: 'R-32',
1539
1549
  severity: 'warning',
1540
1550
  element_id: sc.id,
1541
- message: `${sc.id} is a realized SCHEMA without a contract TEST (no TEST -verify-> SCHEMA)`,
1551
+ message: `${sc.id} is a SCHEMA without a contract TEST (no TEST -verify-> SCHEMA)`,
1542
1552
  fix_hint: 'Add a TEST that exercises every variant of the contract incl. rejection, and link it via verify trace to this SCHEMA',
1543
1553
  context: { element_type: sc.type, element_name: sc.name, candidate_targets: toCandidates(tests) },
1544
1554
  }));
1545
1555
  }
1546
1556
  // ---------------------------------------------------------------------------
1547
- // R-27: physical MOD realRef presence (CR-228) — the MOD arm of the unified
1548
- // "element must have a realRef" rule (FUNC=R-20, SCHEMA=R-26, physical MOD=R-27).
1549
- // A physical MOD (kind='physical') is a Bauteil realized by a CAD/geometry
1550
- // artefact, not by code (RT-01: FUNCs never allocate directly to it). Logical/SW
1551
- // MODs are realized through their FUNCs' code (R-20) and are NOT in scope here.
1552
- // WARNING (presence signal like R-20/R-26); concept/external MODs are exempt.
1553
- // Symbol is optional on a realRef (a geometry file has none) — presence = a valid
1554
- // realRef with a file. RESOLUTION (file on disk) is a consumer/RC concern.
1555
- // ---------------------------------------------------------------------------
1556
- // ---------------------------------------------------------------------------
1557
1557
  // R-28 (Ebenen-Präsenz) ist mit CR-SM-247 ersatzlos entfallen — nicht weil sie falsch war,
1558
1558
  // sondern weil sie seit CR-GC-366 doppelt ist. Sie schloss das Vacuous-Complete-Loch (mit 0/1
1559
1559
  // FUNC feuert keine Pro-Element-Regel, alles liest "fertig"); beide Hälften trägt heute je eine
@@ -1663,42 +1663,82 @@ function funcMustBeWired(graph) {
1663
1663
  },
1664
1664
  }));
1665
1665
  }
1666
- export const V3_RULES = [
1667
- { id: 'R-01', name: 'REQ must have verification', severity: 'error', evaluate: reqMustHaveVerification, domain: ['REQ'] },
1668
- { id: 'R-02', name: 'FUNC must satisfy REQ', severity: 'warning', evaluate: funcMustSatisfyReq, domain: ['FUNC'] },
1666
+ // ---------------------------------------------------------------------------
1667
+ // R-33 (CR-SM-395): die Existenz-Regel des SYSTEMS (Stufe 1).
1668
+ //
1669
+ // Jede Pflichtmenge wird von einer Existenz-Regel der Stufe davor verlangt; eine leere Pflichtmenge
1670
+ // ist deshalb immer ein Befund und nie „0 von 0 bestanden". „Kein System" stand als Code im
1671
+ // Kaltstart von graphcode (`graph_generate`), nicht im Katalog. Ein Graph ohne SYS hat kein Element,
1672
+ // an dem der Befund haengen koennte — er traegt den Anker `GRAPH_ANCHOR`.
1673
+ //
1674
+ // R-34 („FLOW hat ein SCHEMA", Stufe 8) ist mit CR-SM-396 wieder gestrichen: deckungsgleich mit der
1675
+ // R-18-Untergrenze `FLOW -relation-> SCHEMA [1]` — alle 20 Befunde des Bestands standen an FLOWs
1676
+ // mit R-18-Fehler, R-34 konnte nie allein melden. Die Stufe 8 traegt keine eigene Existenz-Regel:
1677
+ // R-18 ist `'immer'` und haelt als Fehler jede Marke, das Gate lehnt den untypisierten FLOW schon
1678
+ // beim Schreiben ab; null Datenfluesse haelt R-31 (Stufe 6).
1679
+ // ---------------------------------------------------------------------------
1680
+ /** Der Anker eines Befunds an einem Graphen ohne SYS — kein Element traegt diese Id. */
1681
+ export const GRAPH_ANCHOR = 'graph';
1682
+ function graphMustHaveSystem(graph) {
1683
+ if (indexOf(graph).elementsOfType('SYS').length > 0)
1684
+ return [];
1685
+ return [{
1686
+ rule_id: 'R-33',
1687
+ severity: 'warning',
1688
+ element_id: GRAPH_ANCHOR,
1689
+ message: 'The graph has no SYS element — nothing names the system and its boundary',
1690
+ fix_hint: 'Add one SYS element that names the system; use cases, requirements and modules are composed below it',
1691
+ }];
1692
+ }
1693
+ /**
1694
+ * CR-SM-395: das UNGEBAUTE — die Elemente, an denen die Praesenzregeln der Bindung melden wuerden
1695
+ * (R-19 TEST, R-20 FUNC, R-26 SCHEMA), unabhaengig davon, ob sie schon faellig sind. EINE
1696
+ * Definition samt Ausnahmen (`external`, Zerlegungs-Eltern mit gebundenen Kindern): AF-05 fragt
1697
+ * damit „gibt es etwas zu bauen?", ohne die Ausnahmen ein zweites Mal zu schreiben.
1698
+ */
1699
+ export function unbuiltElementIds(graph) {
1700
+ return [...testMustHaveRunnableBinding(graph), ...funcMustHaveCodeBinding(graph), ...schemaMustHaveSchemaRef(graph)]
1701
+ .map(v => v.element_id);
1702
+ }
1703
+ // CR-SM-395: die Faelligkeit haengt am KATALOG (`whenDue`), nicht am Auswerter und nicht im
1704
+ // Regelkoerper — die Regeln der Stufe 11 (R-19, R-20, R-26, R-29) schweigen, bis der Bau eroeffnet ist.
1705
+ export const V3_RULES = whenDue([
1706
+ { id: 'R-33', name: 'Graph must have a SYS', severity: 'warning', stage: 1, role: 'existence', evaluate: graphMustHaveSystem, domain: ['graph'] },
1707
+ { id: 'R-01', name: 'REQ must have verification', severity: 'error', stage: 9, role: 'existence', evaluate: reqMustHaveVerification, domain: ['REQ'] },
1708
+ { id: 'R-02', name: 'FUNC must satisfy REQ', severity: 'warning', stage: 5, evaluate: funcMustSatisfyReq, domain: ['FUNC'] },
1669
1709
  // CR-SM-243: domain ist MOD, nicht FUNC — die Regel iteriert Module und meldet am Modul,
1670
1710
  // das ASIL-D und QM mischt. Die FUNCs sind der Anlass des Urteils, nicht seine Traeger.
1671
- { id: 'R-04', name: 'Module boundary width', severity: 'warning', evaluate: moduleBoundaryWidth, domain: ['MOD'] },
1672
- { id: 'R-05', name: 'TEST must verify REQ', severity: 'warning', evaluate: testMustVerifyReq, domain: ['TEST'] },
1673
- { id: 'R-15', name: 'FCHAIN completeness', severity: 'warning', evaluate: fchainCompleteness, domain: ['FCHAIN'] },
1674
- { id: 'R-16', name: 'ACTOR must have io', severity: 'warning', evaluate: actorMustHaveTrace, domain: ['ACTOR'] },
1675
- { id: 'R-17', name: 'SYS must have compose', severity: 'warning', evaluate: sysMustHaveCompose, domain: ['SYS'] },
1676
- { id: 'R-08', name: 'Trace consistency', severity: 'error', evaluate: traceConsistency, domain: ['all'] },
1711
+ { id: 'R-04', name: 'Module boundary width', severity: 'warning', stage: 7, evaluate: moduleBoundaryWidth, domain: ['MOD'] },
1712
+ { id: 'R-05', name: 'TEST must verify REQ', severity: 'warning', stage: 9, evaluate: testMustVerifyReq, domain: ['TEST'] },
1713
+ { id: 'R-15', name: 'FCHAIN completeness', severity: 'warning', stage: 5, role: 'existence', evaluate: fchainCompleteness, domain: ['FCHAIN'] },
1714
+ { id: 'R-16', name: 'ACTOR must have io', severity: 'warning', stage: 2, evaluate: actorMustHaveTrace, domain: ['ACTOR'] },
1715
+ { id: 'R-17', name: 'SYS must have a use case', severity: 'warning', stage: 2, role: 'existence', evaluate: sysMustHaveUseCase, domain: ['SYS'] },
1716
+ { id: 'R-08', name: 'Trace consistency', severity: 'error', stage: 'immer', evaluate: traceConsistency, domain: ['all'] },
1677
1717
  // CR-SM-242: domain ist FLOW, nicht FUNC — die Regel iteriert ueber FLOWs und meldet am FLOW.
1678
1718
  // Als ['FUNC'] deklariert erhoehte sie den Zaehler einer Grundgesamtheit, zu der ihre
1679
1719
  // Verstoesse nicht gehoerten (bis zu 2 Legs je FLOW gegen einen FUNC-Nenner).
1680
- { id: 'R-10', name: 'FLOW completeness', severity: 'warning', evaluate: flowCompleteness, domain: ['FLOW'] },
1720
+ { id: 'R-10', name: 'FLOW completeness', severity: 'warning', stage: 6, evaluate: flowCompleteness, domain: ['FLOW'] },
1681
1721
  // Obergrenze zu R-10s Untergrenze — disjunkt, deshalb zwei IDs (CR-SM-307).
1682
- { id: 'IO-02', name: 'FLOW single producer', severity: 'error', evaluate: flowSingleProducer, domain: ['FLOW'] },
1683
- { id: 'R-12', name: 'No circular dependencies', severity: 'warning', evaluate: noDirectCircular, domain: ['FUNC'] },
1684
- { id: 'R-18', name: 'Valid trace pattern', severity: 'error', evaluate: validTracePattern, domain: ['all'] },
1685
- { id: 'R-19', name: 'Runnable TEST binding', severity: 'warning', evaluate: testMustHaveRunnableBinding, domain: ['TEST'] },
1686
- { id: 'R-29', name: 'Test file exclusivity', severity: 'error', evaluate: testFileExclusivity, domain: ['TEST'] },
1687
- { id: 'R-20', name: 'FUNC realRef binding', severity: 'warning', evaluate: funcMustHaveCodeBinding, domain: ['FUNC'] },
1688
- { id: 'R-21', name: 'FUNC↔FUNC handover needs a shared chain covered by member REQs or an integration test', severity: 'warning', evaluate: fchainMustHaveIntegrationTest, domain: ['FCHAIN', 'FUNC'] },
1689
- { id: 'R-22', name: 'FUNC must be allocated to MOD', severity: 'warning', evaluate: funcMustBeAllocated, domain: ['FUNC'] },
1690
- { id: 'R-23', name: 'MOD must have allocated FUNC', severity: 'warning', evaluate: modMustHaveAllocatedFunc, domain: ['MOD'] },
1691
- { id: 'R-26', name: 'SCHEMA must have realRef', severity: 'warning', evaluate: schemaMustHaveSchemaRef, domain: ['SCHEMA'] },
1692
- { id: 'R-32', name: 'SCHEMA must have contract TEST', severity: 'warning', evaluate: schemaMustHaveContractTest, domain: ['SCHEMA'] },
1693
- { id: 'RD-01', name: 'Unresolved requirement', severity: 'warning', evaluate: unresolvedRequirement, domain: ['REQ'] },
1694
- { id: 'RD-02', name: 'Decomposition consistency', severity: 'warning', evaluate: decompositionConsistency, domain: ['REQ'] },
1695
- { id: 'RD-04', name: 'Decomposition breadth', severity: 'warning', evaluate: decompositionBreadth, domain: ['FUNC', 'MOD', 'SYS'] },
1696
- { id: 'RD-05', name: 'Decomposition too narrow', severity: 'warning', evaluate: decompositionNarrow, domain: ['FUNC', 'MOD'] },
1697
- { id: 'MS-01', name: 'Milestone empty scope', severity: 'warning', evaluate: msEmptyScope, domain: ['MS'] },
1698
- { id: 'MS-02', name: 'Milestone dangling dependency', severity: 'warning', evaluate: msDanglingDependency, domain: ['MS'] },
1699
- { id: 'R-30', name: 'FUNC leaf must belong to a function chain', severity: 'warning', evaluate: funcMustBeInEffectChain, domain: ['FUNC'] },
1700
- { id: 'R-31', name: 'FUNC must be wired (io input + output)', severity: 'warning', evaluate: funcMustBeWired, domain: ['FUNC'] },
1701
- ];
1722
+ { id: 'IO-02', name: 'FLOW single producer', severity: 'error', stage: 6, evaluate: flowSingleProducer, domain: ['FLOW'] },
1723
+ { id: 'R-12', name: 'No circular dependencies', severity: 'warning', stage: 5, evaluate: noDirectCircular, domain: ['FUNC'] },
1724
+ { id: 'R-18', name: 'Valid trace pattern', severity: 'error', stage: 'immer', evaluate: validTracePattern, domain: ['all'] },
1725
+ { id: 'R-19', name: 'Runnable TEST binding', severity: 'warning', stage: 11, evaluate: testMustHaveRunnableBinding, domain: ['TEST'] },
1726
+ { id: 'R-29', name: 'Test file exclusivity', severity: 'error', stage: 11, evaluate: testFileExclusivity, domain: ['TEST'] },
1727
+ { id: 'R-20', name: 'FUNC realRef binding', severity: 'warning', stage: 11, evaluate: funcMustHaveCodeBinding, domain: ['FUNC'] },
1728
+ { id: 'R-21', name: 'FUNC↔FUNC handover needs a shared chain covered by member REQs or an integration test', severity: 'warning', stage: 9, evaluate: fchainMustHaveIntegrationTest, domain: ['FCHAIN', 'FUNC'] },
1729
+ { id: 'R-22', name: 'FUNC must be allocated to MOD', severity: 'warning', stage: 7, role: 'existence', evaluate: funcMustBeAllocated, domain: ['FUNC'] },
1730
+ { id: 'R-23', name: 'MOD must have allocated FUNC', severity: 'warning', stage: 7, evaluate: modMustHaveAllocatedFunc, domain: ['MOD'] },
1731
+ { id: 'R-26', name: 'SCHEMA must have realRef', severity: 'warning', stage: 11, evaluate: schemaMustHaveSchemaRef, domain: ['SCHEMA'] },
1732
+ { id: 'R-32', name: 'SCHEMA must have contract TEST', severity: 'warning', stage: 9, role: 'existence', evaluate: schemaMustHaveContractTest, domain: ['SCHEMA'] },
1733
+ { id: 'RD-01', name: 'Unresolved requirement', severity: 'warning', stage: 3, evaluate: unresolvedRequirement, domain: ['REQ'] },
1734
+ { id: 'RD-02', name: 'Decomposition consistency', severity: 'warning', stage: 3, evaluate: decompositionConsistency, domain: ['REQ'] },
1735
+ { id: 'RD-04', name: 'Decomposition breadth', severity: 'warning', stage: 7, evaluate: decompositionBreadth, domain: ['FUNC', 'MOD', 'SYS'] },
1736
+ { id: 'RD-05', name: 'Decomposition too narrow', severity: 'warning', stage: 7, evaluate: decompositionNarrow, domain: ['FUNC', 'MOD'] },
1737
+ { id: 'MS-01', name: 'Milestone empty scope', severity: 'warning', stage: 10, evaluate: msEmptyScope, domain: ['MS'] },
1738
+ { id: 'MS-02', name: 'Milestone dangling dependency', severity: 'warning', stage: 10, evaluate: msDanglingDependency, domain: ['MS'] },
1739
+ { id: 'R-30', name: 'FUNC leaf must belong to a function chain', severity: 'warning', stage: 5, evaluate: funcMustBeInEffectChain, domain: ['FUNC'] },
1740
+ { id: 'R-31', name: 'FUNC must be wired (io input + output)', severity: 'warning', stage: 6, role: 'existence', evaluate: funcMustBeWired, domain: ['FUNC'] },
1741
+ ]);
1702
1742
  /** Run all rules against a graph. CR-SM-236: `policy` ist Pflicht — wie bei `evaluateAllRules`. */
1703
1743
  export function evaluateRules(graph, policy) {
1704
1744
  return V3_RULES.flatMap(rule => rule.evaluate(graph, policy));
@@ -6,7 +6,7 @@
6
6
  * attributes and fired on exactly the same elements (Jaccard 1.0 vs R-26). SC-03 had
7
7
  * additionally become wrong — a SCHEMA correctly bound via `realRef` still fired it.
8
8
  * `realRef` is the single SCHEMA binding truth (R-26 presence, RC-03/RC-04 resolution);
9
- * a spec-only SCHEMA is `concept:true`, it does NOT carry a Zod body copy in the graph
9
+ * a spec-only SCHEMA simply has none yet, it does NOT carry a Zod body copy in the graph
10
10
  * (same rule as TEST: bindings point at code, they never mirror it).
11
11
  *
12
12
  * What remains here is the one SCHEMA check that is NOT about binding: is the interface
@@ -32,7 +32,7 @@ export const SC_RULES = [
32
32
  // Die Asymmetrie zur FLOW-Seite ist gewollt (CR-SM-242): SC-02 iteriert ueber SCHEMA
33
33
  // („wird dieses SCHEMA benutzt"); die Gegenrichtung je FLOW („hat dieser FLOW einen
34
34
  // Datenvertrag") traegt seit CR-SM-271 das R-18-Untergrenzen-Bein.
35
- { id: 'SC-02', name: 'Schema referenced by FLOW', severity: 'warning', evaluate: sc02IsReferenced, domain: ['SCHEMA'] },
35
+ { id: 'SC-02', name: 'Schema referenced by FLOW', severity: 'warning', stage: 8, evaluate: sc02IsReferenced, domain: ['SCHEMA'] },
36
36
  ];
37
37
  // CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
38
38
  // ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
@@ -52,14 +52,6 @@ function firstWord(name) {
52
52
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).find(w => w.length > 0) ?? '';
53
53
  }
54
54
  const byId = (a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
55
- /** Feld-Menge eines SCHEMA: `attributes.fields` (Array), sonst Beschreibungs-Token. */
56
- function schemaFields(el) {
57
- const fields = el.attributes?.fields;
58
- if (Array.isArray(fields) && fields.length > 0) {
59
- return new Set(fields.map(f => String(typeof f === 'object' && f !== null ? (f.name ?? JSON.stringify(f)) : f).toLowerCase()));
60
- }
61
- return tokens(el.description);
62
- }
63
55
  const CACHE = new WeakMap();
64
56
  function build(graph) {
65
57
  // ND-01: 0.35*descr_jaccard + 0.25*verb_match + 0.25*io_topology + 0.15*req_overlap
@@ -77,14 +69,16 @@ function build(graph) {
77
69
  + 0.15 * jaccard(fReq[i], fReq[j]))),
78
70
  };
79
71
  // ND-02: 0.50*field_jaccard + 0.30*descr_jaccard + 0.20*usage_overlap
72
+ // CR-SM-399: die Feldmenge eines SCHEMA IST seine Beschreibung — das Attribut `fields` (nie
73
+ // deklariert, 0 Traeger) wird nicht mehr gelesen. Beide Terme bleiben getrennt stehen: zu 0.8
74
+ // zusammengezogen wiche der Wert in der letzten Stelle ab, und kein Score soll sich bewegen.
80
75
  const schemas = graph.elements.filter(e => e.type === 'SCHEMA').sort(byId);
81
- const sFields = schemas.map(schemaFields);
82
76
  const sDescr = schemas.map(s => tokens(s.description));
83
77
  const sUse = schemas.map(s => partners(graph, s.id, ['relation', 'io', 'compose']));
84
78
  const schema = {
85
79
  ids: schemas.map(s => s.id),
86
80
  matrix: schemas.map((_, i) => schemas.map((_, j) => i === j ? 1
87
- : 0.5 * jaccard(sFields[i], sFields[j])
81
+ : 0.5 * jaccard(sDescr[i], sDescr[j])
88
82
  + 0.3 * jaccard(sDescr[i], sDescr[j])
89
83
  + 0.2 * jaccard(sUse[i], sUse[j]))),
90
84
  };
@@ -117,10 +117,10 @@ export function uc04GoalDefined(graph) {
117
117
  // CR-SM-357: UC-05/UC-06 (Pre-/Postcondition-REQ) sind gestrichen — Schreibregel in se:author-uc,
118
118
  // wie BQ in author-req (CR-GC-602): sie feuerten an fast jedem UC, und niemand handelte darauf.
119
119
  export const UC_RULES = [
120
- { id: 'UC-01', name: 'UC has requirements', severity: 'warning', evaluate: uc01HasRequirements, domain: ['UC'] },
121
- { id: 'UC-02', name: 'UC has actor', severity: 'warning', evaluate: uc02HasActor, domain: ['UC'] },
122
- { id: 'UC-03', name: 'UC has scenario', severity: 'warning', evaluate: uc03HasScenario, domain: ['UC'] },
123
- { id: 'UC-04', name: 'UC has goal', severity: 'warning', evaluate: uc04GoalDefined, domain: ['UC'] },
120
+ { id: 'UC-01', name: 'UC has requirements', severity: 'warning', stage: 3, role: 'existence', evaluate: uc01HasRequirements, domain: ['UC'] },
121
+ { id: 'UC-02', name: 'UC has actor', severity: 'warning', stage: 2, role: 'existence', evaluate: uc02HasActor, domain: ['UC'] },
122
+ { id: 'UC-03', name: 'UC has scenario', severity: 'warning', stage: 4, role: 'existence', evaluate: uc03HasScenario, domain: ['UC'] },
123
+ { id: 'UC-04', name: 'UC has goal', severity: 'warning', stage: 2, evaluate: uc04GoalDefined, domain: ['UC'] },
124
124
  ];
125
125
  // CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
126
126
  // ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
@@ -4,6 +4,7 @@
4
4
  * CL-01: ACTOR without UCs in at least 2 distinct operatingModes (warning).
5
5
  */
6
6
  import { readTestRefs } from './ontology.js';
7
+ import { whenDue } from './rule-due.js';
7
8
  // ---------------------------------------------------------------------------
8
9
  // VR-01: ein testRefs-Eintrag ohne Ergebnis
9
10
  //
@@ -103,10 +104,11 @@ export function cl01ConopsCompleteness(graph) {
103
104
  // ---------------------------------------------------------------------------
104
105
  // Aggregated array & convenience runner
105
106
  // ---------------------------------------------------------------------------
106
- export const VIEW_RULES = [
107
- { id: 'VR-01', name: 'TestNoResult', severity: 'info', evaluate: vr01TestNoResult, domain: ['TEST'] },
108
- { id: 'CL-01', name: 'ConopsCompleteness', severity: 'warning', evaluate: cl01ConopsCompleteness, domain: ['ACTOR'] },
109
- ];
107
+ // CR-SM-395: VR-01 ist Stufe 11 — ein Testergebnis gibt es erst, wenn der Bau eroeffnet ist (`whenDue`).
108
+ export const VIEW_RULES = whenDue([
109
+ { id: 'VR-01', name: 'TestNoResult', severity: 'info', stage: 11, evaluate: vr01TestNoResult, domain: ['TEST'] },
110
+ { id: 'CL-01', name: 'ConopsCompleteness', severity: 'warning', stage: 2, evaluate: cl01ConopsCompleteness, domain: ['ACTOR'] },
111
+ ]);
110
112
  // CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
111
113
  // ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
112
114
  export function evaluateViewRules(graph, policy) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigloch/contracts",
3
- "version": "10.14.0",
3
+ "version": "12.0.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",