@sigloch/contracts 10.5.0 → 10.6.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.
@@ -1,43 +1,15 @@
1
+ import type { AttributeSpec } from './ontology.js';
2
+ /** Extract a ```format-e block from LLM output. Returns null if not found. */
3
+ export declare function extractFormatE(llmOutput: string): string | null;
1
4
  /**
2
- * Format E Parser Graph mutations from compact text format.
3
- * SE-relevant subset: nodes (+/-/~) and edges (+/-), no chat-canvas or views.
4
- *
5
- * CR-SM-216 (Format-E v2): the element type comes from the `### <TYPE>` section a node
6
- * is declared under, never from the spelling of its id. The old id-derived typing made
7
- * every consumer with a different id convention fail *silently* — aimpro CR-230 lost a
8
- * whole graph that way (`TYPE-slug` ids rejected, result empty, no error).
5
+ * CR-SM-251: der deklarierte Typ des Attributs entscheidet, nicht die Schreibweise des Wertes.
9
6
  *
10
- * @sigloch/contracts/se
7
+ * `ELEMENT_ATTRIBUTES` sagt fuer 14 Attribute (9 boolean, 5 number), welchen Typ sie tragen.
8
+ * Wer stattdessen raet (`raw === 'true'`, `/^\d+$/`), verschiebt den Defekt nur: ein
9
+ * Freitext-Attribut mit dem Wert "true" oder "8" kippt dann still den Typ. Unbekannte Keys —
10
+ * jeder Graph darf eigene tragen — fallen sauber auf String zurueck.
11
11
  */
12
- import { ElementType, TraceType } from './ontology.js';
13
- import type { OntologyGraph, AttributeSpec } from './ontology.js';
14
- export interface FormatEOperation {
15
- type: 'add_node' | 'remove_node' | 'update_node' | 'add_edge' | 'remove_edge' | 'strict_add_node' | 'strict_add_edge';
16
- semanticId: string;
17
- /**
18
- * CR-SM-216: the element type, taken from the node's `### <TYPE>` section. Set on
19
- * every node-creating operation — consumers must read it instead of re-deriving a
20
- * type from the id.
21
- */
22
- elementType?: ElementType;
23
- description?: string;
24
- /**
25
- * CR-147: Parsed @key value attributes from lines below the node entry.
26
- * Values are strings, EXCEPT JSON object/array literals which are hydrated
27
- * (BOK-CR-026) — object- and array-valued bindings like `realRef`/`testRefs` must reach
28
- * `attributes` as objects or R-26/R-19 reject them as invalid.
29
- */
30
- attributes?: Record<string, unknown>;
31
- sourceId?: string;
32
- targetId?: string;
33
- traceType?: TraceType;
34
- }
35
- export interface FormatEDiff {
36
- operations: FormatEOperation[];
37
- errors: string[];
38
- }
39
- /** Extract a ```format-e block from LLM output. Returns null if not found. */
40
- export declare function extractFormatE(llmOutput: string): string | null;
12
+ export declare function attributeTypeOf(elementType: string | undefined, key: string): AttributeSpec['type'] | undefined;
41
13
  /**
42
14
  * BOK-CR-026: hydrate a JSON object/array attribute value. The ontology's bindings
43
15
  * (`realRef {file,symbol?,lang?}`, `testRefs [{file,tool,…}]`) are objects/arrays; kept as raw
@@ -50,33 +22,4 @@ export declare function extractFormatE(llmOutput: string): string | null;
50
22
  * package over. Two hydration rules would drift; there is one, and it lives here with the
51
23
  * schemas it feeds.
52
24
  */
53
- /**
54
- * CR-SM-251: der deklarierte Typ des Attributs entscheidet, nicht die Schreibweise des Wertes.
55
- *
56
- * `ELEMENT_ATTRIBUTES` sagt fuer 14 Attribute (9 boolean, 5 number), welchen Typ sie tragen.
57
- * Wer stattdessen raet (`raw === 'true'`, `/^\d+$/`), verschiebt den Defekt nur: ein
58
- * Freitext-Attribut mit dem Wert "true" oder "8" kippt dann still den Typ. Unbekannte Keys —
59
- * jeder Graph darf eigene tragen — fallen sauber auf String zurueck.
60
- */
61
- export declare function attributeTypeOf(elementType: string | undefined, key: string): AttributeSpec['type'] | undefined;
62
25
  export declare function hydrateAttrValue(raw: string, declaredType?: AttributeSpec['type']): unknown;
63
- export interface ParseFormatEOptions {
64
- /**
65
- * CR-SM-216: resolve the type of a uid that this text does not declare. A mutation
66
- * diff adding edges between existing nodes carries no `## Nodes` block, so the
67
- * caller binds this to its store. Without it such a diff is an error, never a
68
- * silent skip.
69
- */
70
- resolveType?: (uid: string) => ElementType | undefined;
71
- }
72
- /** Parse a Format E text block into validated operations. */
73
- export declare function parseFormatE(input: string, options?: ParseFormatEOptions): FormatEDiff;
74
- /**
75
- * Serialize an OntologyGraph to compact Format E text.
76
- *
77
- * CR-SM-216: nodes are written under `### <TYPE>` sections — the type is declared once
78
- * per section instead of once per id. Measured on the graphcode SSOT graph (369
79
- * elements), 12 section headers cost ~48 tokens where a per-node type attribute would
80
- * have cost ~1476.
81
- */
82
- export declare function serializeToFormatE(graph: OntologyGraph): string;
@@ -1,15 +1,29 @@
1
1
  /**
2
- * Format E Parser Graph mutations from compact text format.
3
- * SE-relevant subset: nodes (+/-/~) and edges (+/-), no chat-canvas or views.
2
+ * Format E — der GETEILTE Teil: Attribut-Hydration und das Herausloesen des Blocks.
4
3
  *
5
- * CR-SM-216 (Format-E v2): the element type comes from the `### <TYPE>` section a node
6
- * is declared under, never from the spelling of its id. The old id-derived typing made
7
- * every consumer with a different id convention fail *silently* — aimpro CR-230 lost a
8
- * whole graph that way (`TYPE-slug` ids rejected, result empty, no error).
4
+ * CR-SM-331: Parser UND Serializer standen bis hierher ebenfalls in dieser Datei ein
5
+ * zweites Paar neben `FormatECodec` in @sigloch/graph-api-core. Zwei Implementierungen
6
+ * desselben Dialekts urteilten ueber denselben Text verschieden: eine Beschreibung mit
7
+ * Zeilenumbruch lehnte DIESER Parser ab ("Invalid node line", `NODE_RE` verlangte eine uid
8
+ * ohne Leerzeichen), waehrend der lebende Codec die uebergelaufene Zeile als KNOTENZEILE
9
+ * annahm und daraus einen Phantom-Knoten baute (ITEM-2026-183, am Gate reproduziert). Der
10
+ * strengere war der, den keine Produktionsstelle der Familie je aufrief.
11
+ *
12
+ * Ein Dialekt, eine Implementierung — und sie lebt in graph-api-core, nicht hier, weil sie
13
+ * DESKRIPTOR-PARAMETRISIERT sein muss: `loadFixture` (test-fixtures.ts) parst Format E mit
14
+ * einer FREMDEN Ontologie und ist publizierte API. Ein SE-fester Parser in contracts koennte
15
+ * das nicht mehr bedienen.
16
+ *
17
+ * Was hier bleibt, bleibt mit Grund: `attributeTypeOf` und `hydrateAttrValue` lesen
18
+ * `ELEMENT_ATTRIBUTES` und fuettern `RealRefSchema`/`TestRefSchema` — sie gehoeren zu den
19
+ * SCHEMATA, nicht zum Dialekt, und der Codec importiert sie von hier (CR-GC-334). Zwei
20
+ * Hydrationsregeln wuerden driften; es gibt genau eine.
21
+ *
22
+ * Der Dateiname bleibt absichtlich stehen — ein Rename traegt Kosten bei jedem Importeur.
9
23
  *
10
24
  * @sigloch/contracts/se
11
25
  */
12
- import { ElementType, ELEMENT_ATTRIBUTES, TraceType, normalizeReqKinds } from './ontology.js';
26
+ import { ELEMENT_ATTRIBUTES } from './ontology.js';
13
27
  // ---------------------------------------------------------------------------
14
28
  // Extraction
15
29
  // ---------------------------------------------------------------------------
@@ -20,40 +34,8 @@ export function extractFormatE(llmOutput) {
20
34
  return m ? m[1].trim() : null;
21
35
  }
22
36
  // ---------------------------------------------------------------------------
23
- // Parser
37
+ // Attribut-Hydration — geteilt mit FormatECodec (CR-GC-334)
24
38
  // ---------------------------------------------------------------------------
25
- // CR-SM-266 D5: ABGELEITET statt abgeschrieben. Die Liste stand hier als zweite Kopie des
26
- // TraceType-Enums und trug `produces` noch, als es dort schon entfernt war — genau die Drift,
27
- // die eine doppelte Wahrheit erzeugt. Eine Quelle, keine Pflege.
28
- const VALID_TRACE_TYPES = new Set(TraceType.options);
29
- const OP_PREFIX = {
30
- '+': 'add',
31
- '-': 'remove',
32
- '~': 'update',
33
- '!': 'strict_add',
34
- };
35
- /**
36
- * CR-SM-215: the target group is `(.+)` — Format-E allows fan-out
37
- * `A -x-> B, C, D`, one edge per target. `graph-api-core`'s codec has always parsed
38
- * it; this parser rejected it as `Invalid edge syntax`, so the same text produced
39
- * different operations depending on which parser saw it.
40
- */
41
- const EDGE_RE = /^([+\-~!])?\s*(\S+)\s+-(\w+)->\s+(.+?)\s*$/;
42
- const NODE_RE = /^([+\-~!])?\s*(\S+?)(?:\|(.*))?$/;
43
- /** CR-147: @key value attribute line (indented, below a node entry). */
44
- const ATTR_RE = /^\s*@(\w+)\s+(.+)$/;
45
- /**
46
- * BOK-CR-026: hydrate a JSON object/array attribute value. The ontology's bindings
47
- * (`realRef {file,symbol?,lang?}`, `testRefs [{file,tool,…}]`) are objects/arrays; kept as raw
48
- * strings they fail RealRefSchema/TestRefSchema and the element reads as unbound.
49
- * Only `{…}`/`[…]` are attempted — every other value stays the string it is, and a
50
- * malformed literal falls back to the string rather than failing the whole parse.
51
- *
52
- * CR-GC-334: exported, because `FormatECodec` (graph-api-core) parses the SAME `@key value`
53
- * lines and did NOT hydrate — the identical defect this function was written for, one
54
- * package over. Two hydration rules would drift; there is one, and it lives here with the
55
- * schemas it feeds.
56
- */
57
39
  /**
58
40
  * CR-SM-251: der deklarierte Typ des Attributs entscheidet, nicht die Schreibweise des Wertes.
59
41
  *
@@ -68,6 +50,18 @@ export function attributeTypeOf(elementType, key) {
68
50
  const specs = ELEMENT_ATTRIBUTES[elementType];
69
51
  return specs?.find(s => s.key === key)?.type;
70
52
  }
53
+ /**
54
+ * BOK-CR-026: hydrate a JSON object/array attribute value. The ontology's bindings
55
+ * (`realRef {file,symbol?,lang?}`, `testRefs [{file,tool,…}]`) are objects/arrays; kept as raw
56
+ * strings they fail RealRefSchema/TestRefSchema and the element reads as unbound.
57
+ * Only `{…}`/`[…]` are attempted — every other value stays the string it is, and a
58
+ * malformed literal falls back to the string rather than failing the whole parse.
59
+ *
60
+ * CR-GC-334: exported, because `FormatECodec` (graph-api-core) parses the SAME `@key value`
61
+ * lines and did NOT hydrate — the identical defect this function was written for, one
62
+ * package over. Two hydration rules would drift; there is one, and it lives here with the
63
+ * schemas it feeds.
64
+ */
71
65
  export function hydrateAttrValue(raw, declaredType) {
72
66
  // CR-SM-251: `concept:true` kam als String "true" an, und alle 11 Vergleiche in
73
67
  // rules.ts/conformance-rules.ts pruefen identitaetsscharf (`=== true`). Damit war jeder
@@ -94,239 +88,3 @@ export function hydrateAttrValue(raw, declaredType) {
94
88
  return raw;
95
89
  }
96
90
  }
97
- /** CR-148: Trace-type normalization aliases (source→target→from→to). */
98
- const TRACE_NORMALIZE = {
99
- FLOW: { SCHEMA: 'relation' }, // FLOW→SCHEMA io → relation
100
- };
101
- /** `### <TYPE>` — the node type section (CR-SM-216). */
102
- const TYPE_SECTION_RE = /^###\s+([A-Za-z_]+)\s*$/;
103
- /** Parse a Format E text block into validated operations. */
104
- export function parseFormatE(input, options = {}) {
105
- const operations = [];
106
- const errors = [];
107
- let section = null;
108
- let currentType = null;
109
- /** uid → type, from this text's node sections. */
110
- const declared = new Map();
111
- const typeOf = (uid) => declared.get(uid) ?? options.resolveType?.(uid);
112
- for (const rawLine of input.split('\n')) {
113
- const line = rawLine.trim();
114
- if (!line || line.startsWith('//') || line.startsWith('#!'))
115
- continue;
116
- // CR-147: @attribute lines attach to the last node operation
117
- const attrMatch = ATTR_RE.exec(line);
118
- if (attrMatch) {
119
- const lastOp = operations.length > 0 ? operations[operations.length - 1] : null;
120
- if (lastOp && (lastOp.type === 'add_node' || lastOp.type === 'update_node' || lastOp.type === 'strict_add_node')) {
121
- if (!lastOp.attributes)
122
- lastOp.attributes = {};
123
- const key = attrMatch[1];
124
- const value = hydrateAttrValue(attrMatch[2].trim(), attributeTypeOf(lastOp.elementType, key));
125
- // CR-SM-320 (ITEM-2026-007): `kinds` ist keine freie Eigenschaft, sondern die REQ-Spalte,
126
- // die hier nur durchreist. Ein Einzelwert (`@kinds non-functional`) blieb als roher
127
- // String liegen und wurde so persistiert — jede Sicht, die auf Listen-Mitgliedschaft
128
- // filtert, verlor die REQ still. Die Form wird am PRODUZENTEN hergestellt, nicht bei
129
- // jedem Leser einzeln: hier ist `kinds` immer eine Liste.
130
- lastOp.attributes[key] = key === 'kinds' ? [...normalizeReqKinds(value)] : value;
131
- }
132
- else {
133
- errors.push(`@attribute line without preceding node: "${line}"`);
134
- }
135
- continue;
136
- }
137
- // Section headers
138
- if (/^##\s*nodes?\s*$/i.test(line)) {
139
- section = 'nodes';
140
- currentType = null;
141
- continue;
142
- }
143
- if (/^##\s*edges?\s*$/i.test(line)) {
144
- section = 'edges';
145
- currentType = null;
146
- continue;
147
- }
148
- // CR-SM-216: `### <TYPE>` carries the element type for the nodes below it.
149
- const typeSection = TYPE_SECTION_RE.exec(line);
150
- if (typeSection && section === 'nodes') {
151
- const parsed = ElementType.safeParse(typeSection[1]);
152
- if (parsed.success) {
153
- currentType = parsed.data;
154
- }
155
- else {
156
- currentType = null;
157
- errors.push(`Unknown element type section: "### ${typeSection[1]}"`);
158
- }
159
- continue;
160
- }
161
- // Skip other markdown headers
162
- if (line.startsWith('#'))
163
- continue;
164
- // Edge: detect by arrow in the structural part only — before the description
165
- // pipe. Node descriptions may legitimately contain '->' ("FUNC->FUNC compose");
166
- // edges carry no pipe, so splitting on '|' disambiguates (CR-GC-247). Since
167
- // CR-SM-215 widened the target group to `(.+?)`, this guard is what keeps a
168
- // multi-word node description from being read as an edge.
169
- const isEdgeLine = line.split('|', 1)[0].includes('->');
170
- if (isEdgeLine || section === 'edges') {
171
- const edgeMatch = isEdgeLine ? EDGE_RE.exec(line) : null;
172
- if (edgeMatch) {
173
- parseEdge(edgeMatch, typeOf, operations, errors);
174
- }
175
- else {
176
- errors.push(`Invalid edge line: "${line}"`);
177
- }
178
- continue;
179
- }
180
- // Node
181
- if (section === 'nodes') {
182
- const nodeMatch = NODE_RE.exec(line);
183
- if (!nodeMatch) {
184
- errors.push(`Invalid node line: "${line}"`);
185
- }
186
- else if (!currentType) {
187
- // CR-SM-216: no type section, no type. Guessing one from the id is what
188
- // CR-230 punished; an error is the point.
189
- errors.push(`Node "${nodeMatch[2]}" is not under a "### <TYPE>" section`);
190
- }
191
- else {
192
- declared.set(nodeMatch[2], currentType);
193
- parseNode(nodeMatch, currentType, operations, errors);
194
- }
195
- continue;
196
- }
197
- // Unknown line (edges are handled above, nodes need their section)
198
- if (line.length > 0)
199
- errors.push(`Unrecognized line: "${line}"`);
200
- }
201
- return { operations, errors };
202
- }
203
- /** CR-148: Normalize trace type using alias table. */
204
- function normalizeTraceType(srcType, tgtType, traceType) {
205
- const aliases = TRACE_NORMALIZE[srcType];
206
- if (aliases && aliases[tgtType] && traceType !== aliases[tgtType]) {
207
- return aliases[tgtType];
208
- }
209
- return traceType;
210
- }
211
- function parseNode(m, elementType, ops, errors) {
212
- const opChar = m[1] || '+';
213
- const id = m[2];
214
- const descr = m[3]?.trim();
215
- const action = OP_PREFIX[opChar] ?? 'add';
216
- if (!id) {
217
- errors.push(`Node line without an id: "${m[0]}"`);
218
- return;
219
- }
220
- if (action === 'remove') {
221
- ops.push({ type: 'remove_node', semanticId: id });
222
- }
223
- else if (action === 'update') {
224
- ops.push({ type: 'update_node', semanticId: id, elementType, description: descr });
225
- }
226
- else if (action === 'strict_add') {
227
- ops.push({ type: 'strict_add_node', semanticId: id, elementType, description: descr });
228
- }
229
- else {
230
- ops.push({ type: 'add_node', semanticId: id, elementType, description: descr });
231
- }
232
- }
233
- function parseEdge(m, typeOf, ops, errors) {
234
- const opChar = m[1] || '+';
235
- const sourceId = m[2];
236
- const traceType = m[3];
237
- const action = OP_PREFIX[opChar] ?? 'add';
238
- if (!VALID_TRACE_TYPES.has(traceType)) {
239
- errors.push(`Invalid trace type: "${traceType}"`);
240
- return;
241
- }
242
- const srcType = typeOf(sourceId);
243
- if (!srcType) {
244
- errors.push(`Cannot resolve type of "${sourceId}" — not declared under a "### <TYPE>" section and no resolveType provided`);
245
- return;
246
- }
247
- const edgeType = action === 'remove'
248
- ? 'remove_edge'
249
- : action === 'strict_add'
250
- ? 'strict_add_edge'
251
- : 'add_edge';
252
- // CR-SM-215: 1:n fan-out — `A -x-> B, C` is n independent edges. Validation runs
253
- // per target (like `graph-api-core`'s codec), so one bad target does not discard
254
- // its siblings.
255
- const targets = m[4].split(',').map(t => t.trim()).filter(Boolean);
256
- for (const targetId of targets) {
257
- const tgtType = typeOf(targetId);
258
- if (!tgtType) {
259
- errors.push(`Cannot resolve type of "${targetId}" — not declared under a "### <TYPE>" section and no resolveType provided`);
260
- continue;
261
- }
262
- // CR-148: normalize the trace type (e.g. FLOW→SCHEMA io → relation).
263
- // CR-SM-325: no legality verdict while parsing — the parser knows neither label nor the
264
- // kinds of existing nodes. The write path judges with the one rule (gate R-18, GraphService).
265
- const resolvedTraceType = normalizeTraceType(srcType, tgtType, traceType);
266
- ops.push({
267
- type: edgeType,
268
- semanticId: `${sourceId}->${targetId}`,
269
- sourceId,
270
- targetId,
271
- traceType: resolvedTraceType,
272
- });
273
- }
274
- }
275
- // ---------------------------------------------------------------------------
276
- // Serializer
277
- // ---------------------------------------------------------------------------
278
- /**
279
- * Serialize an OntologyGraph to compact Format E text.
280
- *
281
- * CR-SM-216: nodes are written under `### <TYPE>` sections — the type is declared once
282
- * per section instead of once per id. Measured on the graphcode SSOT graph (369
283
- * elements), 12 section headers cost ~48 tokens where a per-node type attribute would
284
- * have cost ~1476.
285
- */
286
- export function serializeToFormatE(graph) {
287
- const lines = [];
288
- // Nodes, grouped by type
289
- // CR-SM-266 D5: kein SESSION-Skip mehr — der Elementtyp existiert nicht.
290
- const modelingElements = graph.elements;
291
- if (modelingElements.length > 0) {
292
- lines.push('## Nodes');
293
- const byType = new Map();
294
- for (const el of modelingElements) {
295
- const group = byType.get(el.type);
296
- if (group)
297
- group.push(el);
298
- else
299
- byType.set(el.type, [el]);
300
- }
301
- for (const type of [...byType.keys()].sort()) {
302
- lines.push(`### ${type}`);
303
- for (const el of byType.get(type) ?? []) {
304
- const descr = el.description ? `|${el.description}` : '';
305
- lines.push(`+ ${el.id}${descr}`);
306
- // CR-147: Serialize known attributes
307
- if (el.attributes) {
308
- for (const [k, v] of Object.entries(el.attributes)) {
309
- if (v == null)
310
- continue;
311
- // BOK-CR-026: objects/arrays as JSON — String({}) collapses a realRef/testRefs
312
- // binding to "[object Object]" and loses it on the next parse.
313
- const text = typeof v === 'object' ? JSON.stringify(v) : String(v);
314
- if (text.length > 0)
315
- lines.push(` @${k} ${text}`);
316
- }
317
- }
318
- }
319
- }
320
- }
321
- // Edges
322
- // CR-SM-266 D5: kein audit-Filter mehr — `Trace.category` existiert nicht.
323
- const modelingTraces = graph.traces;
324
- if (modelingTraces.length > 0) {
325
- lines.push('');
326
- lines.push('## Edges');
327
- for (const t of modelingTraces) {
328
- lines.push(`+ ${t.source} -${t.type}-> ${t.target}`);
329
- }
330
- }
331
- return lines.join('\n');
332
- }
@@ -72,4 +72,20 @@ export interface FunctionCriticality {
72
72
  * Cases, dann stabil nach `funcId`. Eine Zeile je FUNC — auch fuer die mit 0, sonst waere die
73
73
  * Abwesenheit eines Wertes von der Abwesenheit der Funktion nicht zu unterscheiden.
74
74
  */
75
+ /**
76
+ * CR-SM-335 (ITEM-2026-096, zugesagt in CR-SM-313) — EINE Definition von "welche Ketten
77
+ * enthalten diese FUNC", als MENGEN.
78
+ *
79
+ * R-21 hielt fuer "teilen diese beiden EINE Kette" einen eigenen, lokalen Index in `rules.ts`,
80
+ * waehrend die ZAHL fuer seine Infrastruktur-Ausnahme aus `functionCriticality` kam. Zwei
81
+ * Stellen lasen dieselbe Kantenmenge nach derselben Definition (R-30: direkte
82
+ * `FCHAIN -compose-> FUNC`, keine Vererbung). Es gab keinen Widerspruch — beide taten
83
+ * dasselbe. Die Gefahr ist die kuenftige Aenderung an nur einer der beiden, und das ist
84
+ * genau die Drift-Klasse, gegen die `module-crossings.ts` gebaut wurde (CR-SM-276).
85
+ *
86
+ * Eine Rechnung liefert jetzt beides: `chainsByFunc` die Mengen, `functionCriticality` die
87
+ * Zahlen daraus. Eine FUNC ohne Kette taucht NICHT auf — `get` liefert `undefined`, und der
88
+ * Aufrufer entscheidet, ob das 0 oder "nicht gefragt" heisst.
89
+ */
90
+ export declare function chainsByFunc(graph: OntologyGraph): Map<string, Set<string>>;
75
91
  export declare function functionCriticality(graph: OntologyGraph): FunctionCriticality[];
@@ -6,15 +6,27 @@ import { indexOf } from './graph-index.js';
6
6
  * Cases, dann stabil nach `funcId`. Eine Zeile je FUNC — auch fuer die mit 0, sonst waere die
7
7
  * Abwesenheit eines Wertes von der Abwesenheit der Funktion nicht zu unterscheiden.
8
8
  */
9
- export function functionCriticality(graph) {
9
+ /**
10
+ * CR-SM-335 (ITEM-2026-096, zugesagt in CR-SM-313) — EINE Definition von "welche Ketten
11
+ * enthalten diese FUNC", als MENGEN.
12
+ *
13
+ * R-21 hielt fuer "teilen diese beiden EINE Kette" einen eigenen, lokalen Index in `rules.ts`,
14
+ * waehrend die ZAHL fuer seine Infrastruktur-Ausnahme aus `functionCriticality` kam. Zwei
15
+ * Stellen lasen dieselbe Kantenmenge nach derselben Definition (R-30: direkte
16
+ * `FCHAIN -compose-> FUNC`, keine Vererbung). Es gab keinen Widerspruch — beide taten
17
+ * dasselbe. Die Gefahr ist die kuenftige Aenderung an nur einer der beiden, und das ist
18
+ * genau die Drift-Klasse, gegen die `module-crossings.ts` gebaut wurde (CR-SM-276).
19
+ *
20
+ * Eine Rechnung liefert jetzt beides: `chainsByFunc` die Mengen, `functionCriticality` die
21
+ * Zahlen daraus. Eine FUNC ohne Kette taucht NICHT auf — `get` liefert `undefined`, und der
22
+ * Aufrufer entscheidet, ob das 0 oder "nicht gefragt" heisst.
23
+ */
24
+ export function chainsByFunc(graph) {
10
25
  const idx = indexOf(graph);
11
- const funcs = idx.elementsOfType('FUNC');
12
26
  const typeOf = new Map(graph.elements.map((e) => [e.id, e.type]));
13
- const funcIds = new Set(funcs.map((f) => f.id));
14
- const compose = idx.tracesOfType('compose');
15
- // FUNC -> seine Ketten. R-30s Fassung, woertlich.
27
+ const funcIds = new Set(idx.elementsOfType('FUNC').map((f) => f.id));
16
28
  const chainsOf = new Map();
17
- for (const t of compose) {
29
+ for (const t of idx.tracesOfType('compose')) {
18
30
  if (typeOf.get(t.source) !== 'FCHAIN' || !funcIds.has(t.target))
19
31
  continue;
20
32
  let s = chainsOf.get(t.target);
@@ -24,6 +36,15 @@ export function functionCriticality(graph) {
24
36
  }
25
37
  s.add(t.source);
26
38
  }
39
+ return chainsOf;
40
+ }
41
+ export function functionCriticality(graph) {
42
+ const idx = indexOf(graph);
43
+ const funcs = idx.elementsOfType('FUNC');
44
+ const typeOf = new Map(graph.elements.map((e) => [e.id, e.type]));
45
+ const compose = idx.tracesOfType('compose');
46
+ // FUNC -> seine Ketten. R-30s Fassung, woertlich — und dieselbe Rechnung, die R-21 liest.
47
+ const chainsOf = chainsByFunc(graph);
27
48
  // FCHAIN -> seine Use Cases.
28
49
  const ucsOf = new Map();
29
50
  for (const t of compose) {
@@ -24,5 +24,5 @@ export declare const GRAMMAR_SNAPSHOT: {
24
24
  readonly conformanceRules: readonly ["RC-01 (error)", "RC-02 (error)", "RC-03 (error)", "RC-04 (warning)", "RC-05 (warning)", "RC-06 (warning)", "RC-07 (warning)"];
25
25
  readonly policy: readonly ["apTable = null", "boundaryWidth = {warning:5}", "criticality = {infrastructure:3}", "crossingFlows = {warning:3}", "decompositionBreadth = {min:3,warning:9}", "instability = null", "lcom4 = {info:4,warning:6}", "riskRpn = 100"];
26
26
  readonly ruleHelp: readonly ["AF-01", "AF-02", "AF-03", "AF-04", "AF-05", "BQ-01", "BQ-02", "BQ-04", "BQ-06", "BQ-07", "BW-02", "CL-01", "CR-01", "CR-R01", "CR-R02", "CR-R03", "FC-02", "FC-03", "FC-04", "FM-01", "FM-02", "FM-03", "IO-01", "IO-02", "MS-01", "MS-02", "MS-03", "MT-01", "MT-02", "MT-04", "ND-01", "ND-02", "NFR-01", "R-01", "R-02", "R-04", "R-05", "R-08", "R-10", "R-12", "R-15", "R-16", "R-17", "R-18", "R-19", "R-20", "R-21", "R-22", "R-23", "R-26", "R-29", "R-30", "R-31", "R-32", "RC-01", "RC-02", "RC-03", "RC-04", "RC-05", "RC-06", "RC-07", "RD-01", "RD-02", "RD-03", "RD-04", "RD-05", "SC-02", "UC-01", "UC-02", "UC-03", "UC-04", "UC-05", "UC-06", "VR-01"];
27
- readonly exports: readonly ["AF_RULES", "ALL_RULE_DEFS", "AO_RULES", "ActionPriority", "AnalysisArtifactId", "AnalysisFreshnessStampSchema", "ApTableSchema", "BOUNDED_PATTERNS", "BQ_RULES", "CLOSED_STATUS", "CODE_CONFORMANCE_RULES", "CR_RULES", "CodeFactsSchema", "DEFAULT_METRIC_POLICY", "DIMENSION_READINESS_DELTA_NAME", "DIMENSION_READINESS_NAME", "ELEMENT_ATTRIBUTES", "ELEMENT_DESCRIPTIONS", "ElementType", "ElementUid", "FC_RULES", "FM_RULES", "FileFactsSchema", "ImportEdgeSchema", "MAX_SLUG_LENGTH", "META_MODEL_VERSION", "MODELING_ELEMENT_TYPES", "MT_RULES", "MetricPolicySchema", "ND_RULES", "ONTOLOGY_VERSION", "OntologyElement", "OntologyGraph", "PHASE_READINESS_NAME", "PhaseGate", "READINESS_SCORED_PROFILES", "REQUIRED_PATTERNS", "RULES_VERSION", "RULE_HELP", "RULE_TO_DIMENSION", "RULE_TO_PHASE", "ReadinessDimension", "ReadinessReport", "ReadinessScore", "RealRefSchema", "RepoRelativePathSchema", "ReqKind", "RuleSeverity", "RuleViolation", "SC_RULES", "TRACE_PATTERNS", "TestRefSchema", "TestRefsSchema", "TestResult", "Trace", "TraceType", "UC_RULES", "V3_RULES", "VIEW_RULES", "VerificationMethod", "ViolationCandidate", "ViolationContext", "actionPriority", "allocationCohesion", "apMethod", "attributeTypeOf", "boxContracts", "bq01Unambiguous", "bq02Verifiable", "bq04Necessary", "bq06Conforming", "bq07Complete", "bw02WhiteboxWidth", "cl01ConopsCompleteness", "cr01CrossingFlowCount", "crossingContractCount", "decomposedFuncs", "evaluateAFRules", "evaluateAORules", "evaluateAllRules", "evaluateBQRules", "evaluateCRRules", "evaluateConformanceRules", "evaluateFCRules", "evaluateFMRules", "evaluateMTRules", "evaluateNDRules", "evaluateRules", "evaluateSCRules", "evaluateUCRules", "evaluateViewRules", "extractFormatE", "fc02LeafUcHasFchain", "fc03FchainFlat", "fc04ActorBounded", "fm01MissingFmeaAttributes", "fm02MissingMitigation", "fm03HighRiskUnverified", "funcSimilarity", "funcSubtree", "functionCriticality", "getRuleDefsForProfile", "hydrateAttrValue", "importCoverage", "indexOf", "io01CrossModuleCompleteness", "isElementUid", "isValidTrace", "jaccard", "maxOccurs", "minOccurs", "moduleCrossings", "moduleMetrics", "mt01Instability", "mt02Lcom4", "mt04WhiteboxLcom4", "nd01FuncNearDuplicate", "nd02SchemaNearDuplicate", "nfr01BudgetOvershoot", "normalizeReqKinds", "pairsAbove", "parseElementUid", "parseFormatE", "sc02IsReferenced", "schemaSimilarity", "serializeToFormatE", "setBQ04SimilarityMatrix", "subtreeFuncs", "toElementUid", "toEvaluableGraph", "tokens", "traceRejection", "tryParseElementUid", "uc01HasRequirements", "uc02HasActor", "uc03HasScenario", "uc04GoalDefined", "uc05HasPostcondition", "uc06HasPrecondition", "vr01TestNoResult", "whiteboxContractCount"];
27
+ readonly exports: readonly ["AF_RULES", "ALL_RULE_DEFS", "AO_RULES", "ActionPriority", "AnalysisArtifactId", "AnalysisFreshnessStampSchema", "ApTableSchema", "BOUNDED_PATTERNS", "BQ_RULES", "CLOSED_STATUS", "CODE_CONFORMANCE_RULES", "CR_RULES", "CodeFactsSchema", "DEFAULT_METRIC_POLICY", "DIMENSION_READINESS_DELTA_NAME", "DIMENSION_READINESS_NAME", "ELEMENT_ATTRIBUTES", "ELEMENT_DESCRIPTIONS", "ElementType", "ElementUid", "FC_RULES", "FM_RULES", "FileFactsSchema", "ImportEdgeSchema", "MAX_SLUG_LENGTH", "META_MODEL_VERSION", "MODELING_ELEMENT_TYPES", "MT_RULES", "MetricPolicySchema", "ND_RULES", "ONTOLOGY_VERSION", "OntologyElement", "OntologyGraph", "PHASE_READINESS_NAME", "PhaseGate", "READINESS_SCORED_PROFILES", "REQUIRED_PATTERNS", "RULES_VERSION", "RULE_HELP", "RULE_TO_DIMENSION", "RULE_TO_PHASE", "ReadinessDimension", "ReadinessReport", "ReadinessScore", "RealRefSchema", "RepoRelativePathSchema", "ReqKind", "RuleSeverity", "RuleViolation", "SC_RULES", "SteerSpace", "SteerTerm", "TRACE_PATTERNS", "TestRefSchema", "TestRefsSchema", "TestResult", "Trace", "TraceType", "UC_RULES", "V3_RULES", "VIEW_RULES", "VerificationMethod", "ViolationCandidate", "ViolationContext", "actionPriority", "allocationCohesion", "apMethod", "attributeTypeOf", "boxContracts", "bq01Unambiguous", "bq02Verifiable", "bq04Necessary", "bq06Conforming", "bq07Complete", "bw02WhiteboxWidth", "chainsByFunc", "cl01ConopsCompleteness", "cr01CrossingFlowCount", "crossingContractCount", "decomposedFuncs", "evaluateAFRules", "evaluateAORules", "evaluateAllRules", "evaluateBQRules", "evaluateCRRules", "evaluateConformanceRules", "evaluateFCRules", "evaluateFMRules", "evaluateMTRules", "evaluateNDRules", "evaluateRules", "evaluateSCRules", "evaluateUCRules", "evaluateViewRules", "extractFormatE", "fc02LeafUcHasFchain", "fc03FchainFlat", "fc04ActorBounded", "fm01MissingFmeaAttributes", "fm02MissingMitigation", "fm03HighRiskUnverified", "funcSimilarity", "funcSubtree", "functionCriticality", "getRuleDefsForProfile", "hydrateAttrValue", "importCoverage", "indexOf", "io01CrossModuleCompleteness", "isElementUid", "isValidTrace", "jaccard", "maxOccurs", "minOccurs", "moduleCrossings", "moduleMetrics", "mt01Instability", "mt02Lcom4", "mt04WhiteboxLcom4", "nd01FuncNearDuplicate", "nd02SchemaNearDuplicate", "nfr01BudgetOvershoot", "normalizeReqKinds", "pairsAbove", "parseElementUid", "sc02IsReferenced", "schemaSimilarity", "setBQ04SimilarityMatrix", "subtreeFuncs", "toElementUid", "toEvaluableGraph", "tokens", "traceRejection", "tryParseElementUid", "uc01HasRequirements", "uc02HasActor", "uc03HasScenario", "uc04GoalDefined", "uc05HasPostcondition", "uc06HasPrecondition", "vr01TestNoResult", "whiteboxContractCount"];
28
28
  };
@@ -342,6 +342,8 @@ export const GRAMMAR_SNAPSHOT = {
342
342
  "RuleSeverity",
343
343
  "RuleViolation",
344
344
  "SC_RULES",
345
+ "SteerSpace",
346
+ "SteerTerm",
345
347
  "TRACE_PATTERNS",
346
348
  "TestRefSchema",
347
349
  "TestRefsSchema",
@@ -365,6 +367,7 @@ export const GRAMMAR_SNAPSHOT = {
365
367
  "bq06Conforming",
366
368
  "bq07Complete",
367
369
  "bw02WhiteboxWidth",
370
+ "chainsByFunc",
368
371
  "cl01ConopsCompleteness",
369
372
  "cr01CrossingFlowCount",
370
373
  "crossingContractCount",
@@ -414,10 +417,8 @@ export const GRAMMAR_SNAPSHOT = {
414
417
  "normalizeReqKinds",
415
418
  "pairsAbove",
416
419
  "parseElementUid",
417
- "parseFormatE",
418
420
  "sc02IsReferenced",
419
421
  "schemaSimilarity",
420
- "serializeToFormatE",
421
422
  "setBQ04SimilarityMatrix",
422
423
  "subtreeFuncs",
423
424
  "toElementUid",
@@ -45,6 +45,51 @@ export type ReadinessScoreType = z.infer<typeof ReadinessScore>;
45
45
  * Ein interpretierbarer Ersatz (`1 − Σviolations / Σapplicable`) kommt, wenn er einen
46
46
  * Konsumenten hat — nicht auf Vorrat.
47
47
  */
48
+ /**
49
+ * CR-SM-337 (ITEM-2026-059) — der STEUERUNGSRAUM im Bericht, Stufe 1: der Vertrag.
50
+ *
51
+ * Readiness misst ABDECKUNG ("wie viele Stellen sind erledigt"), der Steuer-Score AUSPRAEGUNG
52
+ * ("wie schlimm ist die schlimmste offene"). Beide lesen denselben Regelstrom, und beide
53
+ * gehoeren in denselben Bericht — sonst rechnet der naechste Leser die zweite Haelfte selbst
54
+ * nach. Genau das drohte: das GVE-Dashboard (ITEM-2026-018) haette `steerScore` nachbauen
55
+ * muessen, weil die Zahlen bisher nur als `verdict.steer.improvement` je Suggestion sichtbar
56
+ * waren. Eine zweite Rechnung ist eine zweite Wahrheit.
57
+ *
58
+ * Die Form ist die von `SteerScore` in @sigloch/se-engine (CR-SM-292), ZEICHENGLEICH
59
+ * uebernommen — contracts darf se-engine nicht importieren (es ist die Basis), also steht hier
60
+ * der Vertrag und dort die Rechnung. Wer die Form aendert, aendert sie an beiden Stellen; der
61
+ * Vertragstest daneben haelt die Felder fest.
62
+ *
63
+ * `worst` = der schlimmste normierte Ueberschuss `(wert - budget) / budget`. Normiert wird
64
+ * gegen die REGELSCHWELLE selbst, nicht gegen eine eigene Zahl — deshalb gibt es hier keine
65
+ * freien Parameter (CR-SM-292: daran ist der Vorgaenger CR-SM-281 gestorben).
66
+ */
67
+ export declare const SteerTerm: z.ZodObject<{
68
+ ruleId: z.ZodString;
69
+ elementId: z.ZodString;
70
+ value: z.ZodNumber;
71
+ threshold: z.ZodNumber;
72
+ overshoot: z.ZodNumber;
73
+ }, z.core.$strip>;
74
+ export type SteerTermType = z.infer<typeof SteerTerm>;
75
+ export declare const SteerSpace: z.ZodObject<{
76
+ worst: z.ZodNumber;
77
+ worstAt: z.ZodNullable<z.ZodObject<{
78
+ ruleId: z.ZodString;
79
+ elementId: z.ZodString;
80
+ }, z.core.$strip>>;
81
+ mean: z.ZodNumber;
82
+ score: z.ZodNumber;
83
+ measured: z.ZodNumber;
84
+ terms: z.ZodArray<z.ZodObject<{
85
+ ruleId: z.ZodString;
86
+ elementId: z.ZodString;
87
+ value: z.ZodNumber;
88
+ threshold: z.ZodNumber;
89
+ overshoot: z.ZodNumber;
90
+ }, z.core.$strip>>;
91
+ }, z.core.$strip>;
92
+ export type SteerSpaceType = z.infer<typeof SteerSpace>;
48
93
  export declare const ReadinessReport: z.ZodObject<{
49
94
  scores: z.ZodArray<z.ZodObject<{
50
95
  dimension: z.ZodEnum<{
@@ -63,6 +108,23 @@ export declare const ReadinessReport: z.ZodObject<{
63
108
  coreApplicable: z.ZodNumber;
64
109
  }, z.core.$strip>>;
65
110
  timestamp: z.ZodISODateTime;
111
+ steer: z.ZodOptional<z.ZodObject<{
112
+ worst: z.ZodNumber;
113
+ worstAt: z.ZodNullable<z.ZodObject<{
114
+ ruleId: z.ZodString;
115
+ elementId: z.ZodString;
116
+ }, z.core.$strip>>;
117
+ mean: z.ZodNumber;
118
+ score: z.ZodNumber;
119
+ measured: z.ZodNumber;
120
+ terms: z.ZodArray<z.ZodObject<{
121
+ ruleId: z.ZodString;
122
+ elementId: z.ZodString;
123
+ value: z.ZodNumber;
124
+ threshold: z.ZodNumber;
125
+ overshoot: z.ZodNumber;
126
+ }, z.core.$strip>>;
127
+ }, z.core.$strip>>;
66
128
  }, z.core.$strip>;
67
129
  export type ReadinessReportType = z.infer<typeof ReadinessReport>;
68
130
  /**
@@ -57,9 +57,68 @@ export const ReadinessScore = z.object({
57
57
  * Ein interpretierbarer Ersatz (`1 − Σviolations / Σapplicable`) kommt, wenn er einen
58
58
  * Konsumenten hat — nicht auf Vorrat.
59
59
  */
60
+ /**
61
+ * CR-SM-337 (ITEM-2026-059) — der STEUERUNGSRAUM im Bericht, Stufe 1: der Vertrag.
62
+ *
63
+ * Readiness misst ABDECKUNG ("wie viele Stellen sind erledigt"), der Steuer-Score AUSPRAEGUNG
64
+ * ("wie schlimm ist die schlimmste offene"). Beide lesen denselben Regelstrom, und beide
65
+ * gehoeren in denselben Bericht — sonst rechnet der naechste Leser die zweite Haelfte selbst
66
+ * nach. Genau das drohte: das GVE-Dashboard (ITEM-2026-018) haette `steerScore` nachbauen
67
+ * muessen, weil die Zahlen bisher nur als `verdict.steer.improvement` je Suggestion sichtbar
68
+ * waren. Eine zweite Rechnung ist eine zweite Wahrheit.
69
+ *
70
+ * Die Form ist die von `SteerScore` in @sigloch/se-engine (CR-SM-292), ZEICHENGLEICH
71
+ * uebernommen — contracts darf se-engine nicht importieren (es ist die Basis), also steht hier
72
+ * der Vertrag und dort die Rechnung. Wer die Form aendert, aendert sie an beiden Stellen; der
73
+ * Vertragstest daneben haelt die Felder fest.
74
+ *
75
+ * `worst` = der schlimmste normierte Ueberschuss `(wert - budget) / budget`. Normiert wird
76
+ * gegen die REGELSCHWELLE selbst, nicht gegen eine eigene Zahl — deshalb gibt es hier keine
77
+ * freien Parameter (CR-SM-292: daran ist der Vorgaenger CR-SM-281 gestorben).
78
+ */
79
+ export const SteerTerm = z.object({
80
+ /** Eine der STEER_RULES — die messenden Regeln (se-engine `STEER_RULES`). */
81
+ ruleId: z.string(),
82
+ /** Die Blackbox, an der der Term haengt. */
83
+ elementId: z.string(),
84
+ /** Der gemessene Wert (`violation.context.value`). */
85
+ value: z.number(),
86
+ /** Das Budget, gegen das normiert wird (`violation.context.threshold`). */
87
+ threshold: z.number(),
88
+ /** `max(0, (value - threshold) / threshold)` — dimensionslos, damit vergleichbar. */
89
+ overshoot: z.number().min(0),
90
+ });
91
+ export const SteerSpace = z.object({
92
+ worst: z.number().min(0),
93
+ /**
94
+ * Wo der schlimmste Ueberschuss sitzt — die Begruendung, nicht nur die Zahl.
95
+ * `null`, wenn nichts ueberschreitet.
96
+ */
97
+ worstAt: z.object({ ruleId: z.string(), elementId: z.string() }).nullable(),
98
+ mean: z.number().min(0),
99
+ /** `worst + EPS_AUGMENT * mean`. **Kleiner ist besser**, 0 = alles im Budget. */
100
+ score: z.number().min(0),
101
+ /**
102
+ * Zahl der Blackboxes, die in die Rechnung eingegangen sind.
103
+ *
104
+ * DIE WICHTIGSTE ZAHL DES OBJEKTS, und zwar wegen `score: 0`: der steht sowohl fuer "alles
105
+ * innerhalb seiner Budgets" als auch fuer "es wurde nichts gemessen". `measured: 0`
106
+ * unterscheidet die beiden. Ohne sie waere eine stille Null nicht von einem guten Zustand zu
107
+ * trennen — dieselbe Konvention wie `policy.X = null` und `moduleMetrics.instability = null`.
108
+ */
109
+ measured: z.number().int().min(0),
110
+ /** Die Terme je Element, aus denen `worst` und `mean` entstehen. */
111
+ terms: z.array(SteerTerm),
112
+ });
60
113
  export const ReadinessReport = z.object({
61
114
  scores: z.array(ReadinessScore),
62
115
  timestamp: z.iso.datetime(),
116
+ /**
117
+ * CR-SM-337: OPTIONAL, damit ein aelterer Produzent gueltig bleibt — der Vertrag wandert vor
118
+ * dem Fueller (Stufe 2, graphcode CR-GC-537). Fehlt das Feld, heisst das "dieser Produzent
119
+ * kennt den Steuerungsraum noch nicht", und das ist etwas anderes als `measured: 0`.
120
+ */
121
+ steer: SteerSpace.optional(),
63
122
  });
64
123
  /**
65
124
  * CR-SM-305 — welche Profile in die readiness-Zahlen eingehen, und warum `conformance` nicht.
package/dist/se/rules.js CHANGED
@@ -8,7 +8,7 @@ import { ElementType, TraceType, TestRefsSchema, RealRefSchema } from './ontolog
8
8
  import { traceRejection, BOUNDED_PATTERNS, REQUIRED_PATTERNS, maxOccurs } from './meta-model.js';
9
9
  import { indexOf } from './graph-index.js';
10
10
  import { moduleCrossings, subtreeFuncs } from './module-crossings.js';
11
- import { functionCriticality } from './function-criticality.js';
11
+ import { functionCriticality, chainsByFunc } from './function-criticality.js';
12
12
  export const RuleSeverity = z.enum(['error', 'warning', 'info']);
13
13
  /** Candidate target for resolving a violation (e.g. a REQ to satisfy, a TEST to link). */
14
14
  export const ViolationCandidate = z.object({
@@ -1260,14 +1260,11 @@ function fchainMustHaveIntegrationTest(graph, policy) {
1260
1260
  }
1261
1261
  if (connections.length === 0)
1262
1262
  return [];
1263
- // FUNC → set of FCHAINs composing it. LOKALER Index fuer eine andere Frage als die Kennzahl:
1264
- // "teilen diese beiden EINE Kette" braucht die Mengen, nicht die Zahl (CR-SM-313).
1265
- const chainsOfFunc = new Map();
1266
- for (const t of graph.traces) {
1267
- if (t.type === 'compose' && typeOf.get(t.source) === 'FCHAIN' && isFunc(t.target)) {
1268
- (chainsOfFunc.get(t.target) ?? chainsOfFunc.set(t.target, new Set()).get(t.target)).add(t.source);
1269
- }
1270
- }
1263
+ // FUNC → set of FCHAINs composing it. CR-SM-335: DIESELBE Rechnung, die auch die Kennzahl
1264
+ // speist vorher stand hier ein zweiter, lokaler Index ueber dieselbe Kantenmenge. "Teilen
1265
+ // diese beiden EINE Kette" braucht die Mengen, die Infrastruktur-Ausnahme die Zahl; beides
1266
+ // kommt jetzt aus `chainsByFunc` (CR-SM-313 hatte den Helfer zugesagt, CR-SM-314 die Zahl).
1267
+ const chainsOf = chainsByFunc(graph);
1271
1268
  // FCHAINs whose satisfy-REQ is verified by a TEST = chains with an integration test.
1272
1269
  const verifiedReqs = new Set(idx.tracesOfType('verify').map(t => t.target));
1273
1270
  const testedChains = new Set();
@@ -1285,8 +1282,8 @@ function fchainMustHaveIntegrationTest(graph, policy) {
1285
1282
  const violations = [];
1286
1283
  const seen = new Set();
1287
1284
  for (const [p, c] of connections) {
1288
- const pChains = chainsOfFunc.get(p) ?? new Set();
1289
- const cChains = chainsOfFunc.get(c) ?? new Set();
1285
+ const pChains = chainsOf.get(p) ?? new Set();
1286
+ const cChains = chainsOf.get(c) ?? new Set();
1290
1287
  // ZWEIG 1 — ein Ende in GAR KEINER Kette: still. Das ist R-30s Aussage, nicht R-21s.
1291
1288
  // Ohne diese Klausel feuert die Regel an einem code-importierten Graphen wie moneyflow
1292
1289
  // 219 von 219 Mal und meldet in Wahrheit "dieses Repo hat keine Wirkketten", einmal je Kante.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigloch/contracts",
3
- "version": "10.5.0",
3
+ "version": "10.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",