@sigloch/contracts 10.5.0 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/se/format-e-parser.d.ts +9 -66
- package/dist/se/format-e-parser.js +34 -276
- package/dist/se/function-criticality.d.ts +16 -0
- package/dist/se/function-criticality.js +27 -6
- package/dist/se/grammar-snapshot.d.ts +3 -3
- package/dist/se/grammar-snapshot.js +6 -3
- package/dist/se/index.d.ts +1 -1
- package/dist/se/index.js +1 -1
- package/dist/se/meta-model.d.ts +22 -0
- package/dist/se/meta-model.js +40 -0
- package/dist/se/readiness.d.ts +62 -0
- package/dist/se/readiness.js +59 -0
- package/dist/se/rules.d.ts +138 -0
- package/dist/se/rules.js +79 -13
- package/package.json +1 -1
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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-
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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 {
|
|
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
|
-
//
|
|
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
|
-
|
|
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(
|
|
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) {
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
export declare const GRAMMAR_SNAPSHOT: {
|
|
13
13
|
readonly versions: {
|
|
14
14
|
readonly ontology: "9.0.0";
|
|
15
|
-
readonly metaModel: "5.
|
|
15
|
+
readonly metaModel: "5.2.0";
|
|
16
16
|
readonly rules: "28.1.0";
|
|
17
17
|
};
|
|
18
18
|
readonly elementTypes: readonly ["ACTOR", "CR", "FCHAIN", "FLOW", "FUNC", "MOD", "MS", "REQ", "SCHEMA", "SYS", "TEST", "UC"];
|
|
19
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 [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", "TEST -verify-> SCHEMA", "UC -compose-> FCHAIN [1..*]", "UC -compose-> REQ [1..*]"];
|
|
20
|
+
readonly patterns: readonly ["ACTOR -io-> FLOW", "CR -relation-> FUNC", "CR -relation-> MOD", "CR -relation-> MS", "CR -relation-> REQ", "CR -relation-> SCHEMA", "CR -relation-> UC", "FCHAIN -compose-> FUNC [1..*]", "FCHAIN -satisfy-> REQ", "FLOW -io-> ACTOR", "FLOW -io-> FUNC", "FLOW -relation-> SCHEMA [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", "TEST -verify-> SCHEMA", "UC -compose-> FCHAIN [1..*]", "UC -compose-> REQ [1..*]"];
|
|
21
21
|
readonly elementColumns: readonly ["OntologyElement.attributes", "OntologyElement.created_at", "OntologyElement.description", "OntologyElement.id", "OntologyElement.kinds", "OntologyElement.method", "OntologyElement.name", "OntologyElement.status", "OntologyElement.type", "OntologyElement.updated_at", "Trace.attributes", "Trace.created_at", "Trace.label", "Trace.source", "Trace.target", "Trace.type", "Trace.verified_at", "Trace.weight"];
|
|
22
22
|
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"];
|
|
23
23
|
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]", "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]", "BW-02 (warning) 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]", "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]", "IO-02 (error) domain=[FLOW]", "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]", "MT-04 (info) domain=[FUNC]", "ND-01 (error) domain=[FUNC]", "ND-02 (error) domain=[SCHEMA]", "NFR-01 (warning) domain=[FCHAIN,FUNC,MOD]", "R-01 (error) domain=[REQ]", "R-02 (warning) domain=[FUNC]", "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-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,FUNC]", "R-22 (warning) domain=[FUNC]", "R-23 (warning) domain=[MOD]", "R-26 (warning) domain=[SCHEMA]", "R-29 (error) domain=[TEST]", "R-30 (warning) domain=[FUNC]", "R-31 (warning) domain=[FUNC]", "R-32 (warning) domain=[SCHEMA]", "RC-01 (error) domain=[FUNC]", "RC-02 (error) domain=[TEST]", "RC-03 (error) domain=[SCHEMA]", "RC-04 (warning) domain=[SCHEMA]", "RC-05 (warning) domain=[MOD]", "RC-06 (warning) domain=[FUNC,MOD,SCHEMA]", "RC-07 (warning) domain=[CR,SYS]", "RD-01 (warning) domain=[REQ]", "RD-02 (warning) domain=[REQ]", "RD-03 (info) domain=[REQ]", "RD-04 (warning) domain=[FUNC,MOD,SYS]", "RD-05 (warning) domain=[FUNC,MOD,SYS]", "SC-02 (warning) domain=[SCHEMA]", "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]"];
|
|
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", "
|
|
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", "admittingAlternatives", "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
|
};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
export const GRAMMAR_SNAPSHOT = {
|
|
13
13
|
versions: {
|
|
14
14
|
ontology: "9.0.0",
|
|
15
|
-
metaModel: "5.
|
|
15
|
+
metaModel: "5.2.0",
|
|
16
16
|
rules: "28.1.0",
|
|
17
17
|
},
|
|
18
18
|
elementTypes: [
|
|
@@ -43,6 +43,7 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
43
43
|
"CR -relation-> MOD",
|
|
44
44
|
"CR -relation-> MS",
|
|
45
45
|
"CR -relation-> REQ",
|
|
46
|
+
"CR -relation-> SCHEMA",
|
|
46
47
|
"CR -relation-> UC",
|
|
47
48
|
"FCHAIN -compose-> FUNC [1..*]",
|
|
48
49
|
"FCHAIN -satisfy-> REQ",
|
|
@@ -342,6 +343,8 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
342
343
|
"RuleSeverity",
|
|
343
344
|
"RuleViolation",
|
|
344
345
|
"SC_RULES",
|
|
346
|
+
"SteerSpace",
|
|
347
|
+
"SteerTerm",
|
|
345
348
|
"TRACE_PATTERNS",
|
|
346
349
|
"TestRefSchema",
|
|
347
350
|
"TestRefsSchema",
|
|
@@ -355,6 +358,7 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
355
358
|
"ViolationCandidate",
|
|
356
359
|
"ViolationContext",
|
|
357
360
|
"actionPriority",
|
|
361
|
+
"admittingAlternatives",
|
|
358
362
|
"allocationCohesion",
|
|
359
363
|
"apMethod",
|
|
360
364
|
"attributeTypeOf",
|
|
@@ -365,6 +369,7 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
365
369
|
"bq06Conforming",
|
|
366
370
|
"bq07Complete",
|
|
367
371
|
"bw02WhiteboxWidth",
|
|
372
|
+
"chainsByFunc",
|
|
368
373
|
"cl01ConopsCompleteness",
|
|
369
374
|
"cr01CrossingFlowCount",
|
|
370
375
|
"crossingContractCount",
|
|
@@ -414,10 +419,8 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
414
419
|
"normalizeReqKinds",
|
|
415
420
|
"pairsAbove",
|
|
416
421
|
"parseElementUid",
|
|
417
|
-
"parseFormatE",
|
|
418
422
|
"sc02IsReferenced",
|
|
419
423
|
"schemaSimilarity",
|
|
420
|
-
"serializeToFormatE",
|
|
421
424
|
"setBQ04SimilarityMatrix",
|
|
422
425
|
"subtreeFuncs",
|
|
423
426
|
"toElementUid",
|
package/dist/se/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare const ONTOLOGY_VERSION = "9.0.0";
|
|
|
7
7
|
/** Rules engine version (validation rules incl. RC conformance). */
|
|
8
8
|
export declare const RULES_VERSION = "28.1.0";
|
|
9
9
|
/** Meta-model version (trace pattern constraints + format-e parser). */
|
|
10
|
-
export declare const META_MODEL_VERSION = "5.
|
|
10
|
+
export declare const META_MODEL_VERSION = "5.2.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
|
@@ -7,7 +7,7 @@ export const ONTOLOGY_VERSION = '9.0.0'; // BREAKING (CR-SM-294): das Feld `asil
|
|
|
7
7
|
/** Rules engine version (validation rules incl. RC conformance). */
|
|
8
8
|
export const RULES_VERSION = '28.1.0'; // MINOR (CR-SM-329, ITEM-2026-154): +RC-07 'CR node agrees with docs/cr' (warning) und +`CodeFacts.crFiles`. Der CR-Status bleibt am Knoten (drei Regeln und die Meilenstein-Readiness lesen ihn); RC-07 prueft ihn gegen das Verzeichnis und meldet offene CR-Dateien ohne Knoten. Reine Ergaenzung, RC laeuft nicht in evaluateAllRules, Golden unberuehrt. Bestand vorab: 1 Status-Widerspruch (graphcode CR-GC-261), 11 offene Dateien ohne Knoten (bok 9, sigloch-modules 1, graph-view-edit 1). Prior: // MAJOR (CR-SM-327, ITEM-2026-151): neue Regel MT-04 — LCOM4 an der FUNC-WHITEBOX, dieselbe Rechnung wie MT-02 seit CR-SM-326 (Boxen = compose-Kinder, jede mit den Vertraegen ihres Teilbaums, verbunden ueber den geteilten Vertrag). Eigene ID statt domain ['MOD','FUNC'] an MT-02, weil die Grundgesamtheit den Readiness-Nenner bestimmt: an MT-02 gehaengt stiege alloc um bis zu 0,027 ohne Verbesserung am Graphen (Klasse CR-SM-235/270), als eigene Regel in arch <= 0,008. Praezedenz R-20/R-26/R-27. Schwellen geteilt (policy.lcom4). Bestand: MT-04 0 -> 2 (graphcode block-gedaechtnis 9 Boxen/5 Gruppen, block-host-sitzung 6/5), MT-02 unveraendert 11. Prior: // MAJOR (CR-SM-326, ITEM-2026-029): MT-02 urteilt ueber die BOXEN einer Modul-Whitebox (Sub-MODs + direkt allozierte FUNC), jede Box mit den Vertraegen ihres Teilbaums (Besitzkette wie R-04/CR-SM-282, BW-02/CR-SM-283, R-23/CR-SM-321), verbunden ueber den VERTRAG statt ueber FLOW-Identitaet (eine Zaehlbasis wie CR-01/R-04/MT-01 seit CR-SM-274/276). `null` jetzt auch ohne einen einzigen Vertrag im Modul — die unverdrahtete FUNC ist R-31s Aussage. Die Ausnahme aus CR-SM-263 entfaellt, `rollupContainer` bleibt Messung. Bestand ueber 7 Familiengraphen: MT-02 20 -> 11 (-5 graphcode Artefakte, -1 graphify, -4 moneyflow, +1 sirail MOD-in-MOD). MAJOR, weil Befunde nicht nur wegfallen, sondern an anderer Stelle neu entstehen. Prior: // MINOR (CR-SM-321, ITEM-2026-080): R-23 zaehlt den compose-TEILBAUM (subtreeFuncs, dieselbe Zaehlung wie R-04 seit CR-SM-282) statt nur der direkten allocate-Kante. Ein Container-MOD (nur MOD -compose-> MOD, Leitlinie S2) galt als leer und bekam 'remove the module'; jetzt ist leer nur, wessen ganzer Teilbaum keine FUNC traegt. Verengung, kein neuer Befund. Prior: // MAJOR (CR-SM-319, ITEM-2026-064): neue Regel R-32 — jedes realisierte SCHEMA (symbol-realRef, nicht concept) braucht einen Vertragstest (TEST -verify-> SCHEMA); Warnung, Dimension ver, Gate TRR. Prior: // MAJOR (CR-SM-318, ITEM-2026-064): RC-04 prueft auch external-SCHEMAs mit realRef — ein Fremd-API-Vertrag als Zod-Schema im eigenen Code gehoert an seine Grenze; bisher pauschal ausgenommen, jetzt nur noch concept. Prior: // MAJOR (CR-SM-313): R-21 meldet die Uebergabe OHNE gemeinsame Kette -- Befund am SENDER, weil es ohne gemeinsame Kette keinen Kettenanker gibt. CR-GC-315 hatte diesen Fall stillgelegt, weil ein FLOW damals viele Produzenten haben durfte und die P·C-Ableitung Wiederverwendung quadratisch besteuerte; seit IO-02 (CR-SM-307) hat er genau EINEN, die Begruendung ist entfallen. DREI ZWEIGE: (1) ein Ende in GAR KEINER Kette -> still, das ist R-30s Aussage und nicht R-21s -- ohne die Klausel feuert die Regel an einem code-importierten Graphen wie moneyflow 219 von 219 Mal und meldet in Wahrheit 'dieses Repo hat keine Wirkketten', einmal je Kante; (2) gemeinsame Kette, keine davon geprueft -> Befund an der Kette, WOERTLICH unveraendert; (3) beide in Ketten, keine gemeinsame -> Befund am Sender, NEU. AUSNAHME: ist ein Ende Infrastruktur (chains >= policy.criticality.infrastructure, CR-SM-314), schweigt Zweig 3 -- eine Uebergabe an eine Funktion, die quer durch alle Ketten laeuft, ist ein Bus und kein fehlender Integrationstest. GEMESSEN ueber 12 Familiengraphen bei Modellstand 2026-09-12: 500 Uebergabepaare, davon 240 still durch Zweig 1 (219 allein moneyflow -- die Klausel ist der Unterschied zwischen einer Regel und einem Rauschen), 34 Altbefunde, 56 neu roh, 20 durch die Ausnahme freigestellt (alle graphcode, getragen von sechs FUNCs: mutate 11 Ketten, evaluate-rules 6, graph-impact/read-tools/graph-store je 4, list-elements 3), 36 netto neu. R-21 gesamt 34 -> 70. DOMAIN waechst von ['FCHAIN'] auf ['FCHAIN','FUNC']: Zweig 3 verankert am FUNC, und ein Zaehler, der seinen eigenen readiness-Nenner uebersteigt, ist die Fehlmessung aus CR-SM-242. GOLDEN UNVERAENDERT und das ist gemessen: der ssot-Eingang ist docs/graph/sigloch-modules.graph.json mit 0 Uebergabepaaren, n700/n3500 sind Kopien davon. R-21 hatte bis hierher KEINEN Ausloese-Beleg (eine der 42 als unbelegt ausgewiesenen Regeln) -- er kommt mit diesem CR, die Zahl sinkt auf 41. NOCH OFFEN: R-21 haelt fuer die Frage 'teilen diese beiden EINE Kette' weiterhin einen lokalen Ketten-Index, waehrend die ZAHL fuer die Ausnahme aus functionCriticality kommt; ein gemeinsamer Helfer chainsByFunc waere die siebte Datei gewesen und ist ein eigenes Item. Prior: MAJOR (CR-SM-314): neues PFLICHTFELD `criticality` in MetricPolicy (nullable, Default {infrastructure: 3}) und die neue Kennzahl `functionCriticality` -- je FUNC die Zahl der Wirkketten und der Use Cases darueber. MAJOR nach der Praezedenz CR-SM-283 (`boundaryWidth`): ein neues Pflichtfeld laesst JEDEN Konsumenten mit eigener Config hart abbrechen, auch wenn die Oberflaeche nur waechst. KEIN Verstoss aendert sich in dieser Version -- die Kennzahl urteilt nicht, und ihr erster Abnehmer (R-21s Infrastruktur-Ausnahme) landet getrennt als CR-SM-313. DIE DEFINITION IST GELIEHEN: "FUNC in einer Kette" heisst woertlich, was R-30 darunter versteht (direkte `FCHAIN -compose-> FUNC`), ohne Vererbung ueber compose-Vorfahren. Der Rollup wurde gemessen und verworfen -- ueber 17 Familiengraphen haette er die Grundgesamtheit von 224 auf 227 FUNCs gehoben, drei Stueck, und waere ein ZWEITER Kettenbegriff neben R-30s gewesen (die Drift-Klasse aus CR-SM-276). Ein zerlegter Block misst deshalb 0, und das ist richtig: FC-03 verbietet ein Kettenglied mit verschachtelter Zerlegung, er KANN die Kante nicht tragen. KEIN `infrastructure`-Flag an der Kennzahl: ein Flag waere ein Urteil in der Messung, die Schwelle gehoert in die Policy -- dieselbe Trennung, die `moduleMetrics` gegen MT-01/MT-02 haelt. SCHWELLE ABGELESEN, aber NICHT an der Verteilungsform: die ist ein duenner Schwanz (224 FUNCs in 1 Kette, 37 in 2, 6 in 3, 8 in 4, je 1 in 5/6/11) und gibt wie `instability` keine Schwelle her (CR-SM-293). Abgelesen ist die WIRKUNG: auf graphcodes 31 Uebergaben ohne gemeinsame Kette stellen die Schwellen 3 und 4 identisch 19 frei, 5 und 6 identisch 6 -- ein Plateau 3..4, innerhalb dessen die Wahl wirkungsfrei ist; der Startwert ist seine Untergrenze. Prior: MAJOR (CR-SM-312): R-04 misst nur noch die Randbreite des Moduls (verschiedene Vertraege ueber den Modulrand, Schwelle `boundaryWidth` wie BW-02) und steht in STEER_RULES; `policy.moduleSize` entfaellt ersatzlos -- Pflichtfeld weg, Befunde verschieben sich. Prior: MAJOR (CR-SM-311): RD-04 misst die Breite je Ebene als BAND -- neues Pflichtfeld `decompositionBreadth.min` (Default 3, Z3) und die neue Regel RD-05 fuer die Untergrenze (Katalog 70 -> 71), und ein MOD zaehlt Untermodule PLUS eigene Blatt-FUNC (das Allokations-Bein aus CR-SM-296 kommt als Breite zurueck; R-04 wird Kopplung, CR-SM-312). Breaking fuer jede Config (Pflichtfeld) und fuer Befunde (Untergrenze neu, MOD-Breite neu). Prior: MAJOR (CR-SM-307): IO-02 -- ein FLOW hat genau EINEN Produzenten, severity `error`. Katalog 69 -> 70. Reine Ergaenzung im Umfang, MAJOR nach Gate 8: eine neue error-Regel KANN fremde Graphen fallen lassen, und diese tut es -- gemessen ueber fuenf Systeme sind es graphcode 20/42 FLOWs, graph-view-edit 5/8, bok 3/15, waehrend moneyflow (0/220) und test_karp (0/21) sauber sind. Die beiden Referenzen erfuellen sogar die STRENGERE beidseitige Fassung; die Lockerung rettet sie nicht, sie brauchen sie nicht. WARUM NUR DIE PRODUZENTENSEITE: der Inhalt eines FLOW kommt von einer Stelle -- haengen zwei Quellen dran, sind es zwei Fluesse unter einem Namen und kein Leser weiss, welche Fassung er bekommt (Beleg aus dem Bestand: graphcodes FLOW-metric-policy trug ACTOR-owner UND FUNC-load-config, und seine eigene Beschreibung sagte es woertlich -- 'Dieselbe Form in zwei Fassungen, deshalb ein Vertrag'). Mehrere KONSUMENTEN sind dagegen die normale Gestalt eines geteilten Vertrags: eine Quelle, viele Leser, genau wie eine zentrale Konfiguration aussieht. Die beidseitige Fassung wurde GEMESSEN und VERWORFEN -- sie haette allein in graphcode 8 saubere 1:N-Muster bestraft (FLOW-dimension-readiness 1x7, FLOW-steering-snapshot 1x3, sechs weitere) und keinen einzigen zusaetzlichen echten Fehler gefunden; tests/unit/se-rule-trigger-proof.test.ts haelt die Gegenprobe fest, damit sie nicht unbemerkt zurueckkehrt. GATE 4, keine Doppelzaehlung: R-10 prueft die UNTERgrenze derselben Achse (mindestens ein Produzent, mindestens ein Konsument), IO-02 die OBERgrenze der Produzentenseite -- |P| = 0 und |P| > 1 sind disjunkt, die beiden koennen am selben FLOW nie zugleich feuern. Getrennte IDs, weil es getrennte Fragen sind (CR-SM-296): R-10 fragt 'hat dieser Fluss ueberhaupt Enden?', IO-02 'kommt sein Inhalt von einer Stelle?'. WARUM ES BIS HIERHER KEIN GATE PRUEFTE: die vier io-Zeilen in TRACE_PATTERNS tragen kein cardinality-Feld und fallen durch REQUIRED_PATTERNS (= TRACE_PATTERNS.filter(p => p.cardinality === '1')); die SCHEMA-Haelfte IST Grammatik (FLOW -relation-> SCHEMA [1..1], R-18 seit CR-SM-271 Teil 2), die io-Haelfte war nie deklariert -- kein wieder aufgerissenes Loch, die Frage wurde nie gestellt. EIN Befund je FLOW, nicht je ueberzaehliger Kante (Klasse CR-SM-242), value = Zahl der Produzenten, threshold 1 (CR-SM-288); dieselbe Quelle zweimal verdrahtet ist EINE Quelle. NICHT in STEER_RULES, und das ist die wichtigste Abgrenzung: das ist Hygiene, keine Steuerdimension, und der Unterschied ist GEGENLAEUFIG gemessen -- die Reparatur in graphcode (CR-GC-499/500, IO-02 20 -> 9) verbesserte fitAdvisory (coherence +0,111, modifiability +0,072) und verschlechterte den Chebyshev-Abstand um 0,25. KORREKTUR (CR-SM-308): nicht, weil ein aufgetrennter Fluss BW-02 hebt -- moduleCrossings zaehlt verschiedene SCHEMA je Rand, reines Auftrennen laesst BW-02 gleich (gemessen graphcode v257: 15 -> 15); der Anstieg kam von drei neuen SCHEMA und umgelegten Ketten. Die Einordnung als Hygiene statt Steuerdimension bleibt eine Entscheidung, kein gemessener Zielkonflikt. Die neun Befunde, die in graphcode bleiben, sind Busse mit 3 bis 19 Produzenten -- un-modellierte Entwurfsarbeit, kein Regelproblem, eigener Spike. Prior: MINOR (CR-SM-305): die sechs RC-Regeln stehen im KATALOG -- ALL_RULE_DEFS 63 -> 69, mit eigenem Profil `conformance`. Reine Ergaenzung: keine bestehende Regel, Severity oder Schwelle aendert sich, kein Graph faellt, das Golden-File der Regelausgabe steht zeichengleich. ROOT CAUSE: das Kongruenz-Urteil existierte, war getestet und erreichte KEINEN Produktionspfad. Gate (`harness.mutate`), Steuerung (fit-advisory, steering-snapshot) und `GET /api/graph/readiness` lesen alle ALL_RULE_DEFS; RC stand nicht darin, weil seine Signatur `(graph, facts: CodeFacts)` statt `(graph, policy)` ist -- der einzige Aufrufer von `scoreReadinessWithConformance` war ein Test. Ein Modell-Zug, der die Bindung an den Code bricht, passierte damit jedes Gate lautlos. `evaluateAllRules` fuehrt RC weiterhin NICHT aus (es hat keine CodeFacts), und genau diese Differenz ist das Signal: deklariert und nicht ausgewertet heisst NICHT GEPRUEFT; die vorhandene Ableitung `ALL_RULE_DEFS minus ausgewertet` macht es von selbst laut -- kein zweiter Zweig, die ND-fail-open-Lehre aus CR-SM-286. `domain` ist NEU an `ConformanceRuleDefinition` und fuer Konstruierer breaking; die Werte sind GEMESSEN statt gesetzt (der Entwurf des CR schlug `['code']`, `['code','interface']` vor -- `domain` ist aber die Grundgesamtheit, und 'code' ist kein Elementtyp): RC-01 10x FUNC, RC-04 4x SCHEMA, RC-05 5x MOD ueber fuenf Familien-Repos, RC-02/RC-03 aus der Schleife gelesen (TEST/SCHEMA), RC-06 aus ELEMENT_ATTRIBUTES -- nur FUNC/MOD/SCHEMA duerfen `external` + `realRef` tragen. NICHT in RULE_TO_DIMENSION/RULE_TO_PHASE, und das ist die zweite Korrektur am eigenen Entwurf: der readiness-Nenner summiert die Grundgesamtheiten der Regeln einer Dimension, eine deklarierte und nicht ausgewertete Regel HEBT also den Score. Gemessen ueber vier Repos, was das geschenkt haette: graph-view-edit `schema` +5,0 Punkte (Nenner 24 -> 40), graphify `arch` +2,1 und `ver` +1,8, alles Uebrige <= 0,2 -- der Ausschlag sitzt genau in der kleinen Dimension, wo die Zahl am meisten wiegt. `computeApplicable` ueberspringt eine Regel ohne Dimension bereits von selbst; die beiden Vollstaendigkeitstests sind auf die AUSGEWERTETEN Profile eingeengt (READINESS_SCORED_PROFILES) und haben das seit CR-228 in ihrem eigenen Kopf behauptet -- nur war der Katalog bis hierher mit den ausgewerteten Regeln identisch. Der Ausloese-Beleg (CR-SM-285) gilt auch fuer RC: sechs synthetische CodeFacts-Fixturen, kein Checkout, wie `CodeFactsSchema` es seit CR-211 zusagt ('testable without any filesystem') -- die Zahl der unbelegten Regeln bleibt 42 und ist nicht um sechs gestiegen. In se-engine deckt CLASS_MAP die sechs als Constraint ab: wohin eine verwaiste realRef zeigen SOLL, steht in keinem Feld, ein Operator daraus waere geraten. Prior: MINOR (CR-SM-300): `RULE_HELP` -- die Regelhilfe liegt neben der Regel. Reine ERGAENZUNG: kein Katalogeintrag, keine Severity, keine Schwelle aendert sich, kein bestehender Graph faellt. Warum sie umzieht: ein Hilfeeintrag ist keine Annotation UEBER einer Regel, er ist Teil ihrer LIEFERUNG -- eine Regel, deren Befund niemand lesen kann, ist nicht fertig. CR-GC-227 hat die Schicht 2025 bewusst in graphcode gelassen (Tempo: kein Bump, kein Familien-Review) und die Verschiebung ausdruecklich als spaetere Entscheidung notiert; Gate 1 verlangt fuer das Wiederaufmachen eine MESSUNG, und die lag am 2026-09-08 vor: von 73 Eintraegen in graphcodes `HELP_CONTENT` zeigten 11 auf Regel-IDs, die es nicht mehr gab (R-03, R-14, R-27, FC-01, SC-04, CR-R04, AO-D01, AO-D03, RT-01, PH-01, CA-01 -- AO-D03 seit drei Wochen), waehrend 8 Katalogregeln ganz ohne Eintrag dastanden (BW-02, BQ-01, BQ-02, BQ-04, BQ-06, BQ-07, ND-01, ND-02) -- darunter mit BW-02 eine der vier STEUERDIMENSIONEN: eine Regel, die rankt und sich nicht erklaert. Die Drift lief in beide Richtungen und ueber vier Releases, obwohl graphcode sehr wohl eine Vorwaertspruefung hat (`tests/help-content.test.ts`) -- sie war rot und blieb es, weil niemand graphcodes Suite las (CR-GC-488). Eine Pruefung im ungelesenen Repo ist keine Pruefung, und hinter einem Symlink gilt kein Versionsbereich (CR-GC-488 Paragraph 1a). Co-located zwingt die Grammatik selbst: eine Streichung nimmt den Eintrag mit, eine Neuanlage kann ohne ihn nicht landen. Umfang: 69 Eintraege (63 Katalog- + 6 Conformance-Regeln), 18,6 KB reine Strings -- der ./browser-Eintrag bleibt transitiv `node:`-frei (CR-SM-230). Im Golden-File steht nur die SCHLUESSELMENGE (`ruleHelp`), nicht die Prosa: 'Prosa ist ein Patch-Bump, keine Grammatik' (CR-SM-244) -- ein umformulierter Satz darf keinen Bump ausloesen, ein weggefallener Eintrag muss. Abgrenzung zu `fix_hint`, das an jeder Regeldefinition schon steht (Gate 3): `fix_hint` ist die Anweisung an den AGENTEN, `plain`/`se` die Erklaerung fuer den MENSCHEN -- verschiedene Leser, verschiedene Laenge, verschiedene Sprache. NICHT mitgezogen und bewusst in graphcode geblieben: Dashboard-Panels, Artefakte, das Vokabular und die sechs Metrik-Dimensionen (`HELP_METRICS`, CR-GC-458) -- sie haengen an graphcodes Oberflaeche, nicht am Regelkatalog. Prior: BREAKING (CR-SM-293): MT-01 zaehlt KOPPLUNG statt aller Kanten, dreht die Richtung um und urteilt per Default nicht mehr. (1) fan_in/fan_out lasen bis hierher JEDE Kante, deren eines Ende einem Modul gehoerte. Gemessen ueber 19 Familiengraphen: von 3281 gezaehlten Kanten sind nur 1117 (34 %) Kopplung. fan_in 2164 = 674 `allocate` -- das ist die MODULGROESSE, ein Modul wurde also stabiler, indem es Funktionen bekam -- plus 610 `FLOW -io-> FUNC`, 348 `FCHAIN -compose-> FUNC`, 255 `CR -relation-> FUNC`, 277 Rest. fan_out 1117 = 507 `FUNC -io-> FLOW` plus 501 `FUNC -satisfy-> REQ`; fast die halbe fan_out war SPEZIFIKATION, dieselbe Begruendung, mit der CR-SM-297 `satisfy` aus LCOM4 genommen hat. Dazu 70 Kanten ueber `MOD -io-> MOD`, ein Muster, das CR-SM-266 D2 aus TRACE_PATTERNS geloescht hat. MT-01 liest jetzt `moduleCrossings` -- DIESELBE Definition von Rand und dieselbe Zaehlbasis (verschiedene Vertraege), die CR-01, R-04 und BW-02 seit CR-SM-274/276 benutzen; MT-01 hatte bis hierher noch eine dritte. (2) RICHTUNG: der KONSUMENT haengt ab. Wer nach draussen liefert, wird gebraucht (afferent, fan_in); wer von draussen bezieht, haengt ab (efferent, fan_out). Das ist Martins Ca/Ce; die alte Lesart 'Kante zeigt weg = Abhaengigkeit' drehte jedes reine Verbrauchermodul auf I = 0 (maximal stabil), wo es 1 sein muss. Die Konvention stand vorher NIRGENDS im Repo -- sie war nie entschieden, nur codiert. (3) `DEFAULT_METRIC_POLICY.instability` 0.7 -> null, messen statt urteilen. Die Verteilung ist zweigipflig -- 44 von 149 messbaren Modulen exakt auf 0.00, 45 exakt auf 1.00, und 45 der 51 Befunde bei 0.7 sind genau diese 1.00 -- aus ihr laesst sich keine Schwelle ABLESEN (Praezedenz CR-SM-283/BW-02). Tiefer: I ist eine KOORDINATE, kein Defekt; Martins Satz lautet 'in Richtung Stabilitaet abhaengen', nicht 'I klein halten'. Damit fehlt MT-01 die Eigenschaft, die CR-SM-287 §2 von einer Steuerdimension verlangt (gerichtet, 'weniger ist besser, ohne Diskussion'), und (4) `STEER_RULES` schrumpft auf VIER. Praktisch war die Dimension ohnehin still: `steerScore` liest Verstoesse, und graphcodes Config setzt `instability: null` seit CR-GC-329 -- auf dem einzigen Repo, das taeglich steuert, lief der R5 laengst als R4. Der Nachfolger ist gemessen und angelegt (CR-SM-298): Martins Stable Dependencies Principle als gerichtete Groesse, Abhaengigkeiten 'bergauf' je Modul -- 34 von 305 (11 %), 132 von 146 Modulen bei null, Schwanz bis 6; lokal, gerichtet, budgetierbar, nicht entartet. Die Zahl `instability` bleibt vollstaendig erhalten: graph_metrics, Export und das gve-Dashboard rendern sie weiter, letzteres samt Hinweis 'Instabilitaet wird hier nur gemessen'. Prior: BREAKING (CR-SM-297, Teil 1): `satisfy` faellt aus der LCOM4-Verbindung von MT-02. Die Regel misst KOHAESION, und Kohaesion ist Datenkopplung -- geteilte `io`-Ziele und geteilte FLOW. `satisfy` ist dagegen eine SPEZIFIKATIONS-Beziehung: zwei FUNCs, die dasselbe REQ erfuellen, koennen zur Laufzeit vollstaendig entkoppelt sein. Die Wirkung ging dabei nur in EINE Richtung -- `satisfy` verbindet Gruppen, senkte also LCOM4 und liess Module kohaesiver aussehen, als ihr Datenfluss hergibt. Der Modellierungsgrund wiegt schwerer als der Messgrund: teilen sich zwei Funktionen ein Requirement, ist das REQUIREMENT zu zerlegen, nicht die Kohaesionsmessung zu beschoenigen; RD-02 sagt bereits das Verwandte. Gemessen ueber 19 Familiengraphen: MT-02 23 -> 24 Befunde. `satisfy` hat also fast nie zwei Gruppen verbunden -- die Aenderung ist prinzipiell richtig und praktisch billig. Golden neu verankert OHNE Zahlenaenderung: Totals und Reihenfolge bleiben an allen drei Eingaengen gleich (ssot 99, n700 1475, n3500 17495), nur die MT-02-shas wandern, weil die Meldung den gemessenen LCOM4-Wert woertlich nennt. NOCH OFFEN aus CR-SM-297: die beiden Container-Masse fuer den zerlegten FUNC (Kohaesion analog MT-02, Paar-Kopplung analog CR-01) -- ihre Schwellen sind aus der Verteilung ABZULESEN, nicht zu setzen (Praezedenz CR-SM-283/BW-02). Prior: BREAKING (CR-SM-296): je Frage eine Regel, je Schwelle ein Wert. (1) RD-04 gibt das Bein `FUNC -allocate-> MOD` an R-04 ab. Es zaehlte die allozierten FUNCs je Modul -- also die MODULGROESSE, und die misst R-04 auch. Beide feuerten am selben Modul im selben Batch, mit zwei Schwellen aus zwei Policy-Feldern (RD-04 > 11 gegen R-04 > 12), und welche recht hat, stand nirgends; dieselbe Ursache ging doppelt in den readiness-Nenner. R-04 ist die reichere Aussage -- Groesse GEGEN Kreuzungen, drei Urteile -- und liest ueber moduleCrossings ohnehin dieselben Daten. RD-04 bleibt fuer ZERLEGUNGSBREITE zustaendig: FUNC compose FUNC, SYS/MOD compose MOD und der FUNC-Wurzelwald am SYS (CR-SM-282). (2) Beide Schwellen sind an die Doktrin 7+-2 gebunden statt danebengesetzt: decompositionBreadth.warning 11 -> 9 (die Obergrenze der Doktrin) und moduleSize large 12 -> 9, coupled 8 -> 7. CR-SM-282 hatte gegen die Doktrin-Zahl 5 entschieden, weil `info` bei 5 den readiness-Nenner verwaessert haette -- das Argument traegt fuer die OBERgrenze nicht, denn RD-04 meldet als warning. Wirkung ueber 19 Familiengraphen, gemessen: RD-04 17 -> 15, R-04 9 -> 11, Summe 26 -> 26. Die Zahl bleibt, die Zustaendigkeit wird eindeutig. Prior: BREAKING (CR-SM-295): drei Regeln entfallen, eine wird neu gefasst -- Katalog 66 -> 63. (1) R-14 (UC must have compose) kann NIE ALLEIN feuern: sie prueft nur, ob ein UC irgendeine ausgehende compose-Kante hat, ohne den Zieltyp anzusehen, waehrend UC-01 den REQ und UC-03 die FCHAIN einzeln verlangen. Auf ELEMENTEBENE ueber 19 Familiengraphen gemessen, nicht auf Summen: R-14 (2 Befunde) ist eine echte Teilmenge von UC-01 vereinigt UC-03 (23 Befunde). Ihr Fixhinweis ('Add FCHAIN or REQ') versprach zudem eine Pruefung, die der Code nicht macht. (2) FC-01 (FCHAIN has actor boundary) ist von FC-04 gedeckt -- gemessen FC-01 (5) Teilmenge von FC-04 (27); FC-04 verlangt Eingang UND Ausgang, FC-01 nur eine Beruehrung. Dazu trug FC-01 einen TOTEN Ersatzweg: `hasDirectActorIO` prueft, ob der Eltern-UC actor-adjazent ist, und `ACTOR -io-> UC` hat CR-SM-266 (D1/D4) aus TRACE_PATTERNS entfernt -- der Zweig konnte seit drei Versionen nicht mehr wahr werden. (3) CR-R04 entfaellt und CR-R01 wird NEU GEFASST. Beide zielten daneben: CR-R01 akzeptierte jede relation-Kante, also auch einen CR, der nur an einem Meilenstein haengt (Termin, kein Umfang) -- 5 Befunde bei 49 CRs, die ausschliesslich auf ein MS zeigen. CR-R04 verlangte einen FUNC und war damit zu eng: 21 Befunde, darunter Daten-, Doku- und Konfigurationsaenderungen, die zu Recht keine Funktion beruehren. Die neue Fassung fragt nach dem UMFANG: mindestens eine relation auf FUNC, MOD, SCHEMA, REQ oder UC. Grundgesamtheit woertlich die von CR-R04 (CR-SM-255, nur offene CRs) -- an einem geschlossenen CR ist die Frage Archaeologie. Gemessen an 514 CR-Knoten: 54 ohne Umfangsbezug, davon 0 OFFEN, 52 done, 2 ohne Status. Die Verschaerfung laesst die Historie damit unangetastet und wirkt ab dem naechsten CR. MS-03 (CR ohne MS) bleibt unberuehrt -- sie stellt die komplementaere Frage. (4) R-17 behaelt ihre Pruefung und verliert ihren falschen Fixhinweis, der drei Zieltypen nannte, die sie nie angesehen hat. Prior: BREAKING (CR-SM-294): sechs Regeln entfallen ersatzlos, Katalog 72 -> 66. AO-D01 (RelayNodeDetection) ist STRUKTURELL unmoeglich -- sie verlangt `>= 2` ausgehende `io`-Kanten auf andere FUNC, und `FUNC -io-> FUNC` steht nicht in TRACE_PATTERNS; R-18 lehnt es als error ab. Das ist zeichengleich der Fall, fuer den CR-SM-283 AO-D03 geloescht hat -- AO-D03 fiel, AO-D01 blieb stehen. CA-01, RT-01, PH-01, R-27 und R-03 sind GEGENSTANDSLOS: sie lesen `asil`, `attributes.kind === 'physical'`, `attributes.requires` und `attributes.capability`, und ueber 19 Familiengraphen ist jedes dieser Attribute 0-mal gesetzt (305 MOD, 696 FUNC). Beleg beidseitig: `report:silence` meldet alle sechs mit 0 Befunden ueber den gesamten Bestand, UND keine von ihnen hat eine Ausloese-Fixture in se-rule-trigger-proof.test.ts (CR-SM-285) -- also weder feuert sie irgendwo, noch ist belegt, dass sie ueberhaupt feuern KANN. Genau die Doppelbedingung, die Gate 7 fuer eine Streichung verlangt. NICHT gestrichen wurde CL-01, obwohl derselbe Verdacht bestand: sie feuert 2x in 1 von 19 Graphen. Ihre Aussage waere anderswo besser aufgehoben -- jeder UC muss hinreichend distinct sein, und das saehe man an der FCHAIN -- aber diese Regel EXISTIERT NICHT: CR-SM-283 hat AO-D03 geloescht und dabei festgehalten, dass ihre Aufgabe ('zwei Wirkketten, die auf einer Ebene mitgliedsgleich werden') ungeloest bleibt. CL-01 zu streichen hiesse, eine feuernde Pruefung gegen eine nicht existierende zu tauschen. Wirkung auf die Bestandsgraphen: 0 -> 0 Befunde, kein Graph faellt. Die einzige messbare Folge ist der kleinere NENNER jeder readiness-Dimension -- sechs stumme Regeln hoben bisher jeden Score. Prior: BREAKING (CR-SM-286): ND-01/ND-02 rechnen ihre Aehnlichkeit SELBST; setND01SimilarityMatrix/setND02SimilarityMatrix/getND02SimilarityMatrix entfallen ersatzlos. Die Formel stand als Prosa in contracts und als Code in graphcode/src/kernel/measure/nd-similarity.ts, verbunden durch eine Injektionsnaht. Vier gemessene Kosten: (1) FAIL-OPEN -- ohne Injektion gaben zwei `error`-Regeln [] zurueck, ununterscheidbar von 'keine Duplikate'; moneyflow trug 16 ND-01-Befunde, die ausserhalb graphcodes NIEMAND sah (report:silence, die Spike-Skripte, jedes kuenftige Werkzeug). Die Familie hat fuer 'kann nicht urteilen' eine Konvention (policy.X = null, moduleMetrics.instability = null); ND fiel statt dessen nach gruen. (2) PROZESSWEITER MODULZUSTAND -- `let _nd01Matrix` ist global, graphcode brauchte die Klammer withNDMatrices mit finally-Reset, jeder andere Aufrufer nicht: Graph A urteilte ueber Graph B. (3) AO-D01 liegt IM Gate-Katalog und uebersprang seine Overlap-Pruefung ohne Matrix ('no matrix -> assume pass') -- eine Gate-Regel, die sich stillschweigend abschwaecht. (4) BQ-04 haengt am selben Muster und ist komplett tot. Jetzt: similarity.ts, je Graph WeakMap-gecacht, Formeln und Schwelle 0,85 zeichengleich uebernommen -- dasselbe Urteil an der richtigen Stelle, Praezedenz CR-SM-276 (moduleCrossings). DABEI GEFUNDEN UND BEHOBEN: die portierte Formel las Partner ueber ALLE Traces; se-rule-pair-legality (Pruefung A, CR-SM-278) meldete 35 Faelle, in denen ND-02 erst durch eine grammatikwidrige Kante auf SCHEMA anschlug (ACTOR -compose-> SCHEMA und Verwandte). `partners()` filtert jetzt ueber isValidTrace -- eine Regel darf ihr Verdikt nicht an einer Kante haengen, die R-18 als error ablehnt. Golden bewusst neu verankert und im Test-Header aufgeschluesselt: ssot 98 -> 98 UNVERAENDERT, n700 1038 -> 1478, n3500 5176 -> 17496, der gesamte Zuwachs ist ND-01 auf den synthetischen Eingaengen -- buildFixedSizeGraph KOPIERT den Basisgraphen 11x bzw. 56x, dort hat jedes Element 10 bzw. 55 exakte Duplikate; eine Duplikatsregel MUSS das melden. ND-02 bleibt ueberall 0. BQ-04 ausdruecklich NICHT angeschlossen: es verlangt 'pre-computed EMBEDDING similarity', und Embeddings kann ein reines Vertragspaket nicht rechnen; ein Ersatzmass haette ich erfinden muessen, und der Versuch (0.7*Beschreibung + 0.3*Name) lieferte 0 Befunde an allen neun Familiengraphen und 4950 an einer templatierten Fixture -- beide Gate-7-Ausreisser zugleich. BQ-04 ist damit der naechste AO-D03-Fall und ein eigener se-grammar-review-Vorgang. Prior: BREAKING (CR-SM-285): R-12 findet Zyklen JEDER Laenge statt nur Rueckkanten. Die Regel heisst 'No circular dependencies' und prueft bis hierher nur, ob eine Kante DIREKT zurueckliuft (A->B und B->A). Ein Dreierzyklus A->B->C->A ging vollstaendig durch das Gate, mit null Fehlern. Das ist die gefaehrlichere Haelfte der Fehlerklasse dieser Session: ein Absturz meldet sich, eine Pruefung die ihren Gegenstand nicht erreicht BESTAETIGT. Warum es zaehlt: jeder Rollup der Familie setzt Azyklizitaet voraus -- chainOf/funcChainOf (CR-SM-282/-283) tragen seen-Guards gegen genau diese Endlosschleife, moduleMetrics, die RD-04-Breitenzaehlung und gves Container-Sicht ebenso; unter einem Zyklus ist ein Rollup nicht falsch sondern UNDEFINIERT. Das R-18-Bein aus CR-SM-283 deckt es NICHT: bei A->B->C->A hat jeder Knoten genau EINEN compose-Elternteil, die Baum-Eigenschaft ist formal erfuellt und die Struktur trotzdem kein Baum. Gemessen ueber NEUN Familiengraphen: 0 Zyklen jeder Laenge, also 0 -> 0 Befunde -- die Schaerfung laesst keinen bestehenden Graphen fallen. Major nach Gate 8 trotzdem, weil eine verschaerfte Regel fremde Graphen fallen lassen KANN. EIN Befund je Zyklus statt je Kante (Klasse CR-SM-242), verankert am kleinsten Knoten des Rings und damit unabhaengig vom DFS-Einstieg (Gate 5); die Zweierring-Meldung ist woertlich unveraendert. Dazu zwei Haertungen OHNE Regelwirkung: descriptionOf() gab `el.description ?? ''` und starb an jedem Nicht-String (42/{}/[]/true -> '.trim is not a function', vier von fuenf Typen) -- am Gate hiess das Stacktrace statt block-Verdict; jetzt zaehlt nur ein echter String, alles andere ist 'keine Beschreibung'. Und tests/unit/se-rule-trigger-proof.test.ts ist die Umkehrung von report:silence: je Regel ein Graph der sie feuern LASSEN MUSS, 22 belegt, 50 als unbelegt ausgewiesen mit einer Zahl die nur sinken darf. Beim ersten Lauf fand er sofort einen Fall: ND-01/ND-02 sind `error` und beginnen mit `if (!_nd01Matrix) return []` -- ohne injizierte Aehnlichkeitsmatrix fallen sie nach GRUEN, ununterscheidbar von 'keine Duplikate'. Dokumentiert und eingefroren, nicht behoben: die Severity-/Signalfrage ist ein eigener se-grammar-review-Vorgang. Prior: BREAKING (CR-SM-283): AO-D03 entfaellt ERSATZLOS, BW-02 kommt -- Katalog 74 -> 74, kein Wachstum im Komplexitaetsbudget. (1) AO-D03 (`DuplicatePathDetection`) suchte `FUNC -io-> FUNC`, ein Paar das TRACE_PATTERNS nicht kennt und das R-18 als error ablehnt: die Regel konnte STRUKTURELL nicht feuern und hat es an keinem der fuenf Familiengraphen je getan. Eine Regel die nie feuert kostet trotzdem Nenner-Anteil, Katalogzeile, readiness-Zuordnung, Golden-File-Eintrag und einen Leser der sie unterscheiden muss. Die Aufgabe die sie tragen SOLLTE -- zwei Wirkketten die auf einer Ebene mitgliedsgleich werden -- bleibt ungeloest und ist ein eigener CR; BW-02 ersetzt sie nicht. (2) BW-02 misst die Randbreite der FUNC-WHITEBOX: verschiedene SCHEMA-Vertraege auf `FUNC -io-> FLOW -io-> FUNC` mit einem Endpunkt im compose-Teilbaum und einem ausserhalb. Gerechnet im selben Durchlauf wie `byModule` (module-crossings.ts), damit es EINE Definition von Rand gibt. Der Rollup ist hier nicht Genauigkeit sondern Existenz: ein zerlegter FUNC traegt selbst keine io-Kanten (die liegen an seinen Blaettern), ohne Teilbaum-Aufloesung waere die Zahl strukturell immer 0 -- gemessen FUNC-block-grounding 0 -> 19. Gate 6 des Grammatik-Reviews: 12 Befunde an graphcode, davon 0 in Ueberlappung mit irgendeiner FUNC-Regel (AO-D01 misst Relay-Knoten, IO-01 Kettenkohaerenz, R-31 blosse Verdrahtung). Schwelle 5 aus der Verteilung ABGELESEN, nicht gewaehlt: bok und graph-view-edit enden beide bei 3, graphcode geht bis 19, moneyflow bis 17. Kein info-Level -- bei Schwelle 3 truege bok 4 Befunde auf 9 Blackboxes, und readiness zaehlt info ungefiltert in den Nenner (MT-03-Praezedenz). (3) VIERTES BEIN von R-18: ein Element hat hoechstens einen compose-Elternteil (FUNC/FUNC und MOD/MOD), error. TRACE_PATTERNS begrenzt heute nur die KINDER einer Kante, nie die ELTERN -- graph_mutate legt eine zweite compose-Kante anstandslos an. 0 Verstoesse ueber fuenf Graphen, und trotzdem hart, weil jeder Rollup der Familie unter Verletzung nicht falsch sondern UNDEFINIERT ist (byModule/byFunc, RD-04-Breite, die Container-Sicht von gve). Keine eigene Regel-ID, Praezedenz CR-SM-271 Teil 2: dieselbe Aussage, derselbe naechste Schritt. EIN Befund je KIND, nicht je ueberzaehliger Kante. (4) `policy.boundaryWidth` neu (nullable, Default {warning: 5}) -- fuer Konstruierer einer MetricPolicy breaking. Prior: BREAKING (CR-SM-282): zwei Blackbox-Luecken geschlossen, und beide lassen bestehende Graphen fallen. (1) RD-04 zaehlt den FUNC-WURZELWALD, verankert am SYS-Knoten. Die Regel zaehlt Kinder je Parent; die Wurzeln des `FUNC -compose-> FUNC`-Waldes haben keinen, also war die oberste Funktionsebene unsichtbar -- gemessen: moneyflow hat 306 Wurzel-FUNCs und RD-04 meldete 0. Die MOD-Seite hatte das Problem nie, weil `SYS -compose-> MOD` eine echte Kante ist; die FUNC-Seite hat keine (`se:top-level`: der obere FUNC-Satz ist eine PROJEKTION, keine Kante). Anker ist der einzige SYS-Knoten; gibt es nicht genau einen, schweigt der Zweig statt zu raten (R-17 deckt den Fall). (2) `moduleCrossings.byModule` und die Groesse in R-04 lesen jetzt die BESITZKETTE (direkt alloziertes MOD + compose-Vorfahren) statt nur der direkten Allokation. Ein Eltern-MOD hat keine direkt allozierten FUNCs -- es kam in `byModule` nicht vor, `funcCount` war 0, und R-04 schwieg per `funcCount <= coupled` an genau der Whitebox, in die der Leser hineinklickt. `pairs` (CR-01) bleibt BEWUSST auf der direkten Zuordnung: 'wie stark haengen DIESE zwei Module aneinander' ist eine Blattebenen-Frage, ein Elternpaar meldete dieselbe Kopplung ein zweites Mal -- CR-01 ist an allen fuenf Familiengraphen zeichengleich geblieben (5/3/15/0/63). Delta gesamt: moneyflow RD-04 +1, sirail R-04 +1, bok/gve/graphcode unveraendert. (3) Die Schwelle steht in `policy.decompositionBreadth` statt als `DECOMPOSITION_BREADTH_MAX = 11` inline in rules.ts -- dieselbe Klasse, die CR-SM-236 fuer R-04 aufgeloest hat; `null` schaltet die Regel ab, Default 11 = verhaltensgleich. Das neue Pflichtfeld in MetricPolicy ist fuer Konstruierer breaking. Die Doktrin-Zahl 5 (`se:top-level`) ist bewusst NICHT der Default: `info` bei 5 haette an graphcode 20 zusaetzliche Befunde erzeugt, und readiness zaehlt `info` ungefiltert in den Nenner (`score = 1 - Verstoesse / applicable`) -- genau der Grund, aus dem MT-03 zur Messung statt zur Regel wurde (readiness.ts). Wer doktrin-streng fahren will, setzt 5. Prior: BREAKING (CR-SM-271 Teil 2): SC-04 entfaellt ersatzlos. Ein FLOW ohne SCHEMA ist kein schwaecherer Typ, sondern untypisiert — die Untergrenze ist Grammatik geworden (`FLOW -relation-> SCHEMA [1..1]`, META_MODEL 5.0.0) und meldet als drittes Bein von R-18 (error, mit candidate_targets im Kontext wie zuvor SC-04). Kein Regel-Loch: derselbe Sachverhalt, EIN Befund, nur haerter und am Gate durchgesetzt statt nur gemeldet. Der Katalog schrumpft um eine Zeile (Nenner, readiness-Zuordnung 'schema'/CDR und Golden-File-Eintrag fallen mit); der Fall zaehlt jetzt unter R-18/'arch'/PDR.
|
|
9
9
|
/** Meta-model version (trace pattern constraints + format-e parser). */
|
|
10
|
-
export const META_MODEL_VERSION = '5.
|
|
10
|
+
export const META_MODEL_VERSION = '5.2.0'; // MINOR (CR-SM-341): `CR -relation-> SCHEMA` ergaenzt -- reine Erweiterung, keine Bestandskante wird ungueltig. Die Kante war der einzige Punkt, an dem zwei Regeln DESSELBEN Katalogs einander widersprachen: CR-R01 verlangt einen Umfang und nennt SCHEMA ausdruecklich (SCOPE_TYPES = FUNC/MOD/SCHEMA/REQ/UC, cr-quality-rules.ts), R-18 wies genau diese Kante ab, weil die Matrix fuer CR nur MS/UC/REQ/FUNC/MOD kannte. Gemessen an zwei Audit-Logs: 6 Ablehnungen in graphcode, 3 in sigllm -- jedes Mal ein CR, dessen Umfang ein Vertrag ist. Erweitert statt den fixHint zu kuerzen: ein CR, der eine Schnittstelle schneidet, hat genau dort seinen Umfang. Gleicher Zug, ohne Oberflaechenwirkung: R-18 haengt seinen Ablehnungsgrund als `context.trace_rejection` an den Befund (neuer Export `admittingAlternatives`). Prior: // MINOR (CR-SM-317, ITEM-2026-064): neues Muster `TEST -verify-> SCHEMA` — der Vertragstest haengt am Vertrag, nicht an einer Hilfs-REQ; additiv, kein bestehender Graph faellt. Prior: // BREAKING (CR-SM-271 Teil 2): `FLOW -relation-> SCHEMA` wird `1..1` — die erste DURCHGESETZTE Untergrenze (minOccurs/REQUIRED_PATTERNS; nur exakt-`1`, die `1..*`-compose-Untergrenzen bleiben Vollstaendigkeits-Warnungen ihrer Regeln). Migrationsbestand vorab abgetragen: graphcode 2, sigloch-modules 4, graph-view-edit 5 (je durchs Gate), urbanmobility-Fixture geloescht (39, Spike ohne Referenzen); sirail 4 offen (EXPORT_PENDING-Divergenz seit 2026-08-18).
|
|
11
11
|
export * from './ontology.js';
|
|
12
12
|
export * from './rules.js';
|
|
13
13
|
export * from './conformance-rules.js';
|
package/dist/se/meta-model.d.ts
CHANGED
|
@@ -114,5 +114,27 @@ export type TraceRejection =
|
|
|
114
114
|
pattern: TracePattern;
|
|
115
115
|
declared: readonly string[];
|
|
116
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Die Enden, mit denen dieselbe Kante zulaessig WAERE (CR-SM-341).
|
|
119
|
+
*
|
|
120
|
+
* Der Anlass ist gemessen: `traceRejection` rechnet den Ablehnungsgrund vollstaendig aus,
|
|
121
|
+
* `explainRejection` giesst ihn in Prosa, und dort endet er. Ueber einen ganzen
|
|
122
|
+
* Spezifikationslauf trugen 48 von 56 R-18-Befunden keinerlei `context` — kein Konsument
|
|
123
|
+
* konnte handeln, ohne die Meldung zu parsen. Der Hinweis sagt woertlich "use a source type
|
|
124
|
+
* whose satisfy pattern admits them"; WELCHER das ist, stand nirgends.
|
|
125
|
+
*
|
|
126
|
+
* Kein zweiter Entscheidungspfad: beide Listen entstehen, indem `isValidTrace` mit dem
|
|
127
|
+
* getauschten Ende erneut befragt wird. Was hier als Alternative erscheint, haette die
|
|
128
|
+
* urteilende Funktion also auch angenommen.
|
|
129
|
+
*
|
|
130
|
+
* `sources` beantwortet den Kind-Konflikt (ein REQ mit `non-functional` wird von MOD
|
|
131
|
+
* erfuellt, nicht von FUNC), `traceTypes` den Grammatikbruch (welcher Kantentyp verbindet
|
|
132
|
+
* dieses Typ-Paar ueberhaupt). Beide koennen leer sein — dann gibt es keine, und das ist
|
|
133
|
+
* eine Aussage, kein Fehler.
|
|
134
|
+
*/
|
|
135
|
+
export declare function admittingAlternatives(trace: TraceShape, patterns?: TracePattern[]): {
|
|
136
|
+
sources: ElementType[];
|
|
137
|
+
traceTypes: TraceType[];
|
|
138
|
+
};
|
|
117
139
|
/** `undefined`, wenn die Kante gueltig ist — sonst der Grund. */
|
|
118
140
|
export declare function traceRejection(trace: TraceShape, patterns?: TracePattern[]): TraceRejection | undefined;
|
package/dist/se/meta-model.js
CHANGED
|
@@ -106,6 +106,13 @@ export const TRACE_PATTERNS = [
|
|
|
106
106
|
{ source: 'CR', target: 'REQ', type: 'relation', description: 'CR affects requirement' },
|
|
107
107
|
{ source: 'CR', target: 'FUNC', type: 'relation', description: 'CR affects function' },
|
|
108
108
|
{ source: 'CR', target: 'MOD', type: 'relation', description: 'CR affects module' },
|
|
109
|
+
// CR-SM-341: `CR -relation-> SCHEMA` war die Kante, die CR-R01 VERLANGT und R-18 ABWIES.
|
|
110
|
+
// Die Regel nennt SCHEMA seit jeher als Umfang ("no relation to FUNC/MOD/SCHEMA/REQ/UC",
|
|
111
|
+
// SCOPE_TYPES in cr-quality-rules.ts), die Matrix kannte sie nicht — gemessen 6 Ablehnungen
|
|
112
|
+
// im graphcode-Log und 3 im sigllm-Log, jedes Mal ein CR, der einen Vertrag aendert.
|
|
113
|
+
// Erweitert statt den fixHint zu kuerzen: ein CR, der eine Schnittstelle schneidet, hat
|
|
114
|
+
// genau dort seinen Umfang, und der Vertrag ist die Stelle, an der es weh tut.
|
|
115
|
+
{ source: 'CR', target: 'SCHEMA', type: 'relation', description: 'CR affects contract' },
|
|
109
116
|
// CR-SM-266 D5: `SESSION -produces-> *` ENTFAELLT ersatzlos, mit ihm der TraceType `produces`
|
|
110
117
|
// und der ElementType `SESSION`. Der urspruengliche Entwurf hielt das Pattern fuer die
|
|
111
118
|
// Provenance-Kante und wollte es behalten; die MESSUNG hat das widerlegt: 0 produces-Kanten
|
|
@@ -193,6 +200,39 @@ function matchesShape(p, trace) {
|
|
|
193
200
|
p.type === trace.type &&
|
|
194
201
|
(!p.label || p.label === trace.label);
|
|
195
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Die Enden, mit denen dieselbe Kante zulaessig WAERE (CR-SM-341).
|
|
205
|
+
*
|
|
206
|
+
* Der Anlass ist gemessen: `traceRejection` rechnet den Ablehnungsgrund vollstaendig aus,
|
|
207
|
+
* `explainRejection` giesst ihn in Prosa, und dort endet er. Ueber einen ganzen
|
|
208
|
+
* Spezifikationslauf trugen 48 von 56 R-18-Befunden keinerlei `context` — kein Konsument
|
|
209
|
+
* konnte handeln, ohne die Meldung zu parsen. Der Hinweis sagt woertlich "use a source type
|
|
210
|
+
* whose satisfy pattern admits them"; WELCHER das ist, stand nirgends.
|
|
211
|
+
*
|
|
212
|
+
* Kein zweiter Entscheidungspfad: beide Listen entstehen, indem `isValidTrace` mit dem
|
|
213
|
+
* getauschten Ende erneut befragt wird. Was hier als Alternative erscheint, haette die
|
|
214
|
+
* urteilende Funktion also auch angenommen.
|
|
215
|
+
*
|
|
216
|
+
* `sources` beantwortet den Kind-Konflikt (ein REQ mit `non-functional` wird von MOD
|
|
217
|
+
* erfuellt, nicht von FUNC), `traceTypes` den Grammatikbruch (welcher Kantentyp verbindet
|
|
218
|
+
* dieses Typ-Paar ueberhaupt). Beide koennen leer sein — dann gibt es keine, und das ist
|
|
219
|
+
* eine Aussage, kein Fehler.
|
|
220
|
+
*/
|
|
221
|
+
export function admittingAlternatives(trace, patterns = TRACE_PATTERNS) {
|
|
222
|
+
const sources = [
|
|
223
|
+
...new Set(patterns
|
|
224
|
+
.filter(p => p.source !== '*' && p.source !== trace.source)
|
|
225
|
+
.map(p => p.source)
|
|
226
|
+
.filter(src => isValidTrace({ ...trace, source: src }, patterns))),
|
|
227
|
+
].sort();
|
|
228
|
+
const traceTypes = [
|
|
229
|
+
...new Set(patterns
|
|
230
|
+
.filter(p => p.type !== trace.type)
|
|
231
|
+
.map(p => p.type)
|
|
232
|
+
.filter(type => isValidTrace({ ...trace, type }, patterns))),
|
|
233
|
+
].sort();
|
|
234
|
+
return { sources, traceTypes };
|
|
235
|
+
}
|
|
196
236
|
/** `undefined`, wenn die Kante gueltig ist — sonst der Grund. */
|
|
197
237
|
export function traceRejection(trace, patterns = TRACE_PATTERNS) {
|
|
198
238
|
if (isValidTrace(trace, patterns))
|
package/dist/se/readiness.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/se/readiness.js
CHANGED
|
@@ -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.d.ts
CHANGED
|
@@ -81,6 +81,75 @@ export declare const ViolationContext: z.ZodObject<{
|
|
|
81
81
|
parent_module: z.ZodOptional<z.ZodString>;
|
|
82
82
|
current_description: z.ZodOptional<z.ZodString>;
|
|
83
83
|
also_affects: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
84
|
+
trace_rejection: z.ZodOptional<z.ZodObject<{
|
|
85
|
+
reason: z.ZodEnum<{
|
|
86
|
+
"no-pattern": "no-pattern";
|
|
87
|
+
"kinds-undeclared": "kinds-undeclared";
|
|
88
|
+
"kinds-mismatch": "kinds-mismatch";
|
|
89
|
+
}>;
|
|
90
|
+
source_type: z.ZodEnum<{
|
|
91
|
+
SYS: "SYS";
|
|
92
|
+
UC: "UC";
|
|
93
|
+
ACTOR: "ACTOR";
|
|
94
|
+
FCHAIN: "FCHAIN";
|
|
95
|
+
FUNC: "FUNC";
|
|
96
|
+
FLOW: "FLOW";
|
|
97
|
+
REQ: "REQ";
|
|
98
|
+
TEST: "TEST";
|
|
99
|
+
MOD: "MOD";
|
|
100
|
+
SCHEMA: "SCHEMA";
|
|
101
|
+
CR: "CR";
|
|
102
|
+
MS: "MS";
|
|
103
|
+
}>;
|
|
104
|
+
target_type: z.ZodEnum<{
|
|
105
|
+
SYS: "SYS";
|
|
106
|
+
UC: "UC";
|
|
107
|
+
ACTOR: "ACTOR";
|
|
108
|
+
FCHAIN: "FCHAIN";
|
|
109
|
+
FUNC: "FUNC";
|
|
110
|
+
FLOW: "FLOW";
|
|
111
|
+
REQ: "REQ";
|
|
112
|
+
TEST: "TEST";
|
|
113
|
+
MOD: "MOD";
|
|
114
|
+
SCHEMA: "SCHEMA";
|
|
115
|
+
CR: "CR";
|
|
116
|
+
MS: "MS";
|
|
117
|
+
}>;
|
|
118
|
+
trace_type: z.ZodEnum<{
|
|
119
|
+
compose: "compose";
|
|
120
|
+
io: "io";
|
|
121
|
+
satisfy: "satisfy";
|
|
122
|
+
verify: "verify";
|
|
123
|
+
allocate: "allocate";
|
|
124
|
+
relation: "relation";
|
|
125
|
+
}>;
|
|
126
|
+
target_id: z.ZodString;
|
|
127
|
+
field: z.ZodOptional<z.ZodString>;
|
|
128
|
+
allowed: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
129
|
+
declared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
130
|
+
candidate_sources: z.ZodArray<z.ZodEnum<{
|
|
131
|
+
SYS: "SYS";
|
|
132
|
+
UC: "UC";
|
|
133
|
+
ACTOR: "ACTOR";
|
|
134
|
+
FCHAIN: "FCHAIN";
|
|
135
|
+
FUNC: "FUNC";
|
|
136
|
+
FLOW: "FLOW";
|
|
137
|
+
REQ: "REQ";
|
|
138
|
+
TEST: "TEST";
|
|
139
|
+
MOD: "MOD";
|
|
140
|
+
SCHEMA: "SCHEMA";
|
|
141
|
+
CR: "CR";
|
|
142
|
+
MS: "MS";
|
|
143
|
+
}>>;
|
|
144
|
+
candidate_trace_types: z.ZodArray<z.ZodEnum<{
|
|
145
|
+
compose: "compose";
|
|
146
|
+
io: "io";
|
|
147
|
+
satisfy: "satisfy";
|
|
148
|
+
verify: "verify";
|
|
149
|
+
allocate: "allocate";
|
|
150
|
+
relation: "relation";
|
|
151
|
+
}>>;
|
|
152
|
+
}, z.core.$strip>>;
|
|
84
153
|
value: z.ZodOptional<z.ZodNumber>;
|
|
85
154
|
threshold: z.ZodOptional<z.ZodNumber>;
|
|
86
155
|
}, z.core.$strip>;
|
|
@@ -143,6 +212,75 @@ export declare const RuleViolation: z.ZodObject<{
|
|
|
143
212
|
parent_module: z.ZodOptional<z.ZodString>;
|
|
144
213
|
current_description: z.ZodOptional<z.ZodString>;
|
|
145
214
|
also_affects: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
215
|
+
trace_rejection: z.ZodOptional<z.ZodObject<{
|
|
216
|
+
reason: z.ZodEnum<{
|
|
217
|
+
"no-pattern": "no-pattern";
|
|
218
|
+
"kinds-undeclared": "kinds-undeclared";
|
|
219
|
+
"kinds-mismatch": "kinds-mismatch";
|
|
220
|
+
}>;
|
|
221
|
+
source_type: z.ZodEnum<{
|
|
222
|
+
SYS: "SYS";
|
|
223
|
+
UC: "UC";
|
|
224
|
+
ACTOR: "ACTOR";
|
|
225
|
+
FCHAIN: "FCHAIN";
|
|
226
|
+
FUNC: "FUNC";
|
|
227
|
+
FLOW: "FLOW";
|
|
228
|
+
REQ: "REQ";
|
|
229
|
+
TEST: "TEST";
|
|
230
|
+
MOD: "MOD";
|
|
231
|
+
SCHEMA: "SCHEMA";
|
|
232
|
+
CR: "CR";
|
|
233
|
+
MS: "MS";
|
|
234
|
+
}>;
|
|
235
|
+
target_type: z.ZodEnum<{
|
|
236
|
+
SYS: "SYS";
|
|
237
|
+
UC: "UC";
|
|
238
|
+
ACTOR: "ACTOR";
|
|
239
|
+
FCHAIN: "FCHAIN";
|
|
240
|
+
FUNC: "FUNC";
|
|
241
|
+
FLOW: "FLOW";
|
|
242
|
+
REQ: "REQ";
|
|
243
|
+
TEST: "TEST";
|
|
244
|
+
MOD: "MOD";
|
|
245
|
+
SCHEMA: "SCHEMA";
|
|
246
|
+
CR: "CR";
|
|
247
|
+
MS: "MS";
|
|
248
|
+
}>;
|
|
249
|
+
trace_type: z.ZodEnum<{
|
|
250
|
+
compose: "compose";
|
|
251
|
+
io: "io";
|
|
252
|
+
satisfy: "satisfy";
|
|
253
|
+
verify: "verify";
|
|
254
|
+
allocate: "allocate";
|
|
255
|
+
relation: "relation";
|
|
256
|
+
}>;
|
|
257
|
+
target_id: z.ZodString;
|
|
258
|
+
field: z.ZodOptional<z.ZodString>;
|
|
259
|
+
allowed: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
260
|
+
declared: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
261
|
+
candidate_sources: z.ZodArray<z.ZodEnum<{
|
|
262
|
+
SYS: "SYS";
|
|
263
|
+
UC: "UC";
|
|
264
|
+
ACTOR: "ACTOR";
|
|
265
|
+
FCHAIN: "FCHAIN";
|
|
266
|
+
FUNC: "FUNC";
|
|
267
|
+
FLOW: "FLOW";
|
|
268
|
+
REQ: "REQ";
|
|
269
|
+
TEST: "TEST";
|
|
270
|
+
MOD: "MOD";
|
|
271
|
+
SCHEMA: "SCHEMA";
|
|
272
|
+
CR: "CR";
|
|
273
|
+
MS: "MS";
|
|
274
|
+
}>>;
|
|
275
|
+
candidate_trace_types: z.ZodArray<z.ZodEnum<{
|
|
276
|
+
compose: "compose";
|
|
277
|
+
io: "io";
|
|
278
|
+
satisfy: "satisfy";
|
|
279
|
+
verify: "verify";
|
|
280
|
+
allocate: "allocate";
|
|
281
|
+
relation: "relation";
|
|
282
|
+
}>>;
|
|
283
|
+
}, z.core.$strip>>;
|
|
146
284
|
value: z.ZodOptional<z.ZodNumber>;
|
|
147
285
|
threshold: z.ZodOptional<z.ZodNumber>;
|
|
148
286
|
}, z.core.$strip>>;
|
package/dist/se/rules.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod/v4';
|
|
7
7
|
import { ElementType, TraceType, TestRefsSchema, RealRefSchema } from './ontology.js';
|
|
8
|
-
import { traceRejection, BOUNDED_PATTERNS, REQUIRED_PATTERNS, maxOccurs } from './meta-model.js';
|
|
8
|
+
import { traceRejection, admittingAlternatives, isValidTrace, 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({
|
|
@@ -35,6 +35,38 @@ export const ViolationContext = z.object({
|
|
|
35
35
|
* unterschlagen.
|
|
36
36
|
*/
|
|
37
37
|
also_affects: z.array(z.string()).optional(),
|
|
38
|
+
/**
|
|
39
|
+
* CR-SM-341: der Ablehnungsgrund von R-18, maschinenlesbar statt nur als Satz.
|
|
40
|
+
*
|
|
41
|
+
* `traceRejection` rechnet ihn ohnehin aus; bis hierher landete er ausschliesslich in
|
|
42
|
+
* `message`/`fix_hint`. Gemessen an einem vollen Spezifikationslauf: 48 von 56
|
|
43
|
+
* R-18-Befunden trugen gar keinen `context`, und die Fix-Vorlage stieg deshalb in ihrer
|
|
44
|
+
* ersten Zeile aus — die Regel mit den meisten Ablehnungen war die einzige ohne
|
|
45
|
+
* ausfuehrbare Empfehlung. Ein Feld, keine sieben: die Teile gehoeren zusammen, und
|
|
46
|
+
* getrennte Optionals liessen halbe Befunde zu.
|
|
47
|
+
*/
|
|
48
|
+
trace_rejection: z.object({
|
|
49
|
+
reason: z.enum(['no-pattern', 'kinds-undeclared', 'kinds-mismatch']),
|
|
50
|
+
source_type: ElementType,
|
|
51
|
+
target_type: ElementType,
|
|
52
|
+
trace_type: TraceType,
|
|
53
|
+
/**
|
|
54
|
+
* Das ZIEL der beanstandeten Kante. `element_id` nennt nur die Quelle; ohne dieses Feld
|
|
55
|
+
* muesste ein Konsument die Kante im Graphen suchen — und bei einem dryRun, der genau
|
|
56
|
+
* diese Kante gerade abgelehnt hat, steht sie im Arbeitsgraphen nicht. Gemessen beim
|
|
57
|
+
* Bau der Fix-Vorlage (CR-SM-342): so lieferte der Zweig fuer 39 Befunde `null`, nicht
|
|
58
|
+
* weil kein Zug existierte, sondern weil das Ziel unauffindbar war.
|
|
59
|
+
*/
|
|
60
|
+
target_id: z.string(),
|
|
61
|
+
/** Nur bei den beiden kinds-Gruenden: das Praedikatsfeld und seine erlaubte Menge. */
|
|
62
|
+
field: z.string().optional(),
|
|
63
|
+
allowed: z.array(z.string()).optional(),
|
|
64
|
+
declared: z.array(z.string()).optional(),
|
|
65
|
+
/** Quelltypen, mit denen dieselbe Kante zulaessig waere (leer = keine). */
|
|
66
|
+
candidate_sources: z.array(ElementType),
|
|
67
|
+
/** Kantentypen, die dieses Typ-Paar verbinden (leer = keine). */
|
|
68
|
+
candidate_trace_types: z.array(TraceType),
|
|
69
|
+
}).optional(),
|
|
38
70
|
/**
|
|
39
71
|
* CR-SM-288: die gemessene Zahl der Regel an diesem Element — und das Budget, gegen das sie
|
|
40
72
|
* geschwellt wurde. Zusammen ergeben sie die **Verstoßmasse** `d = max(0, value - threshold)`,
|
|
@@ -213,7 +245,22 @@ function funcMustSatisfyReq(graph) {
|
|
|
213
245
|
// Also include constraints whose description mentions this func's name
|
|
214
246
|
const namePattern = fn.name.toLowerCase();
|
|
215
247
|
const matchingConstraints = allReqs.filter(r => r.kinds?.includes('non-functional') && r.description.toLowerCase().includes(namePattern));
|
|
216
|
-
|
|
248
|
+
// CR-SM-346 (ITEM-2026-299): der Kandidatenkreis wird am META-MODELL gefiltert, nicht
|
|
249
|
+
// an einer zweiten, hier abgeschriebenen kinds-Liste. `FUNC -satisfy-> REQ` laesst nur
|
|
250
|
+
// BEHAVIOURAL_KINDS zu; bis hierher bot die Regel ALLE REQ an — gemessen an sigllm hatte
|
|
251
|
+
// damit KEINER von 33 Befunden einen legalen Kandidaten, und die Vorlage aus CR-SM-342
|
|
252
|
+
// gab korrekt `null` zurueck. 86 Befunde, 0 Empfehlungen: der Befund war unbedienbar.
|
|
253
|
+
//
|
|
254
|
+
// Besonders der `matchingConstraints`-Zweig zog gezielt `non-functional` herein — genau
|
|
255
|
+
// die Sorte, die das Muster abweist. Er bleibt stehen (ein REQ kann mehrere kinds tragen,
|
|
256
|
+
// und die Namensnennung ist ein echtes Relevanzsignal), aber er entscheidet nicht mehr
|
|
257
|
+
// ueber die Legalitaet.
|
|
258
|
+
//
|
|
259
|
+
// `isValidTrace` statt Eigenbau: eine Regel, die Kandidaten anbietet, die ihr eigenes
|
|
260
|
+
// Meta-Modell abweist, erzeugt Waste statt Fuehrung — und zwei Wahrheiten ueber dieselbe
|
|
261
|
+
// Grammatik sind genau der Defekt, den CR-SM-286/-302 beseitigt haben.
|
|
262
|
+
const candidates = [...new Map([...siblingReqs, ...matchingConstraints, ...allReqs].map(e => [e.id, e])).values()]
|
|
263
|
+
.filter(r => isValidTrace({ source: 'FUNC', target: 'REQ', type: 'satisfy', targetKinds: r.kinds }));
|
|
217
264
|
return {
|
|
218
265
|
rule_id: 'R-02',
|
|
219
266
|
severity: 'warning',
|
|
@@ -890,12 +937,34 @@ function validTracePattern(graph) {
|
|
|
890
937
|
if (!why)
|
|
891
938
|
return [];
|
|
892
939
|
const { detail, hint } = explainRejection(why, t, src, tgt);
|
|
940
|
+
// CR-SM-341: derselbe Grund, den `detail` in Prosa giesst — hier zusaetzlich als Struktur.
|
|
941
|
+
// Keine zweite Rechnung: `why` liegt vor, die Alternativen befragen `isValidTrace`.
|
|
942
|
+
const shape = {
|
|
943
|
+
source: src, target: tgt, type: t.type, label: t.label,
|
|
944
|
+
sourceKinds: idx.byId.get(t.source)?.kinds,
|
|
945
|
+
targetKinds: idx.byId.get(t.target)?.kinds,
|
|
946
|
+
};
|
|
947
|
+
const alt = admittingAlternatives(shape);
|
|
948
|
+
const where = why.reason === 'no-pattern' ? undefined : why.pattern.where;
|
|
893
949
|
return [{
|
|
894
950
|
rule_id: 'R-18',
|
|
895
951
|
severity: 'error',
|
|
896
952
|
element_id: t.source,
|
|
897
953
|
message: `Invalid trace ${t.source} -${t.type}-> ${t.target}: ${detail}`,
|
|
898
954
|
fix_hint: hint,
|
|
955
|
+
context: {
|
|
956
|
+
trace_rejection: {
|
|
957
|
+
reason: why.reason,
|
|
958
|
+
source_type: src,
|
|
959
|
+
target_type: tgt,
|
|
960
|
+
trace_type: t.type,
|
|
961
|
+
target_id: t.target,
|
|
962
|
+
...(where ? { field: where.field, allowed: [...where.allowed] } : {}),
|
|
963
|
+
...(why.reason === 'kinds-mismatch' ? { declared: [...why.declared] } : {}),
|
|
964
|
+
candidate_sources: alt.sources,
|
|
965
|
+
candidate_trace_types: alt.traceTypes,
|
|
966
|
+
},
|
|
967
|
+
},
|
|
899
968
|
}];
|
|
900
969
|
})
|
|
901
970
|
.concat(cardinalityViolations(graph))
|
|
@@ -1260,14 +1329,11 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1260
1329
|
}
|
|
1261
1330
|
if (connections.length === 0)
|
|
1262
1331
|
return [];
|
|
1263
|
-
// FUNC → set of FCHAINs composing it.
|
|
1264
|
-
//
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
(chainsOfFunc.get(t.target) ?? chainsOfFunc.set(t.target, new Set()).get(t.target)).add(t.source);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1332
|
+
// FUNC → set of FCHAINs composing it. CR-SM-335: DIESELBE Rechnung, die auch die Kennzahl
|
|
1333
|
+
// speist — vorher stand hier ein zweiter, lokaler Index ueber dieselbe Kantenmenge. "Teilen
|
|
1334
|
+
// diese beiden EINE Kette" braucht die Mengen, die Infrastruktur-Ausnahme die Zahl; beides
|
|
1335
|
+
// kommt jetzt aus `chainsByFunc` (CR-SM-313 hatte den Helfer zugesagt, CR-SM-314 die Zahl).
|
|
1336
|
+
const chainsOf = chainsByFunc(graph);
|
|
1271
1337
|
// FCHAINs whose satisfy-REQ is verified by a TEST = chains with an integration test.
|
|
1272
1338
|
const verifiedReqs = new Set(idx.tracesOfType('verify').map(t => t.target));
|
|
1273
1339
|
const testedChains = new Set();
|
|
@@ -1285,8 +1351,8 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1285
1351
|
const violations = [];
|
|
1286
1352
|
const seen = new Set();
|
|
1287
1353
|
for (const [p, c] of connections) {
|
|
1288
|
-
const pChains =
|
|
1289
|
-
const cChains =
|
|
1354
|
+
const pChains = chainsOf.get(p) ?? new Set();
|
|
1355
|
+
const cChains = chainsOf.get(c) ?? new Set();
|
|
1290
1356
|
// ZWEIG 1 — ein Ende in GAR KEINER Kette: still. Das ist R-30s Aussage, nicht R-21s.
|
|
1291
1357
|
// Ohne diese Klausel feuert die Regel an einem code-importierten Graphen wie moneyflow
|
|
1292
1358
|
// 219 von 219 Mal und meldet in Wahrheit "dieses Repo hat keine Wirkketten", einmal je Kante.
|