@sigloch/contracts 4.2.0 → 5.0.1

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.
@@ -24,7 +24,13 @@ export const ALL_RULE_DEFS = [
24
24
  ].map(r => ({ id: r.id, name: r.name, severity: r.severity, domain: r.domain }));
25
25
  // CR-SM-221: 'RD-' was missing — 'RD-01'.startsWith('R-') is false, so the
26
26
  // decomposition rules ran in `default` only and never in the `se` profile.
27
- const SE_PREFIXES = ['R-', 'RD-', 'UC-', 'FC-', 'SC-', 'MT-', 'CR-', 'AO-', 'FM-', 'NFR-', 'RT-', 'PH-', 'CA-', 'IO-', 'VR-', 'CL-', 'AF-'];
27
+ // CR-SM-245: 'MS-' fehlte hier UND in CODING_PREFIXES MS-01/02/03 fielen aus beiden Profilen
28
+ // und liefen nur im 'default'-Lauf. Die Dimension `ms` wird von genau vier Regeln getragen
29
+ // (MS-01/02/03 + AF-05); AF-05 ist eine `domain: ['graph']`-Regel und traegt per CR-SM-239
30
+ // keinen Nenner bei. Im se-Profil hatte `ms` damit keine einzige zaehlende Regel — ein leerer
31
+ // Topic-Score und drei fehlende SRR-Legs. Zweiter Fall derselben Klasse nach 'RD-' (CR-SM-221),
32
+ // jetzt vom Profil-Test in se-grammar-invariant.test.ts abgedeckt.
33
+ const SE_PREFIXES = ['R-', 'RD-', 'UC-', 'FC-', 'SC-', 'MT-', 'CR-', 'AO-', 'FM-', 'NFR-', 'RT-', 'PH-', 'CA-', 'IO-', 'VR-', 'CL-', 'AF-', 'MS-'];
28
34
  const CODING_PREFIXES = ['BQ-', 'ND-'];
29
35
  export function getRuleDefsForProfile(profile) {
30
36
  if (profile === 'se')
@@ -0,0 +1,23 @@
1
+ /**
2
+ * GENERIERT — nicht von Hand pflegen.
3
+ *
4
+ * Das Golden-File der SE-Grammatik (CR-SM-244). Sein Diff IST der Changelog der Oberflaeche:
5
+ * jede Zeile hier ist ein Vertrag, an dem ein Konsument der Familie haengt. Erzeugt von
6
+ * `npm run grammar:snapshot -w @sigloch/contracts`, geprueft von
7
+ * `tests/unit/se-grammar-invariant.test.ts`.
8
+ *
9
+ * Wer diese Datei aendert, ohne die zugehoerige Version in ./index.ts zu bumpen, hat den Zweck
10
+ * verfehlt — `npm run check:grammar` faellt darueber.
11
+ */
12
+ export declare const GRAMMAR_SNAPSHOT: {
13
+ readonly versions: {
14
+ readonly ontology: "7.0.0";
15
+ readonly metaModel: "1.4.0";
16
+ readonly rules: "4.0.0";
17
+ };
18
+ readonly elementTypes: readonly ["ACTOR", "CR", "FCHAIN", "FLOW", "FUNC", "MOD", "MS", "REQ", "SCHEMA", "SESSION", "SYS", "TEST", "UC"];
19
+ readonly traceTypes: readonly ["allocate", "compose", "io", "produces", "relation", "satisfy", "verify"];
20
+ readonly patterns: readonly ["ACTOR -io-> FLOW", "ACTOR -io-> UC", "CR -relation-> FUNC", "CR -relation-> MOD", "CR -relation-> MS", "CR -relation-> REQ", "CR -relation-> UC", "FCHAIN -compose-> FUNC [1..*]", "FCHAIN -satisfy-> REQ", "FLOW -io-> ACTOR", "FLOW -io-> FUNC", "FLOW -io-> UC", "FLOW -relation-> SCHEMA", "FUNC -allocate-> MOD", "FUNC -compose-> FUNC [0..*]", "FUNC -io-> FLOW", "FUNC -satisfy-> REQ", "MOD -compose-> MOD [0..*]", "MOD -io-> MOD", "MOD -satisfy-> REQ", "MS -compose-> FUNC", "MS -compose-> MS", "MS -compose-> REQ", "MS -compose-> UC", "MS -relation-> MS", "REQ -compose-> REQ [0..*]", "SESSION -produces-> * (audit)", "SYS -compose-> MOD [0..*]", "SYS -compose-> REQ [0..*]", "SYS -compose-> SYS [0..*]", "SYS -compose-> UC [1..*]", "SYS -satisfy-> REQ", "TEST -verify-> REQ", "UC -compose-> FCHAIN [1..*]", "UC -compose-> REQ [1..*]"];
21
+ readonly attributes: readonly ["CR.rationale: string", "CR.spike: boolean", "CR.status: enum", "FLOW.protocol: string", "FLOW.qos: string", "FUNC.concept: boolean", "FUNC.external: boolean", "FUNC.measuredMs: number", "FUNC.realRef: object", "FUNC.safety_relevant: boolean", "FUNC.sourceFile: string", "FUNC.timingBudgetMs: number", "MOD.concept: boolean", "MOD.external: boolean", "MOD.kind: string", "MOD.path: string", "MOD.realRef: object", "REQ.detection: number", "REQ.occurrence: number", "REQ.severity: number", "SCHEMA.concept: boolean", "SCHEMA.contract: string", "SCHEMA.external: boolean", "SCHEMA.realRef: object", "TEST.concept: boolean", "TEST.sourceFile: string", "TEST.testRefs: array", "UC.operatingMode: string"];
22
+ readonly rules: readonly ["AF-01 (warning) domain=[graph]", "AF-02 (warning) domain=[graph]", "AF-03 (warning) domain=[graph]", "AF-04 (warning) domain=[graph]", "AF-05 (warning) domain=[graph]", "AO-D01 (info) domain=[FUNC]", "AO-D03 (info) domain=[FUNC]", "BQ-01 (warning) domain=[REQ]", "BQ-02 (warning) domain=[REQ]", "BQ-04 (warning) domain=[REQ]", "BQ-06 (warning) domain=[REQ]", "BQ-07 (warning) domain=[REQ]", "CA-01 (error) domain=[FUNC]", "CL-01 (warning) domain=[ACTOR]", "CR-01 (warning) domain=[MOD]", "CR-R01 (error) domain=[CR]", "CR-R02 (error) domain=[CR]", "CR-R03 (warning) domain=[all]", "CR-R04 (warning) domain=[CR]", "FC-01 (warning) domain=[FCHAIN]", "FC-02 (warning) domain=[UC]", "FC-03 (warning) domain=[FUNC]", "FC-04 (warning) domain=[FCHAIN]", "FM-01 (warning) domain=[REQ]", "FM-02 (warning) domain=[REQ]", "FM-03 (error) domain=[REQ]", "IO-01 (warning) domain=[FUNC]", "MS-01 (warning) domain=[MS]", "MS-02 (error) domain=[MS]", "MS-03 (info) domain=[CR]", "MT-01 (warning) domain=[MOD]", "MT-02 (info) domain=[MOD]", "ND-01 (error) domain=[FUNC]", "ND-02 (error) domain=[SCHEMA]", "NFR-01 (warning) domain=[FCHAIN,FUNC,MOD]", "PH-01 (info) domain=[MOD]", "R-01 (error) domain=[REQ]", "R-02 (warning) domain=[FUNC]", "R-03 (error) domain=[MOD]", "R-04 (warning) domain=[MOD]", "R-05 (warning) domain=[TEST]", "R-08 (error) domain=[all]", "R-10 (warning) domain=[FLOW]", "R-12 (warning) domain=[FUNC]", "R-14 (warning) domain=[UC]", "R-15 (warning) domain=[FCHAIN]", "R-16 (warning) domain=[ACTOR]", "R-17 (warning) domain=[SYS]", "R-18 (error) domain=[all]", "R-19 (warning) domain=[TEST]", "R-20 (warning) domain=[FUNC]", "R-21 (warning) domain=[FCHAIN]", "R-22 (warning) domain=[FUNC]", "R-23 (warning) domain=[MOD]", "R-26 (warning) domain=[SCHEMA]", "R-27 (warning) domain=[MOD]", "R-28 (warning) domain=[graph]", "R-29 (error) domain=[TEST]", "R-30 (warning) domain=[FUNC]", "R-31 (warning) domain=[FUNC]", "RD-01 (warning) domain=[REQ]", "RD-02 (warning) domain=[REQ]", "RD-03 (info) domain=[REQ]", "RD-04 (warning) domain=[FUNC,MOD,SYS]", "RT-01 (error) domain=[FUNC]", "SC-02 (warning) domain=[SCHEMA]", "SC-04 (warning) domain=[FLOW]", "UC-01 (error) domain=[UC]", "UC-02 (error) domain=[UC]", "UC-03 (warning) domain=[UC]", "UC-04 (warning) domain=[UC]", "UC-05 (info) domain=[UC]", "UC-06 (info) domain=[UC]", "VR-01 (info) domain=[TEST]"];
23
+ };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * GENERIERT — nicht von Hand pflegen.
3
+ *
4
+ * Das Golden-File der SE-Grammatik (CR-SM-244). Sein Diff IST der Changelog der Oberflaeche:
5
+ * jede Zeile hier ist ein Vertrag, an dem ein Konsument der Familie haengt. Erzeugt von
6
+ * `npm run grammar:snapshot -w @sigloch/contracts`, geprueft von
7
+ * `tests/unit/se-grammar-invariant.test.ts`.
8
+ *
9
+ * Wer diese Datei aendert, ohne die zugehoerige Version in ./index.ts zu bumpen, hat den Zweck
10
+ * verfehlt — `npm run check:grammar` faellt darueber.
11
+ */
12
+ export const GRAMMAR_SNAPSHOT = {
13
+ versions: {
14
+ ontology: "7.0.0",
15
+ metaModel: "1.4.0",
16
+ rules: "4.0.0",
17
+ },
18
+ elementTypes: [
19
+ "ACTOR",
20
+ "CR",
21
+ "FCHAIN",
22
+ "FLOW",
23
+ "FUNC",
24
+ "MOD",
25
+ "MS",
26
+ "REQ",
27
+ "SCHEMA",
28
+ "SESSION",
29
+ "SYS",
30
+ "TEST",
31
+ "UC",
32
+ ],
33
+ traceTypes: [
34
+ "allocate",
35
+ "compose",
36
+ "io",
37
+ "produces",
38
+ "relation",
39
+ "satisfy",
40
+ "verify",
41
+ ],
42
+ patterns: [
43
+ "ACTOR -io-> FLOW",
44
+ "ACTOR -io-> UC",
45
+ "CR -relation-> FUNC",
46
+ "CR -relation-> MOD",
47
+ "CR -relation-> MS",
48
+ "CR -relation-> REQ",
49
+ "CR -relation-> UC",
50
+ "FCHAIN -compose-> FUNC [1..*]",
51
+ "FCHAIN -satisfy-> REQ",
52
+ "FLOW -io-> ACTOR",
53
+ "FLOW -io-> FUNC",
54
+ "FLOW -io-> UC",
55
+ "FLOW -relation-> SCHEMA",
56
+ "FUNC -allocate-> MOD",
57
+ "FUNC -compose-> FUNC [0..*]",
58
+ "FUNC -io-> FLOW",
59
+ "FUNC -satisfy-> REQ",
60
+ "MOD -compose-> MOD [0..*]",
61
+ "MOD -io-> MOD",
62
+ "MOD -satisfy-> REQ",
63
+ "MS -compose-> FUNC",
64
+ "MS -compose-> MS",
65
+ "MS -compose-> REQ",
66
+ "MS -compose-> UC",
67
+ "MS -relation-> MS",
68
+ "REQ -compose-> REQ [0..*]",
69
+ "SESSION -produces-> * (audit)",
70
+ "SYS -compose-> MOD [0..*]",
71
+ "SYS -compose-> REQ [0..*]",
72
+ "SYS -compose-> SYS [0..*]",
73
+ "SYS -compose-> UC [1..*]",
74
+ "SYS -satisfy-> REQ",
75
+ "TEST -verify-> REQ",
76
+ "UC -compose-> FCHAIN [1..*]",
77
+ "UC -compose-> REQ [1..*]",
78
+ ],
79
+ attributes: [
80
+ "CR.rationale: string",
81
+ "CR.spike: boolean",
82
+ "CR.status: enum",
83
+ "FLOW.protocol: string",
84
+ "FLOW.qos: string",
85
+ "FUNC.concept: boolean",
86
+ "FUNC.external: boolean",
87
+ "FUNC.measuredMs: number",
88
+ "FUNC.realRef: object",
89
+ "FUNC.safety_relevant: boolean",
90
+ "FUNC.sourceFile: string",
91
+ "FUNC.timingBudgetMs: number",
92
+ "MOD.concept: boolean",
93
+ "MOD.external: boolean",
94
+ "MOD.kind: string",
95
+ "MOD.path: string",
96
+ "MOD.realRef: object",
97
+ "REQ.detection: number",
98
+ "REQ.occurrence: number",
99
+ "REQ.severity: number",
100
+ "SCHEMA.concept: boolean",
101
+ "SCHEMA.contract: string",
102
+ "SCHEMA.external: boolean",
103
+ "SCHEMA.realRef: object",
104
+ "TEST.concept: boolean",
105
+ "TEST.sourceFile: string",
106
+ "TEST.testRefs: array",
107
+ "UC.operatingMode: string",
108
+ ],
109
+ rules: [
110
+ "AF-01 (warning) domain=[graph]",
111
+ "AF-02 (warning) domain=[graph]",
112
+ "AF-03 (warning) domain=[graph]",
113
+ "AF-04 (warning) domain=[graph]",
114
+ "AF-05 (warning) domain=[graph]",
115
+ "AO-D01 (info) domain=[FUNC]",
116
+ "AO-D03 (info) domain=[FUNC]",
117
+ "BQ-01 (warning) domain=[REQ]",
118
+ "BQ-02 (warning) domain=[REQ]",
119
+ "BQ-04 (warning) domain=[REQ]",
120
+ "BQ-06 (warning) domain=[REQ]",
121
+ "BQ-07 (warning) domain=[REQ]",
122
+ "CA-01 (error) domain=[FUNC]",
123
+ "CL-01 (warning) domain=[ACTOR]",
124
+ "CR-01 (warning) domain=[MOD]",
125
+ "CR-R01 (error) domain=[CR]",
126
+ "CR-R02 (error) domain=[CR]",
127
+ "CR-R03 (warning) domain=[all]",
128
+ "CR-R04 (warning) domain=[CR]",
129
+ "FC-01 (warning) domain=[FCHAIN]",
130
+ "FC-02 (warning) domain=[UC]",
131
+ "FC-03 (warning) domain=[FUNC]",
132
+ "FC-04 (warning) domain=[FCHAIN]",
133
+ "FM-01 (warning) domain=[REQ]",
134
+ "FM-02 (warning) domain=[REQ]",
135
+ "FM-03 (error) domain=[REQ]",
136
+ "IO-01 (warning) domain=[FUNC]",
137
+ "MS-01 (warning) domain=[MS]",
138
+ "MS-02 (error) domain=[MS]",
139
+ "MS-03 (info) domain=[CR]",
140
+ "MT-01 (warning) domain=[MOD]",
141
+ "MT-02 (info) domain=[MOD]",
142
+ "ND-01 (error) domain=[FUNC]",
143
+ "ND-02 (error) domain=[SCHEMA]",
144
+ "NFR-01 (warning) domain=[FCHAIN,FUNC,MOD]",
145
+ "PH-01 (info) domain=[MOD]",
146
+ "R-01 (error) domain=[REQ]",
147
+ "R-02 (warning) domain=[FUNC]",
148
+ "R-03 (error) domain=[MOD]",
149
+ "R-04 (warning) domain=[MOD]",
150
+ "R-05 (warning) domain=[TEST]",
151
+ "R-08 (error) domain=[all]",
152
+ "R-10 (warning) domain=[FLOW]",
153
+ "R-12 (warning) domain=[FUNC]",
154
+ "R-14 (warning) domain=[UC]",
155
+ "R-15 (warning) domain=[FCHAIN]",
156
+ "R-16 (warning) domain=[ACTOR]",
157
+ "R-17 (warning) domain=[SYS]",
158
+ "R-18 (error) domain=[all]",
159
+ "R-19 (warning) domain=[TEST]",
160
+ "R-20 (warning) domain=[FUNC]",
161
+ "R-21 (warning) domain=[FCHAIN]",
162
+ "R-22 (warning) domain=[FUNC]",
163
+ "R-23 (warning) domain=[MOD]",
164
+ "R-26 (warning) domain=[SCHEMA]",
165
+ "R-27 (warning) domain=[MOD]",
166
+ "R-28 (warning) domain=[graph]",
167
+ "R-29 (error) domain=[TEST]",
168
+ "R-30 (warning) domain=[FUNC]",
169
+ "R-31 (warning) domain=[FUNC]",
170
+ "RD-01 (warning) domain=[REQ]",
171
+ "RD-02 (warning) domain=[REQ]",
172
+ "RD-03 (info) domain=[REQ]",
173
+ "RD-04 (warning) domain=[FUNC,MOD,SYS]",
174
+ "RT-01 (error) domain=[FUNC]",
175
+ "SC-02 (warning) domain=[SCHEMA]",
176
+ "SC-04 (warning) domain=[FLOW]",
177
+ "UC-01 (error) domain=[UC]",
178
+ "UC-02 (error) domain=[UC]",
179
+ "UC-03 (warning) domain=[UC]",
180
+ "UC-04 (warning) domain=[UC]",
181
+ "UC-05 (info) domain=[UC]",
182
+ "UC-06 (info) domain=[UC]",
183
+ "VR-01 (info) domain=[TEST]",
184
+ ],
185
+ };
@@ -3,9 +3,9 @@
3
3
  * Single source of truth for SE ontology schemas across all projects.
4
4
  */
5
5
  /** Ontology schema version (element types + trace types). */
6
- export declare const ONTOLOGY_VERSION = "6.0.0";
6
+ export declare const ONTOLOGY_VERSION = "7.0.0";
7
7
  /** Rules engine version (validation rules incl. RC conformance). */
8
- export declare const RULES_VERSION = "2.28.0";
8
+ export declare const RULES_VERSION = "4.0.0";
9
9
  /** Meta-model version (trace pattern constraints + format-e parser). */
10
10
  export declare const META_MODEL_VERSION = "1.4.0";
11
11
  export * from './ontology.js';
package/dist/se/index.js CHANGED
@@ -3,9 +3,9 @@
3
3
  * Single source of truth for SE ontology schemas across all projects.
4
4
  */
5
5
  /** Ontology schema version (element types + trace types). */
6
- export const ONTOLOGY_VERSION = '6.0.0'; // BREAKING (CR-SM-231b): das knotenweite `testResult` entfaellt — das Ergebnis haengt PRO `testRefs`-Eintrag (`result`, `ranAt`, `evidence`), aus demselben Grund wie `tool`. Ein einzelnes Ergebnis am Knoten konnte bei n Laeufen nicht sagen, welcher gemeint ist, und "einer rot, einer gruen" war gar nicht darstellbar — genau der Zustand, den ein Gate wissen muss. FM-03 liest jetzt "jeder Eintrag passed": "irgendeiner gruen" haette einen gruenen Unit-Lauf einen roten Visual-Lauf verdecken lassen. Ein Eintrag ohne Ergebnis ist nicht bestanden. VR-01 meldet die Dateien ohne Ergebnis statt nur den Knoten. Prior: BREAKING (CR-SM-231): `testRef` (Objekt) entfaellt ersatzlos zugunsten von `testRefs` (Array, min 1) — eine Abnahme, n Testdateien. Kein Union, kein Alias: ein Graph mit dem alten Attribut faellt R-19 zur Last und muss migriert werden. Anlass war ein Zaehl-Audit (537 laufende Tests in 67 Dateien, 14 TEST-Knoten mit je genau einer Datei, 53 Dateien an keine Abnahme gebunden) plus ein belegter Ausweichweg: dieselbe Spec-Datei stand im testRef ZWEIER TEST-Knoten, womit die Relation faktisch n:m war — ein roter Lauf keiner Abnahme mehr eindeutig zuordenbar, der TRR-Gate zaehlte dieselbe Evidenz doppelt. `tool` bleibt PRO EINTRAG: eine Abnahme mischt real die Runner (vitest + playwright). +AttributeSpec.type 'array' (testRefs ist das erste Listen-Attribut). Prior: BREAKING: `SemanticId` (`Name.TypeAbbr.Counter`) deleted, `ElementUid` (`<TYPE>-<slug>`) is the family canon — the old canon was used by no production graph of the family while 626 of 1145 elements already carried TYPE-slug (CR-SM-217); realRef unifies codeRef+schemaRef (+physical-MOD CAD ref), symbol optional; testRef stays separate (CR-228 C); +RepoRelativePathSchema on testRef/realRef .file — no absolute/`..` paths (CR-GC-255)
6
+ export const ONTOLOGY_VERSION = '7.0.0'; // BREAKING (CR-GC-366): das Trace-Pattern `FUNC -satisfy-> UC` entfaellt ersatzlos. Es war ein zweiter Weg vom Verhalten zum Use Case neben dem einzig tragenden `UC -compose-> FCHAIN -compose-> FUNC` — und der billigere: eine FUNC konnte einen UC bedienen, ohne in dessen Wirkkette zu stehen, womit IO-01 (FLOW-Pfade je Kettenpaar) und R-21 (Integrationstest je Kette) sie nie sahen. Die Abkuerzung umging genau die Pruefungen, die die Anbindung belastbar machen. Am graphcode-Selbstmodell waren 10 der 26 solchen Kanten reine Dopplung (die FUNC stand ohnehin in der FCHAIN), die restlichen 13 sind der Migrationsanlass. Jeder Graph mit solchen Kanten faellt jetzt R-18 (error) zur Last und muss migriert werden: FUNC in die FCHAIN des UC aufnehmen, satisfy-Kante loeschen. Prior: BREAKING (CR-SM-231b): das knotenweite `testResult` entfaellt — das Ergebnis haengt PRO `testRefs`-Eintrag (`result`, `ranAt`, `evidence`), aus demselben Grund wie `tool`. Ein einzelnes Ergebnis am Knoten konnte bei n Laeufen nicht sagen, welcher gemeint ist, und "einer rot, einer gruen" war gar nicht darstellbar — genau der Zustand, den ein Gate wissen muss. FM-03 liest jetzt "jeder Eintrag passed": "irgendeiner gruen" haette einen gruenen Unit-Lauf einen roten Visual-Lauf verdecken lassen. Ein Eintrag ohne Ergebnis ist nicht bestanden. VR-01 meldet die Dateien ohne Ergebnis statt nur den Knoten. Prior: BREAKING (CR-SM-231): `testRef` (Objekt) entfaellt ersatzlos zugunsten von `testRefs` (Array, min 1) — eine Abnahme, n Testdateien. Kein Union, kein Alias: ein Graph mit dem alten Attribut faellt R-19 zur Last und muss migriert werden. Anlass war ein Zaehl-Audit (537 laufende Tests in 67 Dateien, 14 TEST-Knoten mit je genau einer Datei, 53 Dateien an keine Abnahme gebunden) plus ein belegter Ausweichweg: dieselbe Spec-Datei stand im testRef ZWEIER TEST-Knoten, womit die Relation faktisch n:m war — ein roter Lauf keiner Abnahme mehr eindeutig zuordenbar, der TRR-Gate zaehlte dieselbe Evidenz doppelt. `tool` bleibt PRO EINTRAG: eine Abnahme mischt real die Runner (vitest + playwright). +AttributeSpec.type 'array' (testRefs ist das erste Listen-Attribut). Prior: BREAKING: `SemanticId` (`Name.TypeAbbr.Counter`) deleted, `ElementUid` (`<TYPE>-<slug>`) is the family canon — the old canon was used by no production graph of the family while 626 of 1145 elements already carried TYPE-slug (CR-SM-217); realRef unifies codeRef+schemaRef (+physical-MOD CAD ref), symbol optional; testRef stays separate (CR-228 C); +RepoRelativePathSchema on testRef/realRef .file — no absolute/`..` paths (CR-GC-255)
7
7
  /** Rules engine version (validation rules incl. RC conformance). */
8
- export const RULES_VERSION = '2.28.0'; // CR-SM-242: R-10 (`['FUNC']`→`['FLOW']`), SC-04 (`['SCHEMA']`→`['FLOW']`) und IO-01 (`['MOD']`→`['FUNC']`) deklarieren jetzt den Typ, an dem sie WIRKLICH melden. Alle drei iterierten ueber einen anderen Typ als den deklarierten: ihre Verstoesse erhoehten den Zaehler einer Grundgesamtheit, zu der sie nicht gehoerten. Gemessen am graphcode-Graphen (46 FLOW / 55 FUNC / 20 SCHEMA / 10 MOD) hob das `schema` von 0.713 auf 0.783 — SC-04 zog 46 FLOW-Verstoesse gegen 20 SCHEMA; bei IO-01 uebertraf der Zaehler (21) den eigenen Nenner-Beitrag (10) schon, nur der Clamp verhinderte einen negativen Score. Kein Evaluator, keine Regel-Semantik und kein Gate-Urteil geaendert — nur die deklarierte Grundgesamtheit, wie bei CR-SM-239. Neu erzwungen durch `se-rule-domain-invariant.test.ts`: der Typ jedes gemeldeten Elements liegt in der `domain` der Regel (stumm fuer 29 der 72 Regeln, die auf den Fixtures nicht feuern). CR-SM-243 (dieselbe Auslieferung): genau dieser Test hat vier weitere Faelle gefunden, die von Hand nie auffielen — R-03 (`['FUNC']`→`['MOD']`, meldet am Modul, das ASIL-D und QM mischt), FC-03 (`['FCHAIN']`→`['FUNC']`, meldet am FUNC mit der verschachtelten Zerlegung), NFR-01 (`['FUNC']`→`['MOD','FUNC','FCHAIN']`, Budgets liegen physisch am MOD und verhaltensseitig an FUNC/FCHAIN) und CR-R03 (`['CR']`→`['all']`, meldet am getrackten Zielknoten beliebigen Typs — nach R-08/R-18 die dritte und bewusst begruendete `all`-Regel). Prior: CR-SM-239: AF-01..05 und R-28 deklarieren `domain: ['graph']` statt `['SYS']` — sie pruefen EINMAL den Graphen, nicht die SYS-Elemente. Als Ein-Element-Gruppe waren sie ein Nenner-Beitrag mit fast sicherem Verstoss und zogen jede sonst leere Dimension auf 0 %: ein Graph ohne eine einzige REQ las `req` = 0 % (1 - 2/2 aus AF-01/AF-03) statt „nichts zu messen", und die Fokuswahl schickte den Executor dorthin. Kein Regelverhalten geaendert — nur die deklarierte Grundgesamtheit. Prior: CR-SM-231: +R-29 Testdatei-Exklusivitaet (severity **error**) — jede Testdatei erscheint in hoechstens einem `testRefs`. Das ist die Haelfte, die 1:n ERZWINGT statt es nur zu erlauben; ohne sie driftet das Attribut zurueck nach n:m. Bewusst schaerfer als R-19/R-20 (beide warning): eine doppelt beanspruchte Datei macht Gate-Zahlen nachweislich falsch, das ist eine Fehlmessung und kein Vollstaendigkeits-Signal. Rein graph-strukturell, kein I/O. R-19 und RC-02 lesen `testRefs`; RC-02 iteriert die Eintraege und nennt den konkreten Pfad statt nur der Knoten-ID. Prior: CR-SM-236: die letzten drei Urteilsschwellen sind Policy-Parameter statt Literal — CR-01 (`crossingFlows`, erstmals mit Aus-Zustand: `null` unterdrueckt auch die info je Modulpaar, die bisher die arch-Dimension verduennte), FM-03 (`riskRpn`, vorher inline 100) und R-04 (`moduleSize.{large,coupled,crossings}`, vorher inline 12/8/2 — drei Urteile, nicht eins). R-04 heisst jetzt 'Module size relative to crossing flows': die Regel waegt Groesse GEGEN Kreuzungen ab, der alte Name 'Max module size' verschwieg die zweite Haelfte der Bedingung. `RuleDefinition.evaluate` nimmt die Policy als zweiten Parameter, damit keine Familie einen Sonderweg braucht; einstellige Regelfunktionen bleiben zuweisbar. Bewertung unter DEFAULT_METRIC_POLICY unveraendert. Prior: CR-GC-315: R-12 + R-21 no longer tax FLOW reuse. R-12 restricted to dependency-carrying traces (compose/allocate/relation) — on `io` the only reachable shape was FUNC ─io→ FLOW ─io→ the SAME FUNC, i.e. read-modify-write on a shared FLOW, so the check was pure false-positive there (real io-cycles span ≥2 FUNCs and a 2-cycle test cannot see them); satisfy/verify/produces 2-cycles are pattern-illegal and belong to R-18. Its dedup key is now direction-independent (`type|sorted pair`) — keying on the message never collapsed anything, so every cycle was reported twice. R-21 no longer derives FUNC↔FUNC connections from FLOW co-adjacency alone: a producer/consumer pair with NO shared FCHAIN is silent. One hub FLOW with P producers and C consumers manufactured P·C findings, each demanding its own FCHAIN+integration test including pairs that never interact — reuse was penalised quadratically. The FCHAIN is the declared integration scope; the test is owed on that declared claim only. Rule intent (CR-GC-240 gap: unit/UC tests do not cover FUNC↔FUNC wiring) unchanged. Prior: CR-SM-227: +AF-01..05 Analysis-Freshness-Legs (analysis-freshness-rules.ts) — the third leg-kind a review gate checks (rule legs, layer-presence legs [R-28], now analysis-freshness legs, docs/articles/07-the-scoring-landscape.md); presence-only rules (Presence/Resolution-Split like R-19/R-20/R-26/R-27 vs. RC-01..05) checking a `graphVersion` freshness stamp exists under SYS.attributes.analysisFreshness.<AnalysisArtifactId> for each of the 5 CR-GC-221 judgment artifacts (conops/trade/assumption-review/fmea/implplan) — staleness (stamp vs live graphVersion) stays a consumer/I-O concern (`CreationCurrencyProvider`, @sigloch/graphcode-client), NOT evaluated here; Familie-Review 2026-08-05 gate assignment → RULE_TO_PHASE: ConOps/Trade/Assumption-Review → PDR, FMEA → CDR, Implementation Plan → TRR (SRR gets none); +AnalysisArtifactId enum + AnalysisFreshnessStampSchema (ontology.ts) as the SSOT graphcode-client's ARTIFACT_CATALOG analysis ids must match; grober Graph-Versions-Stempel only, no scope-hash in this first cut (conscious CR-SM-227 limitation).
8
+ export const RULES_VERSION = '4.0.0'; // BREAKING (CR-SM-245): MS-01 verliert den Legacy-Fallback `MS -compose-> *` ersatzlos — Scope ist ausschliesslich `CR -relation-> MS`. Der Fallback war ein zweiter Weg zu derselben Aussage: ein Meilenstein galt als befuellt, ohne dass ein CR auf ihn zeigte, womit MS-03 und jede Scope-Auswertung ihn nie sahen. Verschaerfung, deshalb major — gemessen faellt an den Selbstmodellen aber kein einziger Meilenstein (alle 7 in graphcode haben CR-Relationen, vier zusaetzlich compose; MS-01 bleibt 0 Befunde). Zweitens: '+MS-' in SE_PREFIXES — MS-01/02/03 lagen in KEINEM Profil (weder se noch coding) und liefen nur im default-Lauf. Die Dimension `ms` haengt an genau vier Regeln (MS-01/02/03 + AF-05), und AF-05 traegt als `domain: ['graph']`-Regel keinen Nenner bei — im se-Profil war `ms` damit ein strukturell leerer Topic-Score und das SRR-Gate um drei Legs aermer. Zweiter Fall der 'RD-'-Klasse (CR-SM-221), gefunden vom Profil-Test aus CR-SM-244. Prior: BREAKING (CR-GC-366): +R-30 Wirkketten-Bindung, +R-31 io-Verdrahtung, R-02 zieltyp-scharf. Die vier Pflichten einer FUNC (ein REQ erfuellen, verdrahtet sein, in einer Wirkkette haengen, in einem MOD wohnen) waren nur zu zweit geprueft: R-02 und R-22. R-30 schliesst die Kette — R-15 verlangte, dass eine FCHAIN Funktionen hat, niemand verlangte, dass eine Funktion zu einer FCHAIN gehoert; daran haengen aber IO-01 und R-21, die beide nur INNERHALB einer Kette pruefen. R-31 schliesst die Verdrahtung: R-10 stellt dieselbe Frage vom FLOW aus und sieht eine FUNC ohne jede io-Kante deshalb nie (sie kommt in der FLOW-Schleife nicht vor); nur ACTOR darf terminieren. Ein Befund je FUNC statt je fehlender Seite, sonst uebersteigt der Zaehler den Nenner-Beitrag (die Fehlmessung aus CR-SM-242). BREAKING ist R-02: der Filter war zieltyp-blind, ein `FUNC -satisfy-> UC` schaltete ihn stumm, obwohl kein Requirement erfuellt war — am Selbstmodell 6 von 33 Faellen verdeckt (gemeldet: 27). Messung am graphcode-Selbstmodell (82 FUNC): R-30 60, R-31 54 (52 ohne Eingang, 50 ohne Ausgang, 48 ohne beides), R-02 27->33, R-22 3, R-18 26 (die Migrationsschuld aus dem gestrichenen Pattern). Prior: CR-SM-242: R-10 (`['FUNC']`→`['FLOW']`), SC-04 (`['SCHEMA']`→`['FLOW']`) und IO-01 (`['MOD']`→`['FUNC']`) deklarieren jetzt den Typ, an dem sie WIRKLICH melden. Alle drei iterierten ueber einen anderen Typ als den deklarierten: ihre Verstoesse erhoehten den Zaehler einer Grundgesamtheit, zu der sie nicht gehoerten. Gemessen am graphcode-Graphen (46 FLOW / 55 FUNC / 20 SCHEMA / 10 MOD) hob das `schema` von 0.713 auf 0.783 — SC-04 zog 46 FLOW-Verstoesse gegen 20 SCHEMA; bei IO-01 uebertraf der Zaehler (21) den eigenen Nenner-Beitrag (10) schon, nur der Clamp verhinderte einen negativen Score. Kein Evaluator, keine Regel-Semantik und kein Gate-Urteil geaendert — nur die deklarierte Grundgesamtheit, wie bei CR-SM-239. Neu erzwungen durch `se-rule-domain-invariant.test.ts`: der Typ jedes gemeldeten Elements liegt in der `domain` der Regel (stumm fuer 29 der 72 Regeln, die auf den Fixtures nicht feuern). CR-SM-243 (dieselbe Auslieferung): genau dieser Test hat vier weitere Faelle gefunden, die von Hand nie auffielen — R-03 (`['FUNC']`→`['MOD']`, meldet am Modul, das ASIL-D und QM mischt), FC-03 (`['FCHAIN']`→`['FUNC']`, meldet am FUNC mit der verschachtelten Zerlegung), NFR-01 (`['FUNC']`→`['MOD','FUNC','FCHAIN']`, Budgets liegen physisch am MOD und verhaltensseitig an FUNC/FCHAIN) und CR-R03 (`['CR']`→`['all']`, meldet am getrackten Zielknoten beliebigen Typs — nach R-08/R-18 die dritte und bewusst begruendete `all`-Regel). Prior: CR-SM-239: AF-01..05 und R-28 deklarieren `domain: ['graph']` statt `['SYS']` — sie pruefen EINMAL den Graphen, nicht die SYS-Elemente. Als Ein-Element-Gruppe waren sie ein Nenner-Beitrag mit fast sicherem Verstoss und zogen jede sonst leere Dimension auf 0 %: ein Graph ohne eine einzige REQ las `req` = 0 % (1 - 2/2 aus AF-01/AF-03) statt „nichts zu messen", und die Fokuswahl schickte den Executor dorthin. Kein Regelverhalten geaendert — nur die deklarierte Grundgesamtheit. Prior: CR-SM-231: +R-29 Testdatei-Exklusivitaet (severity **error**) — jede Testdatei erscheint in hoechstens einem `testRefs`. Das ist die Haelfte, die 1:n ERZWINGT statt es nur zu erlauben; ohne sie driftet das Attribut zurueck nach n:m. Bewusst schaerfer als R-19/R-20 (beide warning): eine doppelt beanspruchte Datei macht Gate-Zahlen nachweislich falsch, das ist eine Fehlmessung und kein Vollstaendigkeits-Signal. Rein graph-strukturell, kein I/O. R-19 und RC-02 lesen `testRefs`; RC-02 iteriert die Eintraege und nennt den konkreten Pfad statt nur der Knoten-ID. Prior: CR-SM-236: die letzten drei Urteilsschwellen sind Policy-Parameter statt Literal — CR-01 (`crossingFlows`, erstmals mit Aus-Zustand: `null` unterdrueckt auch die info je Modulpaar, die bisher die arch-Dimension verduennte), FM-03 (`riskRpn`, vorher inline 100) und R-04 (`moduleSize.{large,coupled,crossings}`, vorher inline 12/8/2 — drei Urteile, nicht eins). R-04 heisst jetzt 'Module size relative to crossing flows': die Regel waegt Groesse GEGEN Kreuzungen ab, der alte Name 'Max module size' verschwieg die zweite Haelfte der Bedingung. `RuleDefinition.evaluate` nimmt die Policy als zweiten Parameter, damit keine Familie einen Sonderweg braucht; einstellige Regelfunktionen bleiben zuweisbar. Bewertung unter DEFAULT_METRIC_POLICY unveraendert. Prior: CR-GC-315: R-12 + R-21 no longer tax FLOW reuse. R-12 restricted to dependency-carrying traces (compose/allocate/relation) — on `io` the only reachable shape was FUNC ─io→ FLOW ─io→ the SAME FUNC, i.e. read-modify-write on a shared FLOW, so the check was pure false-positive there (real io-cycles span ≥2 FUNCs and a 2-cycle test cannot see them); satisfy/verify/produces 2-cycles are pattern-illegal and belong to R-18. Its dedup key is now direction-independent (`type|sorted pair`) — keying on the message never collapsed anything, so every cycle was reported twice. R-21 no longer derives FUNC↔FUNC connections from FLOW co-adjacency alone: a producer/consumer pair with NO shared FCHAIN is silent. One hub FLOW with P producers and C consumers manufactured P·C findings, each demanding its own FCHAIN+integration test including pairs that never interact — reuse was penalised quadratically. The FCHAIN is the declared integration scope; the test is owed on that declared claim only. Rule intent (CR-GC-240 gap: unit/UC tests do not cover FUNC↔FUNC wiring) unchanged. Prior: CR-SM-227: +AF-01..05 Analysis-Freshness-Legs (analysis-freshness-rules.ts) — the third leg-kind a review gate checks (rule legs, layer-presence legs [R-28], now analysis-freshness legs, docs/articles/07-the-scoring-landscape.md); presence-only rules (Presence/Resolution-Split like R-19/R-20/R-26/R-27 vs. RC-01..05) checking a `graphVersion` freshness stamp exists under SYS.attributes.analysisFreshness.<AnalysisArtifactId> for each of the 5 CR-GC-221 judgment artifacts (conops/trade/assumption-review/fmea/implplan) — staleness (stamp vs live graphVersion) stays a consumer/I-O concern (`CreationCurrencyProvider`, @sigloch/graphcode-client), NOT evaluated here; Familie-Review 2026-08-05 gate assignment → RULE_TO_PHASE: ConOps/Trade/Assumption-Review → PDR, FMEA → CDR, Implementation Plan → TRR (SRR gets none); +AnalysisArtifactId enum + AnalysisFreshnessStampSchema (ontology.ts) as the SSOT graphcode-client's ARTIFACT_CATALOG analysis ids must match; grober Graph-Versions-Stempel only, no scope-hash in this first cut (conscious CR-SM-227 limitation).
9
9
  /** Meta-model version (trace pattern constraints + format-e parser). */
10
10
  export const META_MODEL_VERSION = '1.4.0'; // -REQ→MOD allocate pattern removed (CR-228 A); +FUNC→FUNC compose (blackbox function decomposition)
11
11
  export * from './ontology.js';
@@ -20,7 +20,14 @@ export const TRACE_PATTERNS = [
20
20
  { source: 'MOD', target: 'MOD', type: 'io', description: 'Module communicates with module (ACL)' },
21
21
  // ── satisfy (implementation) ──
22
22
  { source: 'FUNC', target: 'REQ', type: 'satisfy', description: 'Function implements requirement' },
23
- { source: 'FUNC', target: 'UC', type: 'satisfy', description: 'Function implements use case' },
23
+ // CR-GC-366: `FUNC -satisfy-> UC` ENTFAELLT (BREAKING). Es war ein zweiter, redundanter Weg
24
+ // vom Verhalten zum Use Case neben dem einzig tragenden `UC -compose-> FCHAIN -compose-> FUNC`.
25
+ // Zwei Wege = zwei Wahrheiten: eine FUNC konnte einen UC bedienen, ohne in dessen Wirkkette zu
26
+ // stehen, womit IO-01 (FLOW-Pfade je FCHAIN-Paar) und R-21 (Integrationstest je Kette) sie nie
27
+ // sahen — die Abkuerzung umging genau die Pruefungen, die die Anbindung belastbar machen.
28
+ // Gemessen am graphcode-Selbstmodell waren 10 von 26 solcher Kanten reine Dopplung (die FUNC
29
+ // stand ohnehin in einer FCHAIN); die restlichen 13 sind der Migrationsanlass, nicht ein
30
+ // Argument fuer das Pattern. Ersatz: die FUNC in die FCHAIN des UC aufnehmen (R-30).
24
31
  // CR-154: NFR satisfy — non-functional REQs can be satisfied by chains, modules, or the system
25
32
  { source: 'FCHAIN', target: 'REQ', type: 'satisfy', description: 'Function chain satisfies end-to-end NFR (e.g. latency)' },
26
33
  { source: 'MOD', target: 'REQ', type: 'satisfy', description: 'Module satisfies budget NFR (e.g. uptime, memory)' },
@@ -60,6 +60,11 @@ export const RULE_TO_DIMENSION = {
60
60
  'R-22': 'alloc', 'R-23': 'alloc', 'R-26': 'schema', 'R-27': 'arch',
61
61
  // CR-SM-226: R-28 Ebenen-Präsenz (>1 FUNC needs FLOW+SCHEMA) is an architecture rule.
62
62
  'R-28': 'arch',
63
+ // CR-GC-366: R-30 (Wirkketten-Bindung) und R-31 (io-Verdrahtung) bewerten beide, ob ein
64
+ // Funktionsblock ueberhaupt im Bauplan haengt — dieselbe Dimension wie R-20 (realRef) und
65
+ // R-28 (Ebenen-Praesenz). NICHT 'uc': R-14..R-17 fragen, ob ein Behaelter gefuellt ist;
66
+ // diese beiden fragen von der FUNC aus, ob sie angeschlossen ist.
67
+ 'R-30': 'arch', 'R-31': 'arch',
63
68
  // uc
64
69
  'UC-01': 'uc', 'UC-02': 'uc', 'UC-03': 'uc', 'UC-04': 'uc',
65
70
  'UC-05': 'uc', 'UC-06': 'uc',
@@ -149,6 +154,9 @@ export const RULE_TO_PHASE = {
149
154
  'R-15': 'PDR',
150
155
  'FC-01': 'PDR', 'FC-03': 'PDR', 'FC-04': 'PDR',
151
156
  'R-28': 'PDR', // Ebenen-Präsenz (CR-SM-226)
157
+ // CR-GC-366: Anschluss des Funktionsbaus — gehoert an dasselbe Gate wie R-15/IO-01,
158
+ // die auf derselben Kette aufsetzen.
159
+ 'R-30': 'PDR', 'R-31': 'PDR',
152
160
  'MT-01': 'PDR', 'MT-02': 'PDR',
153
161
  'ND-01': 'PDR',
154
162
  'AO-D01': 'PDR', 'AO-D03': 'PDR', 'CR-01': 'PDR', 'RT-01': 'PDR', 'PH-01': 'PDR', 'CA-01': 'PDR',
package/dist/se/rules.js CHANGED
@@ -103,8 +103,15 @@ function reqMustHaveVerification(graph) {
103
103
  function funcMustSatisfyReq(graph) {
104
104
  const funcs = graph.elements.filter(e => e.type === 'FUNC');
105
105
  const allReqs = graph.elements.filter(e => e.type === 'REQ');
106
+ // CR-GC-366: der Filter prueft den ZIELTYP. Vorher war er zieltyp-blind (`type === 'satisfy'`),
107
+ // womit ein `FUNC -satisfy-> UC` die Regel stumm schaltete, obwohl kein Requirement erfuellt war —
108
+ // der Regelname versprach mehr als der Code pruefte. Am graphcode-Selbstmodell verdeckte das 6 von
109
+ // 33 Faellen (gemeldet: 27). Mit dem Wegfall des UC-Patterns waere der alte Ausdruck de facto
110
+ // aequivalent geworden; explizit bleibt er trotzdem, damit die Regel nicht davon abhaengt, welche
111
+ // Ziele das Meta-Modell gerade zulaesst.
112
+ const reqIds = new Set(allReqs.map(r => r.id));
106
113
  return funcs
107
- .filter(fn => !graph.traces.some(t => t.source === fn.id && t.type === 'satisfy'))
114
+ .filter(fn => !graph.traces.some(t => t.source === fn.id && t.type === 'satisfy' && reqIds.has(t.target)))
108
115
  .map(fn => {
109
116
  const modId = findParentModule(graph, fn.id);
110
117
  // Candidate REQs: same module's other FUNCs satisfy these, or constraints referencing this func
@@ -564,18 +571,19 @@ function sysMustHaveCompose(graph) {
564
571
  // ---------------------------------------------------------------------------
565
572
  // MS-01: Milestone without scope — no CRs assigned via CR→MS [relation]
566
573
  // (CR-207: scope = CRs pointing to this MS, not compose children)
574
+ //
575
+ // CR-SM-245: der Legacy-Fallback (`MS -compose-> *` zaehlt auch als Scope) ist ersatzlos weg.
576
+ // Er war ein zweiter Weg zu derselben Aussage neben der einzig tragenden `CR -relation-> MS` —
577
+ // und der billigere: ein Meilenstein galt als befuellt, ohne dass ein einziger CR auf ihn zeigte,
578
+ // womit MS-03 (CR ohne Milestone) und jede Scope-Auswertung ihn nie sahen. Gemessen am
579
+ // graphcode-Selbstmodell aendert die Entfernung nichts (alle 7 MS haben CR-Relationen, vier
580
+ // zusaetzlich compose) — genau deshalb ist jetzt der Zeitpunkt.
567
581
  // ---------------------------------------------------------------------------
568
582
  function msEmptyScope(graph) {
569
- const milestones = graph.elements.filter(e => e.type === 'MS');
570
- return milestones
571
- .filter(ms => {
572
- // Check: any CR MS [relation] trace?
573
- const hasCr = graph.traces.some(t => t.type === 'relation' && t.target === ms.id &&
574
- graph.elements.some(e => e.id === t.source && e.type === 'CR'));
575
- // Fallback: legacy compose traces still count
576
- const hasCompose = graph.traces.some(t => t.source === ms.id && t.type === 'compose');
577
- return !hasCr && !hasCompose;
578
- })
583
+ const crIds = new Set(graph.elements.filter(e => e.type === 'CR').map(e => e.id));
584
+ return graph.elements
585
+ .filter(e => e.type === 'MS')
586
+ .filter(ms => !graph.traces.some(t => t.type === 'relation' && t.target === ms.id && crIds.has(t.source)))
579
587
  .map(ms => ({
580
588
  rule_id: 'MS-01',
581
589
  severity: 'warning',
@@ -1016,6 +1024,105 @@ function ebenenPraesenz(graph) {
1016
1024
  context: { element_type: anchor.type, element_name: anchor.name },
1017
1025
  }];
1018
1026
  }
1027
+ // ---------------------------------------------------------------------------
1028
+ // R-30: FUNC gehoert in eine Wirkkette (CR-GC-366)
1029
+ // Die Ontologie deckte nur die Gegenrichtung ab: R-15 verlangt, dass eine FCHAIN
1030
+ // Funktionen hat — niemand verlangte, dass eine Funktion zu einer FCHAIN gehoert.
1031
+ // Genau daran haengt aber die uebrige Absicherung: IO-01 bildet FLOW-Pfade nur
1032
+ // zwischen FUNCs DERSELBEN Kette, R-21 fordert den Integrationstest je Kette.
1033
+ // Eine FUNC ausserhalb jeder Kette faellt durch beide Netze, ohne dass etwas meldet.
1034
+ // Severity warning, nicht error: am Selbstmodell trifft es 60 von 82 FUNC — ein
1035
+ // error wuerde ueber das Delta-Gate jede weitere Mutation blockieren und waere
1036
+ // Neubau-Zwang statt Steuerung.
1037
+ // ---------------------------------------------------------------------------
1038
+ function funcMustBeInEffectChain(graph) {
1039
+ const funcs = graph.elements.filter(e => e.type === 'FUNC');
1040
+ const fchains = graph.elements.filter(e => e.type === 'FCHAIN');
1041
+ const funcIds = new Set(funcs.map(f => f.id));
1042
+ const fchainIds = new Set(fchains.map(fc => fc.id));
1043
+ const inChain = new Set(graph.traces
1044
+ .filter(t => t.type === 'compose' && fchainIds.has(t.source) && funcIds.has(t.target))
1045
+ .map(t => t.target));
1046
+ // Zerlegung erbt Mitgliedschaft nach unten: steht der Elternteil in einer Kette,
1047
+ // sind seine Kinder dessen Realisierung eine Ebene tiefer (TRACE_PATTERNS
1048
+ // `FUNC -compose-> FUNC`) und damit mitgebunden. Ohne diesen Schritt meldete die
1049
+ // Regel jede Sub-Funktion eines Kettenmitglieds als Waise — ein Fehlbefund aus
1050
+ // der Ontologie selbst. Am Selbstmodell aendert der Schritt heute nichts
1051
+ // (Zerlegungsbaum und Ketten sind dort disjunkt), er verhindert den Fehlbefund
1052
+ // fuer jedes Modell, das beides kombiniert.
1053
+ const parentOf = new Map();
1054
+ for (const t of graph.traces) {
1055
+ if (t.type === 'compose' && funcIds.has(t.source) && funcIds.has(t.target)) {
1056
+ parentOf.set(t.target, t.source);
1057
+ }
1058
+ }
1059
+ const reachesChain = (start) => {
1060
+ const seen = new Set([start]);
1061
+ let cur = start;
1062
+ while (cur) {
1063
+ if (inChain.has(cur))
1064
+ return true;
1065
+ cur = parentOf.get(cur);
1066
+ if (!cur || seen.has(cur))
1067
+ return false;
1068
+ seen.add(cur);
1069
+ }
1070
+ return false;
1071
+ };
1072
+ return funcs
1073
+ .filter(fn => !reachesChain(fn.id))
1074
+ .map(fn => ({
1075
+ rule_id: 'R-30',
1076
+ severity: 'warning',
1077
+ element_id: fn.id,
1078
+ message: `${fn.id} belongs to no function chain (no FCHAIN compose, directly or via a parent FUNC)`,
1079
+ fix_hint: 'Add the FUNC to the FCHAIN of the use case it serves (FCHAIN -compose-> FUNC)',
1080
+ context: {
1081
+ element_type: fn.type,
1082
+ element_name: fn.name,
1083
+ candidate_targets: toCandidates(fchains, fn),
1084
+ },
1085
+ }));
1086
+ }
1087
+ // ---------------------------------------------------------------------------
1088
+ // R-31: FUNC ist verdrahtet — Ein- UND Ausgang per io (CR-GC-366)
1089
+ // Nur ACTOR darf eine Kette terminieren; eine FUNC ohne Eingang oder ohne Ausgang
1090
+ // ist ein toter Block. R-10 prueft dieselbe Verdrahtung vom FLOW aus ("hat dieser
1091
+ // Flow Producer und Consumer?") und sieht eine FUNC ohne jede io-Kante deshalb nie:
1092
+ // sie kommt in der FLOW-Schleife gar nicht vor. IO-01 setzt Ketten-Mitgliedschaft
1093
+ // voraus und sieht sie ebenfalls nicht. Diese Regel schliesst die FUNC-Seite.
1094
+ // EIN Befund je FUNC (nicht je fehlender Seite): sonst uebersteigt der Zaehler den
1095
+ // eigenen Nenner-Beitrag, was CR-SM-242 fuer IO-01 als Fehlmessung nachgewiesen hat.
1096
+ // ---------------------------------------------------------------------------
1097
+ function funcMustBeWired(graph) {
1098
+ const funcs = graph.elements.filter(e => e.type === 'FUNC');
1099
+ const flows = graph.elements.filter(e => e.type === 'FLOW');
1100
+ const io = graph.traces.filter(t => t.type === 'io');
1101
+ const hasInput = new Set(io.map(t => t.target)); // FLOW -io-> FUNC
1102
+ const hasOutput = new Set(io.map(t => t.source)); // FUNC -io-> FLOW
1103
+ return funcs
1104
+ .map(fn => {
1105
+ const missing = [];
1106
+ if (!hasInput.has(fn.id))
1107
+ missing.push('input');
1108
+ if (!hasOutput.has(fn.id))
1109
+ missing.push('output');
1110
+ return { fn, missing };
1111
+ })
1112
+ .filter(({ missing }) => missing.length > 0)
1113
+ .map(({ fn, missing }) => ({
1114
+ rule_id: 'R-31',
1115
+ severity: 'warning',
1116
+ element_id: fn.id,
1117
+ message: `${fn.id} is not wired into any flow (missing: ${missing.join(' + ')})`,
1118
+ fix_hint: 'Connect the FUNC to a FLOW on the missing side (FLOW -io-> FUNC for input, FUNC -io-> FLOW for output) — only an ACTOR may terminate a chain',
1119
+ context: {
1120
+ element_type: fn.type,
1121
+ element_name: fn.name,
1122
+ candidate_targets: toCandidates(flows, fn),
1123
+ },
1124
+ }));
1125
+ }
1019
1126
  export const V3_RULES = [
1020
1127
  { id: 'R-01', name: 'REQ must have verification', severity: 'error', evaluate: reqMustHaveVerification, domain: ['REQ'] },
1021
1128
  { id: 'R-02', name: 'FUNC must satisfy REQ', severity: 'warning', evaluate: funcMustSatisfyReq, domain: ['FUNC'] },
@@ -1050,6 +1157,8 @@ export const V3_RULES = [
1050
1157
  { id: 'MS-01', name: 'Milestone empty scope', severity: 'warning', evaluate: msEmptyScope, domain: ['MS'] },
1051
1158
  { id: 'MS-02', name: 'Milestone dangling dependency', severity: 'error', evaluate: msDanglingDependency, domain: ['MS'] },
1052
1159
  { id: 'R-28', name: 'Ebenen-Präsenz (FLOW+SCHEMA when funcCount>1)', severity: 'warning', evaluate: ebenenPraesenz, domain: ['graph'] },
1160
+ { id: 'R-30', name: 'FUNC must belong to a function chain', severity: 'warning', evaluate: funcMustBeInEffectChain, domain: ['FUNC'] },
1161
+ { id: 'R-31', name: 'FUNC must be wired (io input + output)', severity: 'warning', evaluate: funcMustBeWired, domain: ['FUNC'] },
1053
1162
  ];
1054
1163
  /** Run all rules against a graph. CR-SM-236: `policy` ist Pflicht — wie bei `evaluateAllRules`. */
1055
1164
  export function evaluateRules(graph, policy) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigloch/contracts",
3
- "version": "4.2.0",
3
+ "version": "5.0.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -24,11 +24,14 @@
24
24
  },
25
25
  "scripts": {
26
26
  "build": "rm -rf dist && tsc",
27
+ "pretest": "node scripts/check-grammar-version.mjs",
27
28
  "test": "vitest run",
28
- "prepublishOnly": "npm run build && npm run test"
29
+ "check:grammar": "node scripts/check-grammar-version.mjs",
30
+ "grammar:snapshot": "SE_WRITE_GRAMMAR_SNAPSHOT=1 vitest run tests/unit/se-grammar-invariant.test.ts",
31
+ "prepublishOnly": "npm run build && npm run test",
32
+ "grammar:measure": "SE_MEASURE=1 vitest run tests/unit/se-grammar-measure.test.ts"
29
33
  },
30
34
  "dependencies": {
31
- "@sigloch/contracts": "^4.1.0",
32
35
  "zod": "^4.3.6"
33
36
  },
34
37
  "license": "MIT",