@sigloch/contracts 10.5.0 → 10.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1 -1
- package/dist/se/grammar-snapshot.js +3 -2
- package/dist/se/readiness.d.ts +62 -0
- package/dist/se/readiness.js +59 -0
- package/dist/se/rules.js +8 -11
- 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) {
|
|
@@ -24,5 +24,5 @@ export declare const GRAMMAR_SNAPSHOT: {
|
|
|
24
24
|
readonly conformanceRules: readonly ["RC-01 (error)", "RC-02 (error)", "RC-03 (error)", "RC-04 (warning)", "RC-05 (warning)", "RC-06 (warning)", "RC-07 (warning)"];
|
|
25
25
|
readonly policy: readonly ["apTable = null", "boundaryWidth = {warning:5}", "criticality = {infrastructure:3}", "crossingFlows = {warning:3}", "decompositionBreadth = {min:3,warning:9}", "instability = null", "lcom4 = {info:4,warning:6}", "riskRpn = 100"];
|
|
26
26
|
readonly ruleHelp: readonly ["AF-01", "AF-02", "AF-03", "AF-04", "AF-05", "BQ-01", "BQ-02", "BQ-04", "BQ-06", "BQ-07", "BW-02", "CL-01", "CR-01", "CR-R01", "CR-R02", "CR-R03", "FC-02", "FC-03", "FC-04", "FM-01", "FM-02", "FM-03", "IO-01", "IO-02", "MS-01", "MS-02", "MS-03", "MT-01", "MT-02", "MT-04", "ND-01", "ND-02", "NFR-01", "R-01", "R-02", "R-04", "R-05", "R-08", "R-10", "R-12", "R-15", "R-16", "R-17", "R-18", "R-19", "R-20", "R-21", "R-22", "R-23", "R-26", "R-29", "R-30", "R-31", "R-32", "RC-01", "RC-02", "RC-03", "RC-04", "RC-05", "RC-06", "RC-07", "RD-01", "RD-02", "RD-03", "RD-04", "RD-05", "SC-02", "UC-01", "UC-02", "UC-03", "UC-04", "UC-05", "UC-06", "VR-01"];
|
|
27
|
-
readonly exports: readonly ["AF_RULES", "ALL_RULE_DEFS", "AO_RULES", "ActionPriority", "AnalysisArtifactId", "AnalysisFreshnessStampSchema", "ApTableSchema", "BOUNDED_PATTERNS", "BQ_RULES", "CLOSED_STATUS", "CODE_CONFORMANCE_RULES", "CR_RULES", "CodeFactsSchema", "DEFAULT_METRIC_POLICY", "DIMENSION_READINESS_DELTA_NAME", "DIMENSION_READINESS_NAME", "ELEMENT_ATTRIBUTES", "ELEMENT_DESCRIPTIONS", "ElementType", "ElementUid", "FC_RULES", "FM_RULES", "FileFactsSchema", "ImportEdgeSchema", "MAX_SLUG_LENGTH", "META_MODEL_VERSION", "MODELING_ELEMENT_TYPES", "MT_RULES", "MetricPolicySchema", "ND_RULES", "ONTOLOGY_VERSION", "OntologyElement", "OntologyGraph", "PHASE_READINESS_NAME", "PhaseGate", "READINESS_SCORED_PROFILES", "REQUIRED_PATTERNS", "RULES_VERSION", "RULE_HELP", "RULE_TO_DIMENSION", "RULE_TO_PHASE", "ReadinessDimension", "ReadinessReport", "ReadinessScore", "RealRefSchema", "RepoRelativePathSchema", "ReqKind", "RuleSeverity", "RuleViolation", "SC_RULES", "TRACE_PATTERNS", "TestRefSchema", "TestRefsSchema", "TestResult", "Trace", "TraceType", "UC_RULES", "V3_RULES", "VIEW_RULES", "VerificationMethod", "ViolationCandidate", "ViolationContext", "actionPriority", "allocationCohesion", "apMethod", "attributeTypeOf", "boxContracts", "bq01Unambiguous", "bq02Verifiable", "bq04Necessary", "bq06Conforming", "bq07Complete", "bw02WhiteboxWidth", "cl01ConopsCompleteness", "cr01CrossingFlowCount", "crossingContractCount", "decomposedFuncs", "evaluateAFRules", "evaluateAORules", "evaluateAllRules", "evaluateBQRules", "evaluateCRRules", "evaluateConformanceRules", "evaluateFCRules", "evaluateFMRules", "evaluateMTRules", "evaluateNDRules", "evaluateRules", "evaluateSCRules", "evaluateUCRules", "evaluateViewRules", "extractFormatE", "fc02LeafUcHasFchain", "fc03FchainFlat", "fc04ActorBounded", "fm01MissingFmeaAttributes", "fm02MissingMitigation", "fm03HighRiskUnverified", "funcSimilarity", "funcSubtree", "functionCriticality", "getRuleDefsForProfile", "hydrateAttrValue", "importCoverage", "indexOf", "io01CrossModuleCompleteness", "isElementUid", "isValidTrace", "jaccard", "maxOccurs", "minOccurs", "moduleCrossings", "moduleMetrics", "mt01Instability", "mt02Lcom4", "mt04WhiteboxLcom4", "nd01FuncNearDuplicate", "nd02SchemaNearDuplicate", "nfr01BudgetOvershoot", "normalizeReqKinds", "pairsAbove", "parseElementUid", "
|
|
27
|
+
readonly exports: readonly ["AF_RULES", "ALL_RULE_DEFS", "AO_RULES", "ActionPriority", "AnalysisArtifactId", "AnalysisFreshnessStampSchema", "ApTableSchema", "BOUNDED_PATTERNS", "BQ_RULES", "CLOSED_STATUS", "CODE_CONFORMANCE_RULES", "CR_RULES", "CodeFactsSchema", "DEFAULT_METRIC_POLICY", "DIMENSION_READINESS_DELTA_NAME", "DIMENSION_READINESS_NAME", "ELEMENT_ATTRIBUTES", "ELEMENT_DESCRIPTIONS", "ElementType", "ElementUid", "FC_RULES", "FM_RULES", "FileFactsSchema", "ImportEdgeSchema", "MAX_SLUG_LENGTH", "META_MODEL_VERSION", "MODELING_ELEMENT_TYPES", "MT_RULES", "MetricPolicySchema", "ND_RULES", "ONTOLOGY_VERSION", "OntologyElement", "OntologyGraph", "PHASE_READINESS_NAME", "PhaseGate", "READINESS_SCORED_PROFILES", "REQUIRED_PATTERNS", "RULES_VERSION", "RULE_HELP", "RULE_TO_DIMENSION", "RULE_TO_PHASE", "ReadinessDimension", "ReadinessReport", "ReadinessScore", "RealRefSchema", "RepoRelativePathSchema", "ReqKind", "RuleSeverity", "RuleViolation", "SC_RULES", "SteerSpace", "SteerTerm", "TRACE_PATTERNS", "TestRefSchema", "TestRefsSchema", "TestResult", "Trace", "TraceType", "UC_RULES", "V3_RULES", "VIEW_RULES", "VerificationMethod", "ViolationCandidate", "ViolationContext", "actionPriority", "allocationCohesion", "apMethod", "attributeTypeOf", "boxContracts", "bq01Unambiguous", "bq02Verifiable", "bq04Necessary", "bq06Conforming", "bq07Complete", "bw02WhiteboxWidth", "chainsByFunc", "cl01ConopsCompleteness", "cr01CrossingFlowCount", "crossingContractCount", "decomposedFuncs", "evaluateAFRules", "evaluateAORules", "evaluateAllRules", "evaluateBQRules", "evaluateCRRules", "evaluateConformanceRules", "evaluateFCRules", "evaluateFMRules", "evaluateMTRules", "evaluateNDRules", "evaluateRules", "evaluateSCRules", "evaluateUCRules", "evaluateViewRules", "extractFormatE", "fc02LeafUcHasFchain", "fc03FchainFlat", "fc04ActorBounded", "fm01MissingFmeaAttributes", "fm02MissingMitigation", "fm03HighRiskUnverified", "funcSimilarity", "funcSubtree", "functionCriticality", "getRuleDefsForProfile", "hydrateAttrValue", "importCoverage", "indexOf", "io01CrossModuleCompleteness", "isElementUid", "isValidTrace", "jaccard", "maxOccurs", "minOccurs", "moduleCrossings", "moduleMetrics", "mt01Instability", "mt02Lcom4", "mt04WhiteboxLcom4", "nd01FuncNearDuplicate", "nd02SchemaNearDuplicate", "nfr01BudgetOvershoot", "normalizeReqKinds", "pairsAbove", "parseElementUid", "sc02IsReferenced", "schemaSimilarity", "setBQ04SimilarityMatrix", "subtreeFuncs", "toElementUid", "toEvaluableGraph", "tokens", "traceRejection", "tryParseElementUid", "uc01HasRequirements", "uc02HasActor", "uc03HasScenario", "uc04GoalDefined", "uc05HasPostcondition", "uc06HasPrecondition", "vr01TestNoResult", "whiteboxContractCount"];
|
|
28
28
|
};
|
|
@@ -342,6 +342,8 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
342
342
|
"RuleSeverity",
|
|
343
343
|
"RuleViolation",
|
|
344
344
|
"SC_RULES",
|
|
345
|
+
"SteerSpace",
|
|
346
|
+
"SteerTerm",
|
|
345
347
|
"TRACE_PATTERNS",
|
|
346
348
|
"TestRefSchema",
|
|
347
349
|
"TestRefsSchema",
|
|
@@ -365,6 +367,7 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
365
367
|
"bq06Conforming",
|
|
366
368
|
"bq07Complete",
|
|
367
369
|
"bw02WhiteboxWidth",
|
|
370
|
+
"chainsByFunc",
|
|
368
371
|
"cl01ConopsCompleteness",
|
|
369
372
|
"cr01CrossingFlowCount",
|
|
370
373
|
"crossingContractCount",
|
|
@@ -414,10 +417,8 @@ export const GRAMMAR_SNAPSHOT = {
|
|
|
414
417
|
"normalizeReqKinds",
|
|
415
418
|
"pairsAbove",
|
|
416
419
|
"parseElementUid",
|
|
417
|
-
"parseFormatE",
|
|
418
420
|
"sc02IsReferenced",
|
|
419
421
|
"schemaSimilarity",
|
|
420
|
-
"serializeToFormatE",
|
|
421
422
|
"setBQ04SimilarityMatrix",
|
|
422
423
|
"subtreeFuncs",
|
|
423
424
|
"toElementUid",
|
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.js
CHANGED
|
@@ -8,7 +8,7 @@ import { ElementType, TraceType, TestRefsSchema, RealRefSchema } from './ontolog
|
|
|
8
8
|
import { traceRejection, BOUNDED_PATTERNS, REQUIRED_PATTERNS, maxOccurs } from './meta-model.js';
|
|
9
9
|
import { indexOf } from './graph-index.js';
|
|
10
10
|
import { moduleCrossings, subtreeFuncs } from './module-crossings.js';
|
|
11
|
-
import { functionCriticality } from './function-criticality.js';
|
|
11
|
+
import { functionCriticality, chainsByFunc } from './function-criticality.js';
|
|
12
12
|
export const RuleSeverity = z.enum(['error', 'warning', 'info']);
|
|
13
13
|
/** Candidate target for resolving a violation (e.g. a REQ to satisfy, a TEST to link). */
|
|
14
14
|
export const ViolationCandidate = z.object({
|
|
@@ -1260,14 +1260,11 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1260
1260
|
}
|
|
1261
1261
|
if (connections.length === 0)
|
|
1262
1262
|
return [];
|
|
1263
|
-
// FUNC → set of FCHAINs composing it.
|
|
1264
|
-
//
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
(chainsOfFunc.get(t.target) ?? chainsOfFunc.set(t.target, new Set()).get(t.target)).add(t.source);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1263
|
+
// FUNC → set of FCHAINs composing it. CR-SM-335: DIESELBE Rechnung, die auch die Kennzahl
|
|
1264
|
+
// speist — vorher stand hier ein zweiter, lokaler Index ueber dieselbe Kantenmenge. "Teilen
|
|
1265
|
+
// diese beiden EINE Kette" braucht die Mengen, die Infrastruktur-Ausnahme die Zahl; beides
|
|
1266
|
+
// kommt jetzt aus `chainsByFunc` (CR-SM-313 hatte den Helfer zugesagt, CR-SM-314 die Zahl).
|
|
1267
|
+
const chainsOf = chainsByFunc(graph);
|
|
1271
1268
|
// FCHAINs whose satisfy-REQ is verified by a TEST = chains with an integration test.
|
|
1272
1269
|
const verifiedReqs = new Set(idx.tracesOfType('verify').map(t => t.target));
|
|
1273
1270
|
const testedChains = new Set();
|
|
@@ -1285,8 +1282,8 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1285
1282
|
const violations = [];
|
|
1286
1283
|
const seen = new Set();
|
|
1287
1284
|
for (const [p, c] of connections) {
|
|
1288
|
-
const pChains =
|
|
1289
|
-
const cChains =
|
|
1285
|
+
const pChains = chainsOf.get(p) ?? new Set();
|
|
1286
|
+
const cChains = chainsOf.get(c) ?? new Set();
|
|
1290
1287
|
// ZWEIG 1 — ein Ende in GAR KEINER Kette: still. Das ist R-30s Aussage, nicht R-21s.
|
|
1291
1288
|
// Ohne diese Klausel feuert die Regel an einem code-importierten Graphen wie moneyflow
|
|
1292
1289
|
// 219 von 219 Mal und meldet in Wahrheit "dieses Repo hat keine Wirkketten", einmal je Kante.
|