@sigloch/contracts 6.3.0 → 9.1.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.
@@ -59,6 +59,34 @@ export interface ConformanceRuleDefinition {
59
59
  severity: RuleSeverity;
60
60
  evaluate: (graph: OntologyGraph, facts: CodeFacts) => RuleViolation[];
61
61
  }
62
+ /**
63
+ * Wie viel des Import-Graphen konnte RC-05 überhaupt ansehen? (CR-SM-268 Teil 2)
64
+ *
65
+ * Eine MESSUNG über den Lauf, kein Befund über das Modell — deshalb eine eigene Funktion neben
66
+ * den Regeln und keine `RuleViolation`. „12 von 178 Endpunkten nicht zugeordnet" sagt nichts
67
+ * über den Graphen aus; es sagt, wie belastbar die Zahl daneben ist.
68
+ *
69
+ * Vorher hing die Liste als Textanhang an der Meldung eines ANDEREN Befundes und verschwand mit
70
+ * ihm: CR-GC-423 hat die drei RC-05-Drift-Befunde von graphcode geschlossen, und mit ihnen war
71
+ * die Liste der nicht zugeordneten Dateien weg — sie musste in CR-GC-424/425 von Hand
72
+ * rekonstruiert werden. „RC-05: 0 Befunde" hieß damit wahlweise „alle 178 Endpunkte geprüft,
73
+ * keiner driftet" oder „166 geprüft, 12 gar nicht angesehen", und niemand konnte die beiden
74
+ * unterscheiden.
75
+ *
76
+ * Geschwister von `skipped` (CR-GC-398), nicht dasselbe: `skipped` heißt „diese Quelle wurde GAR
77
+ * NICHT ausgewertet" (kein lesbarer repoRoot). Hier ist sie ausgewertet worden, und trotzdem
78
+ * fällt ein Teil durch. Zwei Zustände, zwei Felder — zusammengelegt wären „nicht gelaufen" und
79
+ * „gelaufen, aber blind" wieder ununterscheidbar.
80
+ */
81
+ export interface ImportCoverage {
82
+ /** Verschiedene Dateien, die überhaupt als Import-Endpunkt vorkommen. */
83
+ endpoints: number;
84
+ /** Davon einer MOD zugeordnet — nur diese konnte RC-05 beurteilen. */
85
+ assigned: number;
86
+ /** Der Rest, benannt statt gezählt: ohne die Namen ist die Lücke nicht schließbar. */
87
+ unassigned: string[];
88
+ }
89
+ export declare function importCoverage(graph: OntologyGraph, facts: CodeFacts): ImportCoverage;
62
90
  /** All RC conformance rules — evaluated by executors that can supply CodeFacts. */
63
91
  export declare const CODE_CONFORMANCE_RULES: ConformanceRuleDefinition[];
64
92
  /** Run all RC rules against a graph + extracted code facts. */
@@ -267,15 +267,19 @@ function schemaRefMustBeUsed(graph, facts) {
267
267
  // such adjacency ⇒ one RC-05 per (fromMod → toMod) pair, evidence = the
268
268
  // crossing file imports.
269
269
  // ---------------------------------------------------------------------------
270
- function importDriftConformance(graph, facts) {
271
- const importEdges = facts.importEdges ?? [];
272
- if (importEdges.length === 0)
273
- return [];
270
+ /**
271
+ * Datei MOD, die EINE Auflösung für RC-05 und `importCoverage` (CR-SM-268 Teil 2).
272
+ *
273
+ * Bewusst herausgezogen und nicht zweimal geschrieben: die Abdeckungszahl muss dieselbe Frage
274
+ * stellen wie die Regel, sonst misst sie etwas anderes als das, was geprüft wird — und eine
275
+ * Abdeckungszahl, die nicht zur Prüfung passt, ist schlimmer als keine.
276
+ *
277
+ * Zwei Wege, in dieser Reihenfolge: die `realRef` einer allozierten FUNC (eine gebundene Datei
278
+ * gehört zur MOD ihrer FUNC), danach das `path`-Präfix einer MOD, longest-prefix. Wer keinen
279
+ * von beiden trifft, ist `unassigned` — und wird nie still verworfen.
280
+ */
281
+ function buildModResolver(graph) {
274
282
  const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
275
- const modIds = graph.elements.filter(e => e.type === 'MOD').map(e => e.id);
276
- if (modIds.length === 0)
277
- return [];
278
- // 1. file → MOD. Direct realRef bindings first.
279
283
  const fileToMod = new Map();
280
284
  for (const el of graph.elements) {
281
285
  if (el.type !== 'FUNC')
@@ -292,7 +296,7 @@ function importDriftConformance(graph, facts) {
292
296
  .filter(e => e.type === 'MOD' && typeof e.attributes?.path === 'string')
293
297
  .map(e => ({ id: e.id, path: e.attributes.path.replace(/[*].*$/, '').replace(/\/+$/, '') }))
294
298
  .filter(m => m.path.length > 0);
295
- const resolveMod = (file) => {
299
+ return (file) => {
296
300
  if (fileToMod.has(file))
297
301
  return fileToMod.get(file);
298
302
  let best;
@@ -303,6 +307,27 @@ function importDriftConformance(graph, facts) {
303
307
  }
304
308
  return best?.id;
305
309
  };
310
+ }
311
+ export function importCoverage(graph, facts) {
312
+ const importEdges = facts.importEdges ?? [];
313
+ const resolveMod = buildModResolver(graph);
314
+ const endpoints = new Set();
315
+ for (const edge of importEdges) {
316
+ endpoints.add(edge.from);
317
+ endpoints.add(edge.to);
318
+ }
319
+ const unassigned = [...endpoints].filter(f => resolveMod(f) === undefined).sort();
320
+ return { endpoints: endpoints.size, assigned: endpoints.size - unassigned.length, unassigned };
321
+ }
322
+ function importDriftConformance(graph, facts) {
323
+ const importEdges = facts.importEdges ?? [];
324
+ if (importEdges.length === 0)
325
+ return [];
326
+ const modIds = graph.elements.filter(e => e.type === 'MOD').map(e => e.id);
327
+ if (modIds.length === 0)
328
+ return [];
329
+ const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
330
+ const resolveMod = buildModResolver(graph);
306
331
  // 2. graph MOD-adjacency (undirected) from io crossings FUNC_A ─io→ FLOW ─io→ FUNC_B.
307
332
  const funcMod = (funcId) => {
308
333
  const t = graph.traces.find(tr => tr.source === funcId && tr.type === 'allocate' && typeOf.get(tr.target) === 'MOD');
@@ -332,16 +357,15 @@ function importDriftConformance(graph, facts) {
332
357
  const [x, y] = [a, b].sort();
333
358
  return adjacent.has(`${x}|${y}`);
334
359
  };
335
- // 3. drift per (fromMod → toMod), aggregating evidence; collect unassigned files.
360
+ // 3. drift per (fromMod → toMod), aggregating evidence.
361
+ // CR-SM-268 Teil 2: die nicht zugeordneten Dateien werden hier NICHT mehr gesammelt. Sie
362
+ // hingen als Textanhang an der Meldung eines anderen Befundes und verschwanden mit ihm
363
+ // (CR-GC-423). Ihr Ort ist jetzt `importCoverage` — eine Messung, die auch dann etwas sagt,
364
+ // wenn diese Regel schweigt.
336
365
  const drift = new Map();
337
- const unassigned = new Set();
338
366
  for (const edge of importEdges) {
339
367
  const fromMod = resolveMod(edge.from);
340
368
  const toMod = resolveMod(edge.to);
341
- if (!fromMod)
342
- unassigned.add(edge.from);
343
- if (!toMod)
344
- unassigned.add(edge.to);
345
369
  if (!fromMod || !toMod || fromMod === toMod)
346
370
  continue;
347
371
  if (isAdjacent(fromMod, toMod))
@@ -352,7 +376,6 @@ function importDriftConformance(graph, facts) {
352
376
  drift.set(key, entry);
353
377
  }
354
378
  const nameOf = new Map(graph.elements.map(e => [e.id, e.name]));
355
- const unassignedNote = unassigned.size > 0 ? ` (unassigned files not mapped to a MOD: ${[...unassigned].sort().join(', ')})` : '';
356
379
  return [...drift.values()].map(d => {
357
380
  const shown = d.evidence.slice(0, 5);
358
381
  const more = d.evidence.length > shown.length ? ` +${d.evidence.length - shown.length} more` : '';
@@ -360,7 +383,7 @@ function importDriftConformance(graph, facts) {
360
383
  rule_id: 'RC-05',
361
384
  severity: 'warning',
362
385
  element_id: d.fromMod,
363
- message: `${d.fromMod} imports ${d.toMod} across a module boundary the graph does not document — evidence: ${shown.join('; ')}${more}${unassignedNote}`,
386
+ message: `${d.fromMod} imports ${d.toMod} across a module boundary the graph does not document — evidence: ${shown.join('; ')}${more}`,
364
387
  fix_hint: `Document the ${d.fromMod}→${d.toMod} dependency in the graph (an io/FLOW connection between their FUNCs), or remove the import`,
365
388
  context: {
366
389
  element_type: 'MOD',
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * @sigloch/contracts/se
11
11
  */
12
- import { ElementType } from './ontology.js';
13
- import type { TraceType, OntologyGraph, AttributeSpec } from './ontology.js';
12
+ import { ElementType, TraceType } from './ontology.js';
13
+ import type { OntologyGraph, AttributeSpec, ReqKind } from './ontology.js';
14
14
  export interface FormatEOperation {
15
15
  type: 'add_node' | 'remove_node' | 'update_node' | 'add_edge' | 'remove_edge' | 'strict_add_node' | 'strict_add_edge';
16
16
  semanticId: string;
@@ -68,6 +68,18 @@ export interface ParseFormatEOptions {
68
68
  * silent skip.
69
69
  */
70
70
  resolveType?: (uid: string) => ElementType | undefined;
71
+ /**
72
+ * CR-SM-266 B: die Kinds eines uid, den dieser Text nicht deklariert. Seit die
73
+ * satisfy-Patterns ein `where` tragen, entscheidet nicht mehr das Typ-Paar allein — ein
74
+ * `MOD -satisfy-> REQ` ist nur gueltig, wenn das REQ strukturelle Kinds traegt.
75
+ *
76
+ * Ohne diesen Resolver sieht der Parser die Kinds eines BESTEHENDEN Knotens nicht und lehnt
77
+ * solche Kanten ab. Das ist Absicht: fehlschlagen und es sagen, nicht raten und durchlassen
78
+ * — dieselbe Entscheidung wie bei `resolveType` ("Without it such a diff is an error, never
79
+ * a silent skip"). Knoten, die DIESER Text anlegt, brauchen ihn nicht; ihre Kinds stehen als
80
+ * `@kinds` in denselben Zeilen.
81
+ */
82
+ resolveKinds?: (uid: string) => readonly ReqKind[] | undefined;
71
83
  }
72
84
  /** Parse a Format E text block into validated operations. */
73
85
  export declare function parseFormatE(input: string, options?: ParseFormatEOptions): FormatEDiff;
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * @sigloch/contracts/se
11
11
  */
12
- import { ElementType, ELEMENT_ATTRIBUTES } from './ontology.js';
12
+ import { ElementType, ELEMENT_ATTRIBUTES, TraceType } from './ontology.js';
13
13
  import { isValidTrace } from './meta-model.js';
14
14
  // ---------------------------------------------------------------------------
15
15
  // Extraction
@@ -23,9 +23,10 @@ export function extractFormatE(llmOutput) {
23
23
  // ---------------------------------------------------------------------------
24
24
  // Parser
25
25
  // ---------------------------------------------------------------------------
26
- const VALID_TRACE_TYPES = new Set([
27
- 'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation', 'produces',
28
- ]);
26
+ // CR-SM-266 D5: ABGELEITET statt abgeschrieben. Die Liste stand hier als zweite Kopie des
27
+ // TraceType-Enums und trug `produces` noch, als es dort schon entfernt war — genau die Drift,
28
+ // die eine doppelte Wahrheit erzeugt. Eine Quelle, keine Pflege.
29
+ const VALID_TRACE_TYPES = new Set(TraceType.options);
29
30
  const OP_PREFIX = {
30
31
  '+': 'add',
31
32
  '-': 'remove',
@@ -109,6 +110,24 @@ export function parseFormatE(input, options = {}) {
109
110
  /** uid → type, from this text's node sections. */
110
111
  const declared = new Map();
111
112
  const typeOf = (uid) => declared.get(uid) ?? options.resolveType?.(uid);
113
+ /**
114
+ * CR-SM-266 B: die Kinds eines uid — erst aus DIESEM Text, dann aus dem Store.
115
+ *
116
+ * `kinds` reist in Format E als `@kinds a,b` und wird erst vom Konsumenten aufs
117
+ * Top-Level-Feld gehoben (graph-api-core, CR-195d). Hier steht es also noch in
118
+ * `op.attributes.kinds`, und genau dort wird es gelesen — sonst saehe eine Mutation, die
119
+ * REQ und satisfy-Kante in EINEM Block anlegt, die eigenen Kinds nicht.
120
+ */
121
+ const kindsOf = (uid) => {
122
+ for (const op of operations) {
123
+ if ('elementType' in op && op.semanticId === uid && op.attributes?.kinds != null) {
124
+ const raw = op.attributes.kinds;
125
+ const list = Array.isArray(raw) ? raw.map(String) : String(raw).split(',');
126
+ return list.map(k => k.trim()).filter(Boolean);
127
+ }
128
+ }
129
+ return options.resolveKinds?.(uid);
130
+ };
112
131
  for (const rawLine of input.split('\n')) {
113
132
  const line = rawLine.trim();
114
133
  if (!line || line.startsWith('//') || line.startsWith('#!'))
@@ -163,7 +182,7 @@ export function parseFormatE(input, options = {}) {
163
182
  if (isEdgeLine || section === 'edges') {
164
183
  const edgeMatch = isEdgeLine ? EDGE_RE.exec(line) : null;
165
184
  if (edgeMatch) {
166
- parseEdge(edgeMatch, typeOf, operations, errors);
185
+ parseEdge(edgeMatch, typeOf, kindsOf, operations, errors);
167
186
  }
168
187
  else {
169
188
  errors.push(`Invalid edge line: "${line}"`);
@@ -223,7 +242,7 @@ function parseNode(m, elementType, ops, errors) {
223
242
  ops.push({ type: 'add_node', semanticId: id, elementType, description: descr });
224
243
  }
225
244
  }
226
- function parseEdge(m, typeOf, ops, errors) {
245
+ function parseEdge(m, typeOf, kindsOf, ops, errors) {
227
246
  const opChar = m[1] || '+';
228
247
  const sourceId = m[2];
229
248
  const traceType = m[3];
@@ -254,7 +273,10 @@ function parseEdge(m, typeOf, ops, errors) {
254
273
  }
255
274
  // CR-148: Normalize before the meta-model check (e.g. FLOW→SCHEMA io → relation)
256
275
  const resolvedTraceType = normalizeTraceType(srcType, tgtType, traceType);
257
- if (!isValidTrace({ source: srcType, target: tgtType, type: resolvedTraceType })) {
276
+ if (!isValidTrace({
277
+ source: srcType, target: tgtType, type: resolvedTraceType,
278
+ sourceKinds: kindsOf(sourceId), targetKinds: kindsOf(targetId),
279
+ })) {
258
280
  errors.push(`Meta-model violation: ${srcType} -${traceType}-> ${tgtType} is not valid`);
259
281
  continue;
260
282
  }
@@ -281,7 +303,8 @@ function parseEdge(m, typeOf, ops, errors) {
281
303
  export function serializeToFormatE(graph) {
282
304
  const lines = [];
283
305
  // Nodes, grouped by type
284
- const modelingElements = graph.elements.filter(el => el.type !== 'SESSION'); // skip audit sessions
306
+ // CR-SM-266 D5: kein SESSION-Skip mehr der Elementtyp existiert nicht.
307
+ const modelingElements = graph.elements;
285
308
  if (modelingElements.length > 0) {
286
309
  lines.push('## Nodes');
287
310
  const byType = new Map();
@@ -313,7 +336,8 @@ export function serializeToFormatE(graph) {
313
336
  }
314
337
  }
315
338
  // Edges
316
- const modelingTraces = graph.traces.filter(t => t.category !== 'audit');
339
+ // CR-SM-266 D5: kein audit-Filter mehr — `Trace.category` existiert nicht.
340
+ const modelingTraces = graph.traces;
317
341
  if (modelingTraces.length > 0) {
318
342
  lines.push('');
319
343
  lines.push('## Edges');
@@ -11,13 +11,13 @@
11
11
  */
12
12
  export declare const GRAMMAR_SNAPSHOT: {
13
13
  readonly versions: {
14
- readonly ontology: "7.0.0";
15
- readonly metaModel: "2.0.0";
16
- readonly rules: "6.3.0";
14
+ readonly ontology: "8.0.0";
15
+ readonly metaModel: "4.0.0";
16
+ readonly rules: "9.1.0";
17
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..*]"];
18
+ readonly elementTypes: readonly ["ACTOR", "CR", "FCHAIN", "FLOW", "FUNC", "MOD", "MS", "REQ", "SCHEMA", "SYS", "TEST", "UC"];
19
+ readonly traceTypes: readonly ["allocate", "compose", "io", "relation", "satisfy", "verify"];
20
+ readonly patterns: readonly ["ACTOR -io-> FLOW", "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 -relation-> SCHEMA [0..1]", "FUNC -allocate-> MOD [0..1]", "FUNC -compose-> FUNC [0..*]", "FUNC -io-> FLOW", "FUNC -satisfy-> REQ where target.kinds in {functional,postcondition,precondition}", "MOD -compose-> MOD [0..*]", "MOD -satisfy-> REQ where target.kinds in {mitigation,non-functional,risk}", "MS -compose-> FUNC", "MS -compose-> REQ", "MS -compose-> UC", "MS -relation-> MS label=depends-on", "REQ -compose-> REQ [0..*]", "SYS -compose-> MOD [0..*]", "SYS -compose-> REQ [0..*]", "SYS -compose-> SYS [0..*]", "SYS -compose-> UC [1..*]", "SYS -satisfy-> REQ where target.kinds in {mitigation,non-functional,risk}", "TEST -verify-> REQ", "UC -compose-> FCHAIN [1..*]", "UC -compose-> REQ [1..*]"];
21
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
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-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
23
  readonly conformanceRules: readonly ["RC-01 (error)", "RC-02 (error)", "RC-03 (error)", "RC-04 (warning)", "RC-05 (warning)", "RC-06 (warning)"];
@@ -11,9 +11,9 @@
11
11
  */
12
12
  export const GRAMMAR_SNAPSHOT = {
13
13
  versions: {
14
- ontology: "7.0.0",
15
- metaModel: "2.0.0",
16
- rules: "6.3.0",
14
+ ontology: "8.0.0",
15
+ metaModel: "4.0.0",
16
+ rules: "9.1.0",
17
17
  },
18
18
  elementTypes: [
19
19
  "ACTOR",
@@ -25,7 +25,6 @@ export const GRAMMAR_SNAPSHOT = {
25
25
  "MS",
26
26
  "REQ",
27
27
  "SCHEMA",
28
- "SESSION",
29
28
  "SYS",
30
29
  "TEST",
31
30
  "UC",
@@ -34,14 +33,12 @@ export const GRAMMAR_SNAPSHOT = {
34
33
  "allocate",
35
34
  "compose",
36
35
  "io",
37
- "produces",
38
36
  "relation",
39
37
  "satisfy",
40
38
  "verify",
41
39
  ],
42
40
  patterns: [
43
41
  "ACTOR -io-> FLOW",
44
- "ACTOR -io-> UC",
45
42
  "CR -relation-> FUNC",
46
43
  "CR -relation-> MOD",
47
44
  "CR -relation-> MS",
@@ -51,27 +48,23 @@ export const GRAMMAR_SNAPSHOT = {
51
48
  "FCHAIN -satisfy-> REQ",
52
49
  "FLOW -io-> ACTOR",
53
50
  "FLOW -io-> FUNC",
54
- "FLOW -io-> UC",
55
- "FLOW -relation-> SCHEMA",
56
- "FUNC -allocate-> MOD",
51
+ "FLOW -relation-> SCHEMA [0..1]",
52
+ "FUNC -allocate-> MOD [0..1]",
57
53
  "FUNC -compose-> FUNC [0..*]",
58
54
  "FUNC -io-> FLOW",
59
- "FUNC -satisfy-> REQ",
55
+ "FUNC -satisfy-> REQ where target.kinds in {functional,postcondition,precondition}",
60
56
  "MOD -compose-> MOD [0..*]",
61
- "MOD -io-> MOD",
62
- "MOD -satisfy-> REQ",
57
+ "MOD -satisfy-> REQ where target.kinds in {mitigation,non-functional,risk}",
63
58
  "MS -compose-> FUNC",
64
- "MS -compose-> MS",
65
59
  "MS -compose-> REQ",
66
60
  "MS -compose-> UC",
67
- "MS -relation-> MS",
61
+ "MS -relation-> MS label=depends-on",
68
62
  "REQ -compose-> REQ [0..*]",
69
- "SESSION -produces-> * (audit)",
70
63
  "SYS -compose-> MOD [0..*]",
71
64
  "SYS -compose-> REQ [0..*]",
72
65
  "SYS -compose-> SYS [0..*]",
73
66
  "SYS -compose-> UC [1..*]",
74
- "SYS -satisfy-> REQ",
67
+ "SYS -satisfy-> REQ where target.kinds in {mitigation,non-functional,risk}",
75
68
  "TEST -verify-> REQ",
76
69
  "UC -compose-> FCHAIN [1..*]",
77
70
  "UC -compose-> REQ [1..*]",
@@ -3,11 +3,11 @@
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 = "7.0.0";
6
+ export declare const ONTOLOGY_VERSION = "8.0.0";
7
7
  /** Rules engine version (validation rules incl. RC conformance). */
8
- export declare const RULES_VERSION = "6.3.0";
8
+ export declare const RULES_VERSION = "9.1.0";
9
9
  /** Meta-model version (trace pattern constraints + format-e parser). */
10
- export declare const META_MODEL_VERSION = "2.0.0";
10
+ export declare const META_MODEL_VERSION = "4.0.0";
11
11
  export * from './ontology.js';
12
12
  export * from './rules.js';
13
13
  export * from './conformance-rules.js';
package/dist/se/index.js CHANGED
@@ -3,11 +3,11 @@
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 = '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)
6
+ export const ONTOLOGY_VERSION = '8.0.0'; // BREAKING (CR-SM-266a): drei Enums schrumpfen, und alle drei sind ENTFERNUNGEN. (1) ElementType 13 -> 12: `SESSION` entfaellt. (2) TraceType 7 -> 6: `produces` entfaellt. Beide trugen genau EIN Pattern (`SESSION -produces-> *`), und die Messung hat es widerlegt: 0 produces-Kanten und 0 SESSION-Knoten in ALLEN 9 aktiven Familie-Graphen, auch im graphcode-Selbstmodell nach 195 gegateten Versionen. Die reale Provenance lebt seit CR-GC-347 in `.graphcode/audit.jsonl` -- audit_trail/audit_stats lesen die DATEI, nie den Graphen; einziger Schreiber war ein toter Pfad (CR-195d in graph-api-core), Leser mit Wirkung keiner. Der urspruengliche CR-Entwurf hielt das Pattern fuer die Provenance-Kante und wollte es BEHALTEN ('Nicht-Befund'); die Zaehlung hat den Nicht-Befund kassiert -- Gate 1 verlangt fuer das Wiederaufmachen einer Entscheidung eine Messung, und hier war sie es. (3) Mit `produces` faellt `TraceCategory` und `Trace.category`. Das ist die wichtigste Haelfte: R-08 und R-18 haben `category === 'audit'` UEBERSPRUNGEN, also haette nach D5 jede Kante mit diesem selbstgesetzten Attribut die komplette Pattern-Matrix UND die Referenzintegritaet umgangen. Ein Attribut, das eine Strukturpruefung abschaltet, ist die Klasse aus CR-SM-263/-257; hier zusaetzlich der billige Zweitweg aus CR-GC-366, nur ohne Kante. Gemessen: 0 von 0 Traces aller 9 Graphen tragen `category` -- die Entfernung nimmt niemandem etwas weg. (4) ReqKind 7 -> 6: `negative` entfaellt. Ein Verbots-REQ ist auch eine Anforderung -- die Implementierung muss Regeln abfragen oder Sicherungen einbauen, das ist `functional`. Der Wert hatte NULL Leser (anders als risk/mitigation -> FM-01..03, pre/postcondition -> UC-05/06, non-functional -> NFR-01) und haette durch die neuen where-Praedikate per AUSLASSUNG erstmals Wirkung bekommen: er stand in keiner der vier Listen und waere nur noch per FCHAIN erfuellbar gewesen. Die sechs verbleibenden Werte partitionieren die where-Listen VOLLSTAENDIG. Migration `negative`: 3 REQs, alle graphcode, Ziel-kind je REQ statt pauschal -- REQ-dashboard-readonly und REQ-readonly-bridge nach `non-functional` (Architektur-Constraint an der Modulgrenze, konsistent mit D2), REQ-no-extraction nach `functional`. Rest-Reject dabei: genau 1 (`FUNC-serve-sse -satisfy-> REQ-readonly-bridge`), aufzuloesen durch Loeschen oder Heben auf die FCHAIN. Prior: 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 = '6.3.0'; // MINOR (CR-SM-262): +RC-06 'external realRef names a declared dependency' (severity warning) und +`CodeFacts.declaredDependencies`. RC-01..03 ueberspringen `external === true` zu Recht — der Pfad eines fremden Pakets ist im Konsumenten-Repo nicht aufloesbar. Die Folge war, dass externe Bindungen NIE geprueft wurden und still verrotteten. Gefunden von Hand am graphcode-Selbstmodell (2026-08-22), nicht von einer Regel: `SCHEMA-metric-vector` trug `realRef.file = packages/se-optimizer/src/metrics.ts`, und `@sigloch/se-optimizer` existiert seit CR-SM-248 nicht mehr (MetricVector liegt in se-engine). Der Pfad ist hier nicht entscheidbar, der PAKETNAME schon: `packages/<name>/…` behauptet ein Workspace-Paket, also muss `@sigloch/<name>` in dependencies oder devDependencies stehen. Andere Pfadformen bleiben UNGEPRUEFT statt geraten. Severity warning, nicht error: der Pfad darf aus ehrlichen Gruenden von der Konvention abweichen (Monorepo-Umbau, verlinkte Arbeitskopie), und ein error wuerde ueber die Delta-Semantik jede weitere Mutation blockieren — eine verrottete Bindung ist ein Vollstaendigkeitssignal, keine Fehlmessung (anders als R-29, wo die Zahl selbst falsch wird). `declaredDependencies` ist OPTIONAL und der fehlende Fall ist SCHWEIGEN, nicht ein Befund — die bewusste Umkehrung des `files`-Vertrags, wo ein fehlender Schluessel laut scheitern muss: ein fehlender Datei-Eintrag heisst 'nachgesehen und nichts gefunden', eine fehlende Dependency-Liste heisst 'gar nicht nachgesehen', und das als 'deklariert nichts' zu lesen wuerde jede externe Bindung des Graphen auf einmal melden. Der Extraktor liegt in graphcode (`src/conformance.ts`, `extractDeclaredDependencies`) — vorher las `extractCodeFacts` die package.json NIE, die urspruengliche Kostenschaetzung des CR war insofern falsch. Gemessen an den drei Selbstmodellen: 0 Befunde (graphcode 9 externe Knoten mit realRef, davon 7 in der Pfadform, alle deklariert; sigloch-modules und graphcodedemo tragen keine externen realRefs). Die Null ist der REPARIERTE Zustand — vor der Reparatur am selben Tag waere es genau 1 gewesen, der reale Drift-Fall. ACHTUNG, Gate-Luecke, hier groesser als sonst: RC-Regeln stehen UEBERHAUPT NICHT im Golden-File. `ALL_RULE_DEFS` (die Quelle der `rules`-Oberflaeche in `grammar-snapshot.ts`) enthaelt nur die Familien, die `evaluateAllRules` faehrt; die sechs RC-Regeln laufen ueber `evaluateConformanceRules` mit CodeFacts und sind damit von `check-grammar-version.mjs` gar nicht erfasst — auch eine ENTFERNUNG wuerde dort nicht auffallen. Der Bump hier ist von Hand; die Luecke selbst ist ein eigener Befund. Prior: MINOR (CR-SM-263): MT-02 ueberspringt ROLLUP-CONTAINER — MODs, deren allozierte FUNCs AUSNAHMSLOS zerlegte Bloecke sind. `lcom4Of` verbindet zwei FUNCs ueber ein geteiltes io/satisfy-ZIEL, und CR-SM-256 hat festgehalten, dass ein Rollup genau die nicht traegt (12 der 13 Bloecke am graphcode-Selbstmodell tragen keine einzige io/satisfy-Ausgangskante). Daraus folgt LCOM4 = Zahl der Bloecke, IMMER: die Regel war dort nicht streng, sondern unerfuellbar — gruen wird sie erst mit der Kante, die CR-SM-256 fuer falsch erklaert hat. Zwei Regeln desselben Katalogs forderten Gegensaetzliches, der gegenlaeufige Fall aus Gate 4. Gemessen an allen drei Selbstmodellen (graphVersion 181): es existiert genau EIN Container, `MOD-repo-root` (5/5 Bloecke) — MT-02 7 -> 6 Befunde, die Differenz ist eine info; sigloch-modules und graphcodedemo tragen keinen. Lockerung, kein heute gruener Graph wird rot, keine Migration. Die MESSUNG bleibt: `moduleMetrics()` liefert `lcom4` unveraendert weiter und markiert den Container mit `rollupContainer` — nur das URTEIL entfaellt, denn die Zahl ist wahr und die Schwelle an dieser Stelle bedeutungslos. EIN geteilter Helper: `decomposedFuncs` ist jetzt exportiert und hat mit MT-02 seinen vierten Nutzer, damit keine zweite Definition von 'Blatt' entsteht (Gate 2). ABGELEHNT im selben Durchlauf, hier festgehalten damit es nicht wiederkommt: die FASSADEN-Ausnahme ueber `MOD.kind` (Option A und B des CR). Fachlich ist der Einwand richtig — fuer eine Registry wie `MOD-mcp-tools` (9 orthogonale Werkzeuge, KEIN Block darunter) ist hoher LCOM4 das gewuenschte Ergebnis, und geteilte Ziele hinter einer API waeren der Verdachtsfall. Aber `kind` ist an 30 von 30 MODs aller drei Selbstmodelle unset: ein Attribut mit Leser (R-27) und ohne Schreiber, und eine Selbstauskunft, die eine Strukturregel abschaltet — genau die Klasse, die CR-SM-257 am selben Tag abgelehnt hat. Der Container ist aus dem Graphen ABLEITBAR, die Fassade ist es nicht (Gate 3); deshalb nur die eine Haelfte. Fuer die Fassade bleibt Option C: die Zahl steht da und wird gelesen. ACHTUNG, Gate-Luecke (wie CR-SM-249 und CR-SM-256): `check-grammar-version.mjs` sieht die Aenderung NICHT — der Snapshot erfasst id/severity/domain, alle drei bleiben gleich. Der Bump hier ist von Hand. Prior: MINOR (CR-SM-255 + CR-SM-256): zwei Grundgesamtheiten verengt, beide Lockerungen — kein heute gruener Graph wird rot, keine Migration, keine Kante aendert sich. CR-SM-255: CR-R04 fragt nur noch CRs mit status open/in-progress (und CRs ganz ohne status — ein fehlendes Attribut darf keine Regel stumm schalten); die Statusmenge ist woertlich die von CR-R03 zwoelf Zeilen darueber. 'Welche FUNC fasst dieser CR an' ist eine PLANUNGSfrage; am geschlossenen CR ist sie Archaeologie (Umfang steht im Commit und im Diff), und wer die Kanten nachtraeglich zieht, raet. Gemessen am graphcode-Selbstmodell (graphVersion 171): 41 meldende CRs, davon 40 done und 1 dropped, KEIN einziger offener — 41 unbearbeitbare Befunde. Nach der Aenderung 0, und die Grundgesamtheit ist nicht leer: die 7 offenen CRs tragen ihre FUNC-Kanten bereits, die Regel ist erfuellt statt gegenstandslos. CR-R01 ('ein CR trackt ueberhaupt etwas') bleibt fuer ALLE CRs gueltig. Regelname 'CR must have FUNC' -> 'Open CR must have FUNC'. CR-SM-256: R-02 und R-31 bekommen dieselbe Grundgesamtheit wie R-30 seit CR-SM-249 und R-20 seit CR-210 — ausschliesslich BLAETTER (FUNC ohne FUNC-Kind). Ein zerlegter FUNC ist ein Rollup: durch einen Blackbox-Block fliesst nichts, und er erfuellt keine REQ, die nicht schon ein Blatt erfuellt; wer die beiden Regeln an ihm befriedigen will, muss eine io-Kante an einem Knoten erfinden, an dem nichts anliegt, oder eine satisfy-Kante, die dieselbe REQ ein zweites Mal beansprucht. CR-GC-375 hatte das fuer R-31 ausdruecklich offengelassen ('ein Block ohne io ist auch als Rollup ein Befund … eine eigene Frage, hier bewusst nicht'); die Messung, die Gate 1 zum Wiederaufmachen verlangt, liegt vor: 12 der 13 Bloecke am graphcode-Selbstmodell melden R-02 UND R-31, keiner aus einem anderen Grund — dieselbe Ursache zweimal gezaehlt, die Klasse CR-SM-235/-242. R-02 22->10, R-31 29->17. R-30 unveraendert. EIN geteilter Helper `decomposedFuncs` fuer alle drei Regeln, damit keine zweite Definition von 'Blatt' entsteht (Gate 2). ABGELEHNT im selben Durchlauf, hier festgehalten damit es nicht wiederkommt (CR-SM-257): R-02/R-30/R-31 sollten zusaetzlich `attributes.external === true` ueberspringen. `external` ist ein REALISIERUNGS-Marker — genau so steht es in ELEMENT_ATTRIBUTES ('exempt from the R-20 realRef-binding requirement') und genau so nutzen es R-27 und RC-01..03. Struktur schuldet der Konsument trotzdem: 5 der 12 externen FUNC am graphcode-Selbstmodell haengen vollstaendig in einer lokalen Wirkkette und sind beidseitig verdrahtet. Entscheidend ist die Reihenschaltung am System-Rand: R-31 erzwingt den Grenz-FLOW, SC-04 erzwingt dessen SCHEMA, und `external` am SCHEMA sagt 'der Vertrag wird drueben veroeffentlicht' (33 von 33 Grenz-FLOWs in graphcode tragen so ein SCHEMA, 12 davon external). Eine Ausnahme in R-31 kappt das erste Glied: ohne geforderten FLOW feuert SC-04 nie, und am externen Interface wird NIE ein Schema-Vertrag verlangt. Die 21 Befunde, die der Vorschlag entfernt haette, sind der Hebel, der genau diese Vertraege entstehen laesst. ACHTUNG, Gate-Luecke (wie CR-SM-249): `check-grammar-version.mjs` sieht beide Aenderungen NICHT — der Snapshot erfasst id/severity/domain, und alle drei bleiben gleich. Der Bump hier ist von Hand. Prior: BREAKING (CR-SM-247): R-28 (Ebenen-Praesenz) entfaellt ersatzlos. Sie schloss das Vacuous-Complete-Loch — mit 0/1 FUNC feuert keine Pro-Element-Regel und jede Vollstaendigkeitsfrage liest 'fertig' —, ist seit CR-GC-366 aber doppelt: die FLOW-Haelfte traegt R-31 (je FUNC statt einmal je Graph) plus IO-01, die SCHEMA-Haelfte SC-04 (je FLOW). Probe (SYS+UC+FCHAIN+3 FUNC, ohne FLOW/SCHEMA): 28 Befunde aus 18 Regeln, ohne R-28 bleiben 17 — das Loch bleibt zu. Gemessen feuerte sie 1x/0x an den Selbstmodellen, dort neben 14x IO-01 und 10x R-31. Dimension und Gate bleiben besetzt (R-30/R-31 sind ebenfalls arch/PDR). Bewusst in Kauf genommen: die SCHEMA-Absenz rutscht von PDR nach CDR, weil SC-04 erst feuert, wenn FLOWs existieren. Als domain:['graph']-Regel trug sie keinen Nenner bei — kein Dimensions-Score verschiebt sich. Prior: MINOR (CR-SM-250): IO-01 prueft ERREICHBARKEIT im io-Teilgraphen der Kette statt Adjazenz jedes Paares. Die Regel forderte einen vollstaendigen Graphen (n·(n−1)/2 Paarungen), obwohl eine Wirkkette eine Sequenz ist — der Zaehler wuchs quadratisch mit der Kettenlaenge, der Nenner-Beitrag linear, und wer sie befriedigen wollte, erfand einen fachlich falschen FLOW oder baute einen Ketten-Bus. Dieselbe Korrektur wie CR-GC-315 fuer R-21, eine Ebene hoeher. Zwei Praezisierungen aus dem Grammar-Review: ein Befund je abgehaengtem Glied statt je Paar (sonst waeren zwei Komponenten der Groesse n/2 wieder n²/4 Befunde, Zaehler > Nenner-Beitrag), und gar nicht verdrahtete Glieder gehoeren R-31 — am sigloch-modules-Selbstmodell waren 6 der 7 IO-01-Elemente zugleich R-31-Elemente, also Doppelzaehlung derselben Ursache. Lockerung, kein heute gruener Graph wird rot. Prior: BREAKING (CR-SM-249): R-15 wird VERSCHAERFT und heisst jetzt 'FCHAIN completeness' — eine Kette braucht Funktionen UNTER sich UND einen UC UEBER sich. Zweites Bein unter derselben rule_id nach dem R-10-Muster (bis zu zwei Meldungen je FCHAIN), bewusst keine neue Regel-ID: eine ID kostet dauerhaft einen Nenner-Anteil, eine Katalogzeile, eine readiness-Zuordnung und einen Golden-File-Eintrag — fuer einen einzigen Befund ueber beide Selbstmodelle (Gate 6 des Grammatik-Reviews). Major, weil ein heute gruener Graph rot werden kann: seit Ontologie 7.0.0 ist `UC -compose-> FCHAIN -compose-> FUNC` der EINZIGE Pfad vom Verhalten zum Zweck, und geprueft war nur sein unteres Bein. Migration: eine Kette ohne UC unterstellen oder loeschen (graphcode: `FCHAIN-doc-export`, 1 Befund). Zweitens R-30: die Grundgesamtheit sind ab jetzt AUSSCHLIESSLICH Blaetter (FUNC ohne FUNC-Kinder), und die Aufwaerts-Vererbung aus CR-GC-366 entfaellt ersatzlos. Ein zerlegter FUNC ist ein Rollup seiner Kinder und DARF gar kein Kettenglied sein — FC-03 ('FCHAIN is flat') verbietet es; die erste Fassung forderte einen Zustand, den eine andere Regel untersagt (am graphcode-Gate quittiert: graphVersion 111, zurueckgenommen in 112). Die Vererbung konnte nur greifen, wenn ein Block Kettenglied ist, und haette sonst ein unverbundenes Blatt stumm geschaltet. R-30 allein waere ein Minor (es meldet weniger: 56 -> 43 am graphcode-Selbstmodell, die 13 Differenz sind exakt die Bloecke). ACHTUNG, Gate-Luecke: `check-grammar-version.mjs` sieht diese Aenderung NICHT — der Snapshot erfasst id/severity/domain, und alle drei bleiben gleich. Eine semantische Verschaerfung ist mit einem Oberflaechen-Abzug nicht greifbar; der Bump hier ist von Hand. Prior: 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).
8
+ export const RULES_VERSION = '9.1.0'; // MINOR (CR-SM-268 Teil 2): +`importCoverage(graph, facts)` — die Abdeckung des Import-Graphen als MESSUNG neben den Regeln, nicht als Befund im Regelstrom. 'X von Y Import-Endpunkten nicht zugeordnet' sagt nichts ueber den Graphen aus; es sagt, wie belastbar die Zahl daneben ist. Defekte gehoeren in den Regelstrom, Messungen auf die Auswertungsflaeche — und die gibt es bereits: CR-GC-398 hat `rules_evaluate` um `skipped` erweitert, mit exakt derselben Begruendung ('an empty skipped is what makes the count interpretable'). Ein eigener info-Befund waere ein ZWEITER Mechanismus fuer dieselbe Aussage gewesen, eine eigene Regel-ID (RC-07) haette dauerhaft Katalogzeile, readiness-Zuordnung und Golden-File-Eintrag gekostet (Gate 6) — fuer eine Zahl, die gar kein Urteil ist. GESCHWISTER von `skipped`, nicht dasselbe: `skipped` heisst 'diese Quelle wurde GAR NICHT ausgewertet' (kein lesbarer repoRoot), hier ist sie ausgewertet worden und ein Teil faellt trotzdem durch; zusammengelegt waeren 'nicht gelaufen' und 'gelaufen, aber blind' wieder ununterscheidbar. Der Anlass ist ein realer Verlust: die Liste der nicht zugeordneten Dateien hing als TEXTANHANG an der Meldung eines ANDEREN Befundes (`unassignedNote`) und verschwand mit ihm — CR-GC-423 hat die drei RC-05-Drift-Befunde von graphcode geschlossen, und mit ihnen war die Liste weg; sie musste in CR-GC-424/425 von Hand rekonstruiert werden. Der Anhang entfaellt jetzt ersatzlos. EIN geteilter Resolver (`buildModResolver`) fuer Regel UND Messung, bewusst herausgezogen statt zweimal geschrieben: eine Abdeckungszahl, die anders aufloest als die Regel, misst etwas anderes als das, was geprueft wird — und das waere schlimmer als keine Zahl. Additiv: `evaluateConformanceRules` behaelt seine Signatur `RuleViolation[]`, kein Aufrufer bricht. Golden-File unberuehrt (die `conformanceRules`-Oberflaeche fuehrt Regeln, und dies ist keine). Gemessen am graphcode-Checkout (2026-08-26): 14 von 73 Import-Endpunkten unzugeordnet. CR-GC-425 nannte 12 von 178 — die Zahlen sind NICHT dieselbe Groesse und duerfen nicht gleichgesetzt werden: hier sind Endpunkte VERSCHIEDENE Dateien (Kanten-Enden dedupliziert), und der Graph steht inzwischen mehrere Versionen weiter. Was den Mechanismus belegt, ist die Deckung im Inhalt: alle fuenf in CR-GC-425 benannten reinen Zod-Vertragsdateien (gve-session-, schema-fingerprint-, target-profile-, lock-owner-contract, test-selection) stehen in der Liste, ebenso die beiden dort als modellierungswuerdig genannten (src/evaluation.ts, src/testreport.ts). Die Angabe ist damit abfragbar statt rekonstruiert. Die graphcode-Haelfte (Feld in `rules_evaluate` neben `skipped`) folgt nach dem Release. Prior: BREAKING (CR-SM-270): `ReadinessScore.score` ist `number | null`, +`coreApplicable`. `null` heisst NIE 0 %, dieselbe Zusage wie bei graph_metrics seit CR-GC-326 ('a value that is not measurable is not zero percent'). Der Anlass war die Gegenrichtung, gemessen an moneyflow (reiner Code-Import: 0 UC, 0 FCHAIN, 0 ACTOR, 1 SYS, 306 FUNC): die Dimension `uc` meldete score 0,997 und ready TRUE. Ein Modell ohne einen einzigen Use Case war zu 99,7 % use-case-bereit. Ursache: von den 15 uc-Regeln haben dreizehn eine leere Grundgesamtheit und SCHWEIGEN mangels Gegenstand, R-17 traegt 1 (SYS) und FC-03 traegt 306 (FUNC) — 99,7 % des Nenners aus EINER Fremdtyp-Regel, und das Schweigen der dreizehn las sich als Erfolg. Der Score zeigte damit in die falsche Richtung: je mehr importierte FUNCs ein Repo ohne Wozu-Ebene hat, desto reifer sah seine uc-Readiness aus — und `dimension_readiness` steuert `graph_next_step`. Die KERNMENGE ist ABGELEITET, nicht deklariert: die Elementtypen, die die MEHRHEIT der Dimensionsregeln prueft (uc->UC, arch->FUNC, ver->TEST, ...). Eine zweite Tabelle neben RULE_TO_DIMENSION waere ein zweiter Speicher derselben Wahrheit und liefe auseinander, sobald jemand nur eine von beiden pflegt — genau der Defekt, den CR-SM-235 hier schon einmal beseitigt hat (RULE_ELEMENT_TYPE, 18 von 71 Regeln fehlten); ein Attribut waere zusaetzlich eine Selbstauskunft (abgelehnt in CR-SM-263). Laesst sich keine Mehrheit ableiten, ist der Kern ALLES was die Dimension prueft — der Waechter greift dann nie, lieber gar nicht als am falschen Ort. KEIN Neuschnitt von `applicable`: wo die Kernmenge da ist, zaehlen die Fremdtyp-Beitraege unveraendert mit, und `applicable === 0` behaelt ausdruecklich seine 0,0 (Akzeptanzkriterium 3 des CR; derselbe Einwand liesse sich dort erheben, aber ein stiller Mitnahmeeffekt waere die schlechtere Wahl als eine eigene Entscheidung). `ready` ist bei `null` immer false. Gemessen: moneyflow uc 0,997/ready -> null; ausserdem neu gefangen moneyflow `cr` (0 CR, Nenner 1229), graphcodedemo `cr` (0 CR, Nenner 219), sirail `ms` (0 MS, Nenner 2). graphcode ist auf allen acht Dimensionen unveraendert messbar — der Regressionsnachweis. Beim Testen gefunden und mitentschieden: ein Graph GANZ ohne TEST-Knoten meldet `ver` jetzt `null` statt einer niedrigen Zahl. Das ist gewollt ('es gibt keine Abnahmen' ist ein anderer Zustand als 'die Abnahme greift nicht'), verlangt aber vom Konsumenten die null-sichere Rangfolge: nicht messbar rankt OBEN, nicht unten (Entscheidung 3 des CR). Ein naiver `a.score - b.score`-Komparator ergibt mit null NaN und sortiert dann gar nicht. Prior: BREAKING (CR-SM-266b): R-18 bekommt ein ZWEITES BEIN — die Kardinalitaets-Obergrenzen. Bewusst dieselbe rule_id nach dem Muster von R-10 und R-15 (CR-SM-249): die Aussage ist dieselbe ('diese Kante ist nach dem Meta-Modell nicht zulaessig'), nur ist die Bedingung eine ueber die MENGE der Kanten eines Knotens statt ueber das Typ-Paar. Eine eigene ID kostete dauerhaft Nenner-Anteil, Katalogzeile, readiness-Zuordnung und Golden-File-Eintrag (Gate 6), und der naechste Schritt des Lesers waere derselbe: Kante weg. EIN Befund je Quellknoten, nicht je ueberzaehliger Kante — bei n Allokationen sind nicht n-1 Kanten falsch, es ist EINE Entscheidung offen, und n-1 Befunde trieben den Zaehler ueber den Nenner-Beitrag (die Fehlmessung aus CR-SM-242). Die Zaehlung ist pattern-scharf (Zieltyp muss passen), sonst meldete eine ohnehin pattern-ungueltige Kante zweimal. Migration, gemessen an den 9 aktiven Familie-Graphen: 9 Faelle, ALLE in sirail (1x FUNC mit zwei allocate, 8x FLOW mit zwei SCHEMA). Alle anderen acht Graphen sind bereits konform — die Obergrenze kostet sie nichts. Prior: BREAKING (CR-SM-266a): drei Regeln ziehen mit der Matrix nach, keine neue Regel-ID. UC-02 prueft ERREICHBARKEIT statt Adjazenz: sie suchte einen io-Ziel-Treffer direkt am UC oder an dessen compose-Kind -- also genau die mit D1/D4 entfallenen Kanten -- und waere damit UNERFUELLBAR geworden, haette also einen Zustand verlangt, den R-18 im selben Lauf als ungueltige Kante ablehnt. Das ist der gegenlaeufige Fall aus Gate 4 des Grammatik-Reviews und fuer den Nutzer eine Sackgasse statt eines Fehlers; genau deshalb ist der Umbau Teil DIESES Bumps und nicht eines Folge-CR. Neu: ACTOR -io-> FLOW -io-> FUNC, FUNC in einer FCHAIN des UC, in BEIDE Richtungen -- ein UC ist auch dann angebunden, wenn die Kette dem Actor etwas LIEFERT; die alte Fassung sah nur die Trigger-Richtung, weil `ACTOR -io-> UC` nur so herum existierte. Die CR-SM-261-Optimierung bleibt erhalten (zwei Mengen vorab, dann Mitgliedschaft je UC, alles ueber den Index aus CR-SM-264; kein Scan je UC), und die aeussere Schleife bleibt `elementsOfType('UC')`, also ist die Befundreihenfolge weiter die Graph-Reihenfolge (Gate 5). R-10 bietet UC nicht mehr als FLOW-Konsument an -- der fix_hint haette nach D4 in eine R-18-Ablehnung geschickt. R-08/R-18 verlieren den `category !== 'audit'`-Filter (das Attribut existiert nicht mehr, s. ONTOLOGY_VERSION). BQ-07 'Complete' bekommt einen DRITTEN Grund: `kinds` nicht deklariert (REQ-X06). Bewusst KEINE neue Regel-ID -- Gate 6: eine ID kostet dauerhaft einen Nenner-Anteil, eine Katalogzeile, eine readiness-Zuordnung und einen Golden-File-Eintrag; BQ-07 sammelt bereits mehrere Unvollstaendigkeits-Gruende in EINEN Befund, und ein REQ ohne Klassifikation ist genau das. Severity-Trennung wie CR-SM-262 fuer RC-06: das blosse FEHLEN ist ein Vollstaendigkeitssignal (warning, hier), eine satisfy-Kante auf ein REQ ohne kinds ist eine FEHLMESSUNG (error, via R-18/where). Migrationsbestand kinds: 420 REQs familienweit -- kadjar 305 (Doc-Import), sirail 61, graphcode 23, siconizer 17, sigloch-modules 10, Rest <=3; an den beiden Selbstmodellen 33. Delta-Semantik: unberuehrter Bestand blockiert keine Mutation. Prior: MINOR (CR-SM-262): +RC-06 'external realRef names a declared dependency' (severity warning) und +`CodeFacts.declaredDependencies`. RC-01..03 ueberspringen `external === true` zu Recht — der Pfad eines fremden Pakets ist im Konsumenten-Repo nicht aufloesbar. Die Folge war, dass externe Bindungen NIE geprueft wurden und still verrotteten. Gefunden von Hand am graphcode-Selbstmodell (2026-08-22), nicht von einer Regel: `SCHEMA-metric-vector` trug `realRef.file = packages/se-optimizer/src/metrics.ts`, und `@sigloch/se-optimizer` existiert seit CR-SM-248 nicht mehr (MetricVector liegt in se-engine). Der Pfad ist hier nicht entscheidbar, der PAKETNAME schon: `packages/<name>/…` behauptet ein Workspace-Paket, also muss `@sigloch/<name>` in dependencies oder devDependencies stehen. Andere Pfadformen bleiben UNGEPRUEFT statt geraten. Severity warning, nicht error: der Pfad darf aus ehrlichen Gruenden von der Konvention abweichen (Monorepo-Umbau, verlinkte Arbeitskopie), und ein error wuerde ueber die Delta-Semantik jede weitere Mutation blockieren — eine verrottete Bindung ist ein Vollstaendigkeitssignal, keine Fehlmessung (anders als R-29, wo die Zahl selbst falsch wird). `declaredDependencies` ist OPTIONAL und der fehlende Fall ist SCHWEIGEN, nicht ein Befund — die bewusste Umkehrung des `files`-Vertrags, wo ein fehlender Schluessel laut scheitern muss: ein fehlender Datei-Eintrag heisst 'nachgesehen und nichts gefunden', eine fehlende Dependency-Liste heisst 'gar nicht nachgesehen', und das als 'deklariert nichts' zu lesen wuerde jede externe Bindung des Graphen auf einmal melden. Der Extraktor liegt in graphcode (`src/conformance.ts`, `extractDeclaredDependencies`) — vorher las `extractCodeFacts` die package.json NIE, die urspruengliche Kostenschaetzung des CR war insofern falsch. Gemessen an den drei Selbstmodellen: 0 Befunde (graphcode 9 externe Knoten mit realRef, davon 7 in der Pfadform, alle deklariert; sigloch-modules und graphcodedemo tragen keine externen realRefs). Die Null ist der REPARIERTE Zustand — vor der Reparatur am selben Tag waere es genau 1 gewesen, der reale Drift-Fall. ACHTUNG, Gate-Luecke, hier groesser als sonst: RC-Regeln stehen UEBERHAUPT NICHT im Golden-File. `ALL_RULE_DEFS` (die Quelle der `rules`-Oberflaeche in `grammar-snapshot.ts`) enthaelt nur die Familien, die `evaluateAllRules` faehrt; die sechs RC-Regeln laufen ueber `evaluateConformanceRules` mit CodeFacts und sind damit von `check-grammar-version.mjs` gar nicht erfasst — auch eine ENTFERNUNG wuerde dort nicht auffallen. Der Bump hier ist von Hand; die Luecke selbst ist ein eigener Befund. Prior: MINOR (CR-SM-263): MT-02 ueberspringt ROLLUP-CONTAINER — MODs, deren allozierte FUNCs AUSNAHMSLOS zerlegte Bloecke sind. `lcom4Of` verbindet zwei FUNCs ueber ein geteiltes io/satisfy-ZIEL, und CR-SM-256 hat festgehalten, dass ein Rollup genau die nicht traegt (12 der 13 Bloecke am graphcode-Selbstmodell tragen keine einzige io/satisfy-Ausgangskante). Daraus folgt LCOM4 = Zahl der Bloecke, IMMER: die Regel war dort nicht streng, sondern unerfuellbar — gruen wird sie erst mit der Kante, die CR-SM-256 fuer falsch erklaert hat. Zwei Regeln desselben Katalogs forderten Gegensaetzliches, der gegenlaeufige Fall aus Gate 4. Gemessen an allen drei Selbstmodellen (graphVersion 181): es existiert genau EIN Container, `MOD-repo-root` (5/5 Bloecke) — MT-02 7 -> 6 Befunde, die Differenz ist eine info; sigloch-modules und graphcodedemo tragen keinen. Lockerung, kein heute gruener Graph wird rot, keine Migration. Die MESSUNG bleibt: `moduleMetrics()` liefert `lcom4` unveraendert weiter und markiert den Container mit `rollupContainer` — nur das URTEIL entfaellt, denn die Zahl ist wahr und die Schwelle an dieser Stelle bedeutungslos. EIN geteilter Helper: `decomposedFuncs` ist jetzt exportiert und hat mit MT-02 seinen vierten Nutzer, damit keine zweite Definition von 'Blatt' entsteht (Gate 2). ABGELEHNT im selben Durchlauf, hier festgehalten damit es nicht wiederkommt: die FASSADEN-Ausnahme ueber `MOD.kind` (Option A und B des CR). Fachlich ist der Einwand richtig — fuer eine Registry wie `MOD-mcp-tools` (9 orthogonale Werkzeuge, KEIN Block darunter) ist hoher LCOM4 das gewuenschte Ergebnis, und geteilte Ziele hinter einer API waeren der Verdachtsfall. Aber `kind` ist an 30 von 30 MODs aller drei Selbstmodelle unset: ein Attribut mit Leser (R-27) und ohne Schreiber, und eine Selbstauskunft, die eine Strukturregel abschaltet — genau die Klasse, die CR-SM-257 am selben Tag abgelehnt hat. Der Container ist aus dem Graphen ABLEITBAR, die Fassade ist es nicht (Gate 3); deshalb nur die eine Haelfte. Fuer die Fassade bleibt Option C: die Zahl steht da und wird gelesen. ACHTUNG, Gate-Luecke (wie CR-SM-249 und CR-SM-256): `check-grammar-version.mjs` sieht die Aenderung NICHT — der Snapshot erfasst id/severity/domain, alle drei bleiben gleich. Der Bump hier ist von Hand. Prior: MINOR (CR-SM-255 + CR-SM-256): zwei Grundgesamtheiten verengt, beide Lockerungen — kein heute gruener Graph wird rot, keine Migration, keine Kante aendert sich. CR-SM-255: CR-R04 fragt nur noch CRs mit status open/in-progress (und CRs ganz ohne status — ein fehlendes Attribut darf keine Regel stumm schalten); die Statusmenge ist woertlich die von CR-R03 zwoelf Zeilen darueber. 'Welche FUNC fasst dieser CR an' ist eine PLANUNGSfrage; am geschlossenen CR ist sie Archaeologie (Umfang steht im Commit und im Diff), und wer die Kanten nachtraeglich zieht, raet. Gemessen am graphcode-Selbstmodell (graphVersion 171): 41 meldende CRs, davon 40 done und 1 dropped, KEIN einziger offener — 41 unbearbeitbare Befunde. Nach der Aenderung 0, und die Grundgesamtheit ist nicht leer: die 7 offenen CRs tragen ihre FUNC-Kanten bereits, die Regel ist erfuellt statt gegenstandslos. CR-R01 ('ein CR trackt ueberhaupt etwas') bleibt fuer ALLE CRs gueltig. Regelname 'CR must have FUNC' -> 'Open CR must have FUNC'. CR-SM-256: R-02 und R-31 bekommen dieselbe Grundgesamtheit wie R-30 seit CR-SM-249 und R-20 seit CR-210 — ausschliesslich BLAETTER (FUNC ohne FUNC-Kind). Ein zerlegter FUNC ist ein Rollup: durch einen Blackbox-Block fliesst nichts, und er erfuellt keine REQ, die nicht schon ein Blatt erfuellt; wer die beiden Regeln an ihm befriedigen will, muss eine io-Kante an einem Knoten erfinden, an dem nichts anliegt, oder eine satisfy-Kante, die dieselbe REQ ein zweites Mal beansprucht. CR-GC-375 hatte das fuer R-31 ausdruecklich offengelassen ('ein Block ohne io ist auch als Rollup ein Befund … eine eigene Frage, hier bewusst nicht'); die Messung, die Gate 1 zum Wiederaufmachen verlangt, liegt vor: 12 der 13 Bloecke am graphcode-Selbstmodell melden R-02 UND R-31, keiner aus einem anderen Grund — dieselbe Ursache zweimal gezaehlt, die Klasse CR-SM-235/-242. R-02 22->10, R-31 29->17. R-30 unveraendert. EIN geteilter Helper `decomposedFuncs` fuer alle drei Regeln, damit keine zweite Definition von 'Blatt' entsteht (Gate 2). ABGELEHNT im selben Durchlauf, hier festgehalten damit es nicht wiederkommt (CR-SM-257): R-02/R-30/R-31 sollten zusaetzlich `attributes.external === true` ueberspringen. `external` ist ein REALISIERUNGS-Marker — genau so steht es in ELEMENT_ATTRIBUTES ('exempt from the R-20 realRef-binding requirement') und genau so nutzen es R-27 und RC-01..03. Struktur schuldet der Konsument trotzdem: 5 der 12 externen FUNC am graphcode-Selbstmodell haengen vollstaendig in einer lokalen Wirkkette und sind beidseitig verdrahtet. Entscheidend ist die Reihenschaltung am System-Rand: R-31 erzwingt den Grenz-FLOW, SC-04 erzwingt dessen SCHEMA, und `external` am SCHEMA sagt 'der Vertrag wird drueben veroeffentlicht' (33 von 33 Grenz-FLOWs in graphcode tragen so ein SCHEMA, 12 davon external). Eine Ausnahme in R-31 kappt das erste Glied: ohne geforderten FLOW feuert SC-04 nie, und am externen Interface wird NIE ein Schema-Vertrag verlangt. Die 21 Befunde, die der Vorschlag entfernt haette, sind der Hebel, der genau diese Vertraege entstehen laesst. ACHTUNG, Gate-Luecke (wie CR-SM-249): `check-grammar-version.mjs` sieht beide Aenderungen NICHT — der Snapshot erfasst id/severity/domain, und alle drei bleiben gleich. Der Bump hier ist von Hand. Prior: BREAKING (CR-SM-247): R-28 (Ebenen-Praesenz) entfaellt ersatzlos. Sie schloss das Vacuous-Complete-Loch — mit 0/1 FUNC feuert keine Pro-Element-Regel und jede Vollstaendigkeitsfrage liest 'fertig' —, ist seit CR-GC-366 aber doppelt: die FLOW-Haelfte traegt R-31 (je FUNC statt einmal je Graph) plus IO-01, die SCHEMA-Haelfte SC-04 (je FLOW). Probe (SYS+UC+FCHAIN+3 FUNC, ohne FLOW/SCHEMA): 28 Befunde aus 18 Regeln, ohne R-28 bleiben 17 — das Loch bleibt zu. Gemessen feuerte sie 1x/0x an den Selbstmodellen, dort neben 14x IO-01 und 10x R-31. Dimension und Gate bleiben besetzt (R-30/R-31 sind ebenfalls arch/PDR). Bewusst in Kauf genommen: die SCHEMA-Absenz rutscht von PDR nach CDR, weil SC-04 erst feuert, wenn FLOWs existieren. Als domain:['graph']-Regel trug sie keinen Nenner bei — kein Dimensions-Score verschiebt sich. Prior: MINOR (CR-SM-250): IO-01 prueft ERREICHBARKEIT im io-Teilgraphen der Kette statt Adjazenz jedes Paares. Die Regel forderte einen vollstaendigen Graphen (n·(n−1)/2 Paarungen), obwohl eine Wirkkette eine Sequenz ist — der Zaehler wuchs quadratisch mit der Kettenlaenge, der Nenner-Beitrag linear, und wer sie befriedigen wollte, erfand einen fachlich falschen FLOW oder baute einen Ketten-Bus. Dieselbe Korrektur wie CR-GC-315 fuer R-21, eine Ebene hoeher. Zwei Praezisierungen aus dem Grammar-Review: ein Befund je abgehaengtem Glied statt je Paar (sonst waeren zwei Komponenten der Groesse n/2 wieder n²/4 Befunde, Zaehler > Nenner-Beitrag), und gar nicht verdrahtete Glieder gehoeren R-31 — am sigloch-modules-Selbstmodell waren 6 der 7 IO-01-Elemente zugleich R-31-Elemente, also Doppelzaehlung derselben Ursache. Lockerung, kein heute gruener Graph wird rot. Prior: BREAKING (CR-SM-249): R-15 wird VERSCHAERFT und heisst jetzt 'FCHAIN completeness' — eine Kette braucht Funktionen UNTER sich UND einen UC UEBER sich. Zweites Bein unter derselben rule_id nach dem R-10-Muster (bis zu zwei Meldungen je FCHAIN), bewusst keine neue Regel-ID: eine ID kostet dauerhaft einen Nenner-Anteil, eine Katalogzeile, eine readiness-Zuordnung und einen Golden-File-Eintrag — fuer einen einzigen Befund ueber beide Selbstmodelle (Gate 6 des Grammatik-Reviews). Major, weil ein heute gruener Graph rot werden kann: seit Ontologie 7.0.0 ist `UC -compose-> FCHAIN -compose-> FUNC` der EINZIGE Pfad vom Verhalten zum Zweck, und geprueft war nur sein unteres Bein. Migration: eine Kette ohne UC unterstellen oder loeschen (graphcode: `FCHAIN-doc-export`, 1 Befund). Zweitens R-30: die Grundgesamtheit sind ab jetzt AUSSCHLIESSLICH Blaetter (FUNC ohne FUNC-Kinder), und die Aufwaerts-Vererbung aus CR-GC-366 entfaellt ersatzlos. Ein zerlegter FUNC ist ein Rollup seiner Kinder und DARF gar kein Kettenglied sein — FC-03 ('FCHAIN is flat') verbietet es; die erste Fassung forderte einen Zustand, den eine andere Regel untersagt (am graphcode-Gate quittiert: graphVersion 111, zurueckgenommen in 112). Die Vererbung konnte nur greifen, wenn ein Block Kettenglied ist, und haette sonst ein unverbundenes Blatt stumm geschaltet. R-30 allein waere ein Minor (es meldet weniger: 56 -> 43 am graphcode-Selbstmodell, die 13 Differenz sind exakt die Bloecke). ACHTUNG, Gate-Luecke: `check-grammar-version.mjs` sieht diese Aenderung NICHT — der Snapshot erfasst id/severity/domain, und alle drei bleiben gleich. Eine semantische Verschaerfung ist mit einem Oberflaechen-Abzug nicht greifbar; der Bump hier ist von Hand. Prior: 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
- export const META_MODEL_VERSION = '2.0.0'; // BREAKING, zwei Anlaesse in einer Zahl. (1) Nachtrag zu CR-GC-366: dort entfiel das Trace-Pattern `FUNC -satisfy-> UC`, gebumpt wurde aber nur ONTOLOGY_VERSION — nach der in CR-SM-244 festgeschriebenen Zustaendigkeit (meta-model.ts + format-e-Parser -> META_MODEL_VERSION) fehlte der Bump hier. Eine Entfernung ist major; dass kein Konsument semver-maessig auf diese Konstante verzweigt (schema-guard.ts keyt bewusst auf einen DDL-Fingerprint), macht sie nicht kleiner, nur folgenlos fuer die Bump-Kette. (2) CR-SM-251: `hydrateAttrValue` hydriert typ-geleitet ueber ELEMENT_ATTRIBUTES — boolean/number-Attribute kamen ueber BEIDE Format-E-Pfade als String an, womit die elf `=== true`-Vergleiche in den Regeln jede Ausnahme (concept/external/safety_relevant/spike) ins Leere laufen liessen und ein Export-Reimport bestehende Ausnahmen still entwertete. Verhaltensaenderung am Parser, also hier verbucht. Prior: -REQ→MOD allocate pattern removed (CR-228 A); +FUNC→FUNC compose (blackbox function decomposition)
10
+ export const META_MODEL_VERSION = '4.0.0'; // BREAKING (CR-SM-266b): `cardinality` wird zum ersten Mal DURCHGESETZT statt nur angezeigt. Die Angabe stand seit Jahren an der Matrix (`FCHAIN -compose-> FUNC [1..*]`) und hatte genau zwei Leser, beide in graphcode und beide nur zum Rendern (executor-prompt, tools/report) — ein Vertrag ohne Durchsetzung. Zwei Kanten bekommen eine echte Obergrenze, beide BESITZ-Aussagen: `FUNC -allocate-> MOD [0..1]` und `FLOW -relation-> SCHEMA [0..1]`. Zwei Allokationen sind kein staerkeres Modell, sondern eine Frage ohne Antwort ('in welchem Modul liegt der Code?'); zwei SCHEMA an einem FLOW heben genau die Typgarantie auf, fuer die der GETEILTE SCHEMA-Knoten existiert — zwei Vertraege koennen divergieren, einer kann es nicht. Bewusst `0..1` und nicht `1..1`: die UNTERE Grenze haben R-22 (FUNC muss alloziert sein) und SC-04 (FLOW hat SCHEMA) laengst, und sie hier zu wiederholen waere die Doppelzaehlung aus Gate 4 — eine Ursache, zwei Befunde, ein Nenner doppelt belastet. Beide Haelften zusammen ergeben das `[1..1]` aus dem CR. Deshalb bleibt SC-04 vorerst bestehen statt 'ersatzlos zu entfallen': das Entfallen setzt die 54 FLOWs ohne SCHEMA voraus, und deren Migration haengt am selben Fenster wie moneyflow/GVE (266c). +`maxOccurs`, +`BOUNDED_PATTERNS`. Neue Kardinalitaeten stehen in der Golden-File-Oberflaeche, `check:grammar` erzwingt den Bump. Prior: BREAKING (CR-SM-266a): Pattern-Matrix 34 -> 29 und ein neuer Bedingungstyp. FUENF Patterns entfallen, jedes nach dem 366-Kriterium 'zweiter Weg neben dem einzig tragenden Pfad': D1 `ACTOR -io-> UC` (tragend: ACTOR -io-> FLOW -io-> FUNC in der FCHAIN; die Direktkante war die TYPLOSE Abkuerzung -- ein Actor-Beitrag ohne FLOW traegt kein SCHEMA. 70 Kanten ueber 6 Graphen, der groesste Migrationsposten). D2 `MOD -io-> MOD` (Modul-Kopplung ist ABLEITBAR aus FUNC-allocate-MOD x FUNC-io-FLOW-io-FUNC; eine behauptete Kante driftet, eine berechnete nicht. 12 Kanten, alle graphcodedemo). D3 `MS -compose-> MS` (Schachtelung und Abfolge sind am Meilenstein nicht unterscheidbar; MS-01 zaehlt Scope seit CR-SM-245 ausschliesslich ueber `CR -relation-> MS`. 0 Kanten familienweit -- migrationsfrei). D4 `FLOW -io-> UC` (Zweitweg neben FLOW -io-> FUNC; die UC-Grenze ERGIBT SICH aus den FLOWs der Kettenglieder, die Direktkante umgeht die Kettenzuordnung und damit IO-01 und R-21. 3 Kanten, alle graph-view-edit). D5 `SESSION -produces-> *` samt Typen, s. ONTOLOGY_VERSION. NEU: `TracePattern.where` -- ein Attribut-Praedikat am Pattern (REQ-X03). Bis hier entschied ausschliesslich das TYP-PAAR, womit ein MOD oder SYS ein FUNKTIONALES REQ 'erfuellen' konnte, ohne dass je eine Wirkkette es trug: derselbe Zweitweg wie CR-GC-366, eine Ebene hoeher, und er umging dieselben Pruefungen (IO-01/R-21 pruefen nur INNERHALB einer Kette). Jetzt: FUNC-satisfy-REQ nur auf functional/precondition/postcondition, MOD- und SYS-satisfy-REQ nur auf non-functional/risk/mitigation, FCHAIN-satisfy-REQ bewusst auf ALLE (sie IST die Wirkkette, an ihr haengen IO-01 und R-21 -- sie ist nie die Abkuerzung). TEILMENGEN-Semantik, nicht Schnittmenge: JEDER deklarierte Kind muss erlaubt sein. Die schwaechere Lesart waere das Loch gewesen, das der Abschnitt schliesst -- ein funktionales REQ zusaetzlich als non-functional zu deklarieren haette gereicht, damit ein MOD es wieder erfuellen darf, und der Zweitweg waere ueber ein Attribut zurueck. Der LEERE Fall lehnt AB: ohne Deklaration ist nicht entscheidbar, ob die Kante zulaessig ist, und 'unentscheidbar' darf nicht 'erlaubt' heissen, sonst wird das WEGLASSEN des Attributs zum Umgehungsweg (die durchsetzende Haelfte von REQ-X06; die meldende haengt an BQ-07, s. RULES_VERSION). Format-E-Parser mit im Bump (Zustaendigkeit nach CR-SM-244): +`resolveKinds`-Option, weil das Typ-Paar nicht mehr reicht; `kinds` wird zuerst aus DIESEM Text gelesen (`@kinds` steht in `op.attributes`, gehoben wird es erst vom Konsumenten), damit eine Mutation, die REQ und satisfy-Kante in EINEM Block anlegt, die eigenen Kinds sieht. Fehlt der Resolver fuer einen BESTEHENDEN Knoten, schlaegt die Kante fehl statt geraten zu werden -- dieselbe Entscheidung wie bei `resolveType`. Ausserdem: `VALID_TRACE_TYPES` ist jetzt aus `TraceType.options` ABGELEITET statt abgeschrieben -- die Kopie trug `produces` noch, als das Enum es schon verloren hatte. Migration D1/D2/D4 je Bestandskante: FLOW einfuehren bzw. auf das Kettenglied umhaengen, sonst loeschen. D3/D5: keine. Prior: BREAKING, zwei Anlaesse in einer Zahl. (1) Nachtrag zu CR-GC-366: dort entfiel das Trace-Pattern `FUNC -satisfy-> UC`, gebumpt wurde aber nur ONTOLOGY_VERSION — nach der in CR-SM-244 festgeschriebenen Zustaendigkeit (meta-model.ts + format-e-Parser -> META_MODEL_VERSION) fehlte der Bump hier. Eine Entfernung ist major; dass kein Konsument semver-maessig auf diese Konstante verzweigt (schema-guard.ts keyt bewusst auf einen DDL-Fingerprint), macht sie nicht kleiner, nur folgenlos fuer die Bump-Kette. (2) CR-SM-251: `hydrateAttrValue` hydriert typ-geleitet ueber ELEMENT_ATTRIBUTES — boolean/number-Attribute kamen ueber BEIDE Format-E-Pfade als String an, womit die elf `=== true`-Vergleiche in den Regeln jede Ausnahme (concept/external/safety_relevant/spike) ins Leere laufen liessen und ein Export-Reimport bestehende Ausnahmen still entwertete. Verhaltensaenderung am Parser, also hier verbucht. Prior: -REQ→MOD allocate pattern removed (CR-228 A); +FUNC→FUNC compose (blackbox function decomposition)
11
11
  export * from './ontology.js';
12
12
  export * from './rules.js';
13
13
  export * from './conformance-rules.js';
@@ -3,24 +3,69 @@
3
3
  * Single source of truth for which ElementType pairs are valid for which TraceType.
4
4
  * @sigloch/contracts/se
5
5
  */
6
- import type { ElementType, TraceType } from './ontology.js';
6
+ import type { ElementType, ReqKind, TraceType } from './ontology.js';
7
+ /**
8
+ * Ein Attribut-Praedikat am Pattern (CR-SM-266 B, REQ-X03).
9
+ *
10
+ * Bis hierher entschied AUSSCHLIESSLICH das Typ-Paar ueber die Gueltigkeit einer Kante. Das
11
+ * liess vier satisfy-Patterns ununterscheidbar nebeneinander stehen: ein FUNKTIONALES REQ
12
+ * konnte von einem MOD oder SYS "erfuellt" werden, ohne dass je eine Wirkkette es trug —
13
+ * strukturell derselbe billige Zweitweg, den CR-GC-366 fuer `FUNC -satisfy-> UC` entfernt hat,
14
+ * und er umging dieselben Pruefungen (IO-01, R-21 sehen nur INNERHALB einer Kette).
15
+ *
16
+ * Bewusst eng gehalten: `kinds` ist das einzige mehrwertige Enum-Feld am Knoten, und ein
17
+ * Praedikat ueber freien Text oder abgeleitete Werte waere nicht deterministisch
18
+ * entscheidbar. Kein generischer Ausdruck, keine Funktion — eine Liste erlaubter Enum-Werte.
19
+ */
20
+ export interface AttributePredicate {
21
+ /** An welchem Kantenende das Praedikat haengt. */
22
+ on: 'source' | 'target';
23
+ /** Das geprueft Feld — heute nur `kinds` (REQ). */
24
+ field: 'kinds';
25
+ /** Die erlaubten Werte. */
26
+ allowed: readonly ReqKind[];
27
+ }
7
28
  export interface TracePattern {
8
29
  source: ElementType | '*';
9
30
  target: ElementType | '*';
10
31
  type: TraceType;
11
- category?: 'modeling' | 'audit';
12
32
  label?: string;
13
- cardinality?: '1' | '1..*' | '0..*';
33
+ cardinality?: '1' | '0..1' | '1..*' | '0..*';
34
+ /** Zusatzbedingung am Quell-/Zielknoten. Fehlt sie, entscheidet allein das Typ-Paar. */
35
+ where?: AttributePredicate;
14
36
  description: string;
15
37
  }
16
38
  export declare const TRACE_PATTERNS: TracePattern[];
17
39
  /**
18
- * Check if a trace matches a valid pattern in the meta-model.
19
- * Uses element types (not IDs) for validation.
40
+ * Die Kante, wie `isValidTrace` sie sieht: die beiden Typen, der Kantentyp — und, sobald ein
41
+ * Pattern ein `where` traegt, die deklarierten Kinds des betroffenen Endes.
42
+ *
43
+ * `sourceKinds`/`targetKinds` sind OPTIONAL, und ihr Fehlen bedeutet "nicht deklariert", nicht
44
+ * "beliebig": ein Pattern mit `where` lehnt dann ab (s. `satisfiesPredicate`). Wer nur das
45
+ * Typ-Paar kennt — die Vorschlags-Pfade in fix-templates/rule-apply, die eine Kante erst noch
46
+ * bauen — bekommt fuer die where-Patterns korrekterweise `false` und schlaegt sie nicht vor.
20
47
  */
21
- export declare function isValidTrace(trace: {
48
+ export interface TraceShape {
22
49
  source: ElementType;
23
50
  target: ElementType;
24
51
  type: TraceType;
25
52
  label?: string;
26
- }, patterns?: TracePattern[]): boolean;
53
+ sourceKinds?: readonly ReqKind[];
54
+ targetKinds?: readonly ReqKind[];
55
+ }
56
+ /**
57
+ * Die Obergrenze eines Kardinalitaets-Ausdrucks — `Infinity`, wo keine gilt.
58
+ *
59
+ * Getrennt von `isValidTrace`, weil es eine andere ART von Bedingung ist: `isValidTrace`
60
+ * urteilt ueber EINE Kante und braucht nur deren Enden, eine Kardinalitaet urteilt ueber die
61
+ * MENGE der Kanten eines Knotens. Der urspruengliche CR-Entwurf wollte beides in
62
+ * `isValidTrace` erledigen; das geht nicht, und die Korrektur steht im Impact-Audit.
63
+ */
64
+ export declare function maxOccurs(cardinality: TracePattern['cardinality']): number;
65
+ /** Die Patterns mit einer echten Obergrenze — die Arbeitsliste des Kardinalitaets-Beins. */
66
+ export declare const BOUNDED_PATTERNS: readonly TracePattern[];
67
+ /**
68
+ * Check if a trace matches a valid pattern in the meta-model.
69
+ * Uses element types (not IDs) plus — where a pattern declares one — an attribute predicate.
70
+ */
71
+ export declare function isValidTrace(trace: TraceShape, patterns?: TracePattern[]): boolean;