@sigloch/contracts 10.9.0 → 10.12.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/analysis-freshness-rules.d.ts +20 -0
- package/dist/se/analysis-freshness-rules.js +71 -0
- package/dist/se/conformance-rules.d.ts +6 -1
- package/dist/se/conformance-rules.js +196 -55
- package/dist/se/cr-quality-rules.d.ts +5 -0
- package/dist/se/cr-quality-rules.js +10 -5
- package/dist/se/evaluate-all.js +3 -1
- package/dist/se/fmea-rules.js +6 -6
- package/dist/se/function-criticality.d.ts +8 -22
- package/dist/se/function-criticality.js +71 -1
- package/dist/se/grammar-snapshot.d.ts +6 -6
- package/dist/se/grammar-snapshot.js +35 -25
- package/dist/se/index.d.ts +2 -2
- package/dist/se/index.js +2 -2
- package/dist/se/metric-rules.d.ts +20 -61
- package/dist/se/metric-rules.js +90 -1
- package/dist/se/near-duplicate-rules.d.ts +4 -4
- package/dist/se/near-duplicate-rules.js +5 -5
- package/dist/se/ontology.d.ts +28 -0
- package/dist/se/ontology.js +23 -0
- package/dist/se/readiness.js +18 -13
- package/dist/se/rule-help.js +20 -13
- package/dist/se/rules.js +10 -40
- package/dist/se/uc-quality-rules.d.ts +0 -2
- package/dist/se/uc-quality-rules.js +6 -44
- package/dist/se/view-rules.js +5 -5
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* CR-121 P3: Architecture Metrics MT-01..MT-03.
|
|
3
3
|
* Module-level metrics computed from graph structure.
|
|
4
4
|
*/
|
|
5
|
+
import { z } from 'zod/v4';
|
|
5
6
|
import type { OntologyGraph } from './ontology.js';
|
|
6
7
|
import type { RuleViolation } from './rules.js';
|
|
7
8
|
import type { MetricPolicy } from './policy.js';
|
|
@@ -82,66 +83,24 @@ export interface AllocationCohesion {
|
|
|
82
83
|
* is deferred (CR-SM-223).
|
|
83
84
|
*/
|
|
84
85
|
/** Per-module architecture metrics — the numbers behind MT-01/MT-02 (CR-SM-232). */
|
|
85
|
-
export
|
|
86
|
-
moduleId:
|
|
87
|
-
moduleName:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* CR-SM-326: die Zahl der BOXEN, ueber die `lcom4` gerechnet wurde — Sub-MODs
|
|
104
|
-
* (`MOD -compose-> MOD`) plus die direkt allozierten FUNC. Sie ist der Nenner der MT-02-Meldung
|
|
105
|
-
* und NICHT `allocatedFuncs`: ein Eltern-MOD hat Boxen ohne eine einzige eigene Allokation.
|
|
106
|
-
*/
|
|
107
|
-
lcom4Boxes: number;
|
|
108
|
-
/**
|
|
109
|
-
* CR-SM-263: JEDE allozierte FUNC ist ein zerlegter Block (Rollup), das MOD also ein
|
|
110
|
-
* Container statt eines Implementierungsmoduls.
|
|
111
|
-
* `false` unterhalb von 2 allozierten FUNCs (kein Container, nur zu klein zum Messen).
|
|
112
|
-
*
|
|
113
|
-
* CR-SM-326: seither eine reine MESSUNG. Das Urteil haengt nicht mehr daran — eine Box erbt
|
|
114
|
-
* die Vertraege ihres Teilbaums, also ist LCOM4 an einem Container genauso aussagekraeftig
|
|
115
|
-
* wie an einem Implementierungsmodul. Der Wert steht hier, weil er die Lesart der Zeile
|
|
116
|
-
* erklaert (Bloecke statt Blaetter), nicht weil er etwas stilllegt.
|
|
117
|
-
*/
|
|
118
|
-
rollupContainer: boolean;
|
|
119
|
-
/** The CR-SM-223 measurement, deliberately threshold-free. null where contracts omits it. */
|
|
120
|
-
cohesion: {
|
|
121
|
-
internal: number;
|
|
122
|
-
external: number;
|
|
123
|
-
ratio: number;
|
|
124
|
-
} | null;
|
|
125
|
-
/**
|
|
126
|
-
* CR-SM-301 — Martins *Stable Dependencies Principle*, als MESSUNG und ausdruecklich als
|
|
127
|
-
* KEINE Regel: die Zahl der Vertraege, die dieses Modul von einem WENIGER stabilen Lieferanten
|
|
128
|
-
* bezieht (`I_Lieferant > I_Bezieher`). Abhaengigkeiten „bergauf".
|
|
129
|
-
*
|
|
130
|
-
* Warum keine Regel: CR-SM-298 hat genau das versucht und ist an Gate 7 gescheitert. Ueber
|
|
131
|
-
* 19 Familiengraphen laufen 25 von 250 Abhaengigkeiten bergauf, aber der MEDIAN der
|
|
132
|
-
* Stabilitaetsluecke ist **0.10** — bei einer Vertragsmasse von im Mittel 5 ist das ein
|
|
133
|
-
* Vertrag Unterschied, also Rauschen. Filtert man auf Luecke > 0.2 und beide Enden >= 3
|
|
134
|
-
* Vertraege, bleiben 3 von 25, davon zwei in einem Wegwerf-Trial und einer Demo. Eine Regel
|
|
135
|
-
* haette zwei erfundene Schwellen gebraucht, um EINEN Befund zu produzieren.
|
|
136
|
-
*
|
|
137
|
-
* Was bleibt, ist die Zahl. `graph_metrics` traegt sie in jeder Modulzeile, ein Mensch liest
|
|
138
|
-
* sie neben `instability` und deutet sie — Praezedenz MT-03 (CR-SM-223) und `cohesion` eine
|
|
139
|
-
* Zeile weiter oben: messen, nicht urteilen, ohne Katalogeintrag und ohne Nenner-Anteil.
|
|
140
|
-
*
|
|
141
|
-
* `null`, wo `instability` es auch ist — ohne Kopplung gibt es keine Richtung.
|
|
142
|
-
*/
|
|
143
|
-
uphillDependencies: number | null;
|
|
144
|
-
}
|
|
86
|
+
export declare const ModuleMetrics: z.ZodObject<{
|
|
87
|
+
moduleId: z.ZodString;
|
|
88
|
+
moduleName: z.ZodString;
|
|
89
|
+
allocatedFuncs: z.ZodNumber;
|
|
90
|
+
fanIn: z.ZodNumber;
|
|
91
|
+
fanOut: z.ZodNumber;
|
|
92
|
+
instability: z.ZodNullable<z.ZodNumber>;
|
|
93
|
+
lcom4: z.ZodNullable<z.ZodNumber>;
|
|
94
|
+
lcom4Boxes: z.ZodNumber;
|
|
95
|
+
rollupContainer: z.ZodBoolean;
|
|
96
|
+
cohesion: z.ZodNullable<z.ZodObject<{
|
|
97
|
+
internal: z.ZodNumber;
|
|
98
|
+
external: z.ZodNumber;
|
|
99
|
+
ratio: z.ZodNumber;
|
|
100
|
+
}, z.core.$strip>>;
|
|
101
|
+
uphillDependencies: z.ZodNullable<z.ZodNumber>;
|
|
102
|
+
}, z.core.$strip>;
|
|
103
|
+
export type ModuleMetrics = z.infer<typeof ModuleMetrics>;
|
|
145
104
|
/**
|
|
146
105
|
* Per-module architecture metrics as NUMBERS — one row per MOD, threshold or not
|
|
147
106
|
* (CR-SM-232).
|
|
@@ -166,7 +125,7 @@ export declare const MT_RULES: readonly [{
|
|
|
166
125
|
}, {
|
|
167
126
|
readonly id: "MT-02";
|
|
168
127
|
readonly name: "Module cohesion (LCOM4)";
|
|
169
|
-
readonly severity: "
|
|
128
|
+
readonly severity: "warning";
|
|
170
129
|
readonly evaluate: typeof mt02Lcom4;
|
|
171
130
|
readonly domain: readonly ["MOD"];
|
|
172
131
|
}, {
|
package/dist/se/metric-rules.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-121 P3: Architecture Metrics MT-01..MT-03.
|
|
3
|
+
* Module-level metrics computed from graph structure.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod/v4';
|
|
1
6
|
import { decomposedFuncs } from './rules.js';
|
|
2
7
|
import { indexOf } from './graph-index.js';
|
|
3
8
|
import { moduleCrossings, subtreeFuncs, funcSubtree, boxContracts } from './module-crossings.js';
|
|
@@ -185,6 +190,87 @@ function connectionPairs(graph) {
|
|
|
185
190
|
}
|
|
186
191
|
return pairs;
|
|
187
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Allocation cohesion — a **measurement, not a rule** (CR-SM-223, decision 2026-07-29).
|
|
195
|
+
*
|
|
196
|
+
* It used to be MT-03 with an 80 % threshold, and it fired on nearly every module of
|
|
197
|
+
* every real graph: 6 of 7 on graphcode, 4 of 4 on graph-view-edit, 10 of 11 on the
|
|
198
|
+
* family graph. `CR-SM-221` first suspected the edge definition and made it
|
|
199
|
+
* FLOW-transitive — the hit rate did not move. The threshold was the miscalibration:
|
|
200
|
+
* in a flow-routed layered architecture, module interaction crosses boundaries by
|
|
201
|
+
* design, so "80 % of interaction is internal" describes a monolith, not a healthy
|
|
202
|
+
* module.
|
|
203
|
+
*
|
|
204
|
+
* Rather than fit a cut-off to 11 data points, this reports the number and lets the
|
|
205
|
+
* architect judge. Returned worst-first, so the head of the list is where to look.
|
|
206
|
+
* Modules with fewer than two allocated FUNCs, or with no external connection at all,
|
|
207
|
+
* carry no signal and are omitted.
|
|
208
|
+
*
|
|
209
|
+
* Deliberately NOT a `RuleDefinition`: `computeReadiness` counts every violation into
|
|
210
|
+
* its dimension score regardless of severity, so a per-module advisory would depress
|
|
211
|
+
* the `alloc` score permanently. A measurement must not masquerade as a defect.
|
|
212
|
+
*
|
|
213
|
+
* Validation of this metric — and of MT-01/MT-02, which are thresholded the same way —
|
|
214
|
+
* is deferred (CR-SM-223).
|
|
215
|
+
*/
|
|
216
|
+
/** Per-module architecture metrics — the numbers behind MT-01/MT-02 (CR-SM-232). */
|
|
217
|
+
// CR-SM-361: ein Zod-Vertrag, kein Typ — gemessen wird einmal je Graph, dort wird geprueft.
|
|
218
|
+
export const ModuleMetrics = z.object({
|
|
219
|
+
moduleId: z.string(),
|
|
220
|
+
moduleName: z.string(),
|
|
221
|
+
/** FUNCs allocated to this module (`FUNC —allocate→ MOD`). */
|
|
222
|
+
allocatedFuncs: z.number(),
|
|
223
|
+
/** MT-01 core: coupling. `instability = fanOut / (fanIn + fanOut)`. */
|
|
224
|
+
fanIn: z.number(),
|
|
225
|
+
fanOut: z.number(),
|
|
226
|
+
/** null when fanIn + fanOut === 0 — no signal, no substitute value. */
|
|
227
|
+
instability: z.number().nullable(),
|
|
228
|
+
/**
|
|
229
|
+
* MT-02 core: connected-component count ueber die BOXEN dieser Modul-Whitebox (CR-SM-326).
|
|
230
|
+
* `null`, wo nichts zu messen ist: unter zwei Boxen, oder wenn keine einzige Box einen
|
|
231
|
+
* Vertrag traegt — dann meldet R-31 die unverdrahteten Funktionen am Element, und eine Zahl
|
|
232
|
+
* hier waere keine Aussage ueber Kohaesion, sondern dieselbe Aussage ein zweites Mal.
|
|
233
|
+
*/
|
|
234
|
+
lcom4: z.number().nullable(),
|
|
235
|
+
/**
|
|
236
|
+
* CR-SM-326: die Zahl der BOXEN, ueber die `lcom4` gerechnet wurde — Sub-MODs
|
|
237
|
+
* (`MOD -compose-> MOD`) plus die direkt allozierten FUNC. Sie ist der Nenner der MT-02-Meldung
|
|
238
|
+
* und NICHT `allocatedFuncs`: ein Eltern-MOD hat Boxen ohne eine einzige eigene Allokation.
|
|
239
|
+
*/
|
|
240
|
+
lcom4Boxes: z.number(),
|
|
241
|
+
/**
|
|
242
|
+
* CR-SM-263: JEDE allozierte FUNC ist ein zerlegter Block (Rollup), das MOD also ein
|
|
243
|
+
* Container statt eines Implementierungsmoduls.
|
|
244
|
+
* `false` unterhalb von 2 allozierten FUNCs (kein Container, nur zu klein zum Messen).
|
|
245
|
+
*
|
|
246
|
+
* CR-SM-326: seither eine reine MESSUNG. Das Urteil haengt nicht mehr daran — eine Box erbt
|
|
247
|
+
* die Vertraege ihres Teilbaums, also ist LCOM4 an einem Container genauso aussagekraeftig
|
|
248
|
+
* wie an einem Implementierungsmodul. Der Wert steht hier, weil er die Lesart der Zeile
|
|
249
|
+
* erklaert (Bloecke statt Blaetter), nicht weil er etwas stilllegt.
|
|
250
|
+
*/
|
|
251
|
+
rollupContainer: z.boolean(),
|
|
252
|
+
/** The CR-SM-223 measurement, deliberately threshold-free. null where contracts omits it. */
|
|
253
|
+
cohesion: z.object({ internal: z.number(), external: z.number(), ratio: z.number() }).nullable(),
|
|
254
|
+
/**
|
|
255
|
+
* CR-SM-301 — Martins *Stable Dependencies Principle*, als MESSUNG und ausdruecklich als
|
|
256
|
+
* KEINE Regel: die Zahl der Vertraege, die dieses Modul von einem WENIGER stabilen Lieferanten
|
|
257
|
+
* bezieht (`I_Lieferant > I_Bezieher`). Abhaengigkeiten „bergauf".
|
|
258
|
+
*
|
|
259
|
+
* Warum keine Regel: CR-SM-298 hat genau das versucht und ist an Gate 7 gescheitert. Ueber
|
|
260
|
+
* 19 Familiengraphen laufen 25 von 250 Abhaengigkeiten bergauf, aber der MEDIAN der
|
|
261
|
+
* Stabilitaetsluecke ist **0.10** — bei einer Vertragsmasse von im Mittel 5 ist das ein
|
|
262
|
+
* Vertrag Unterschied, also Rauschen. Filtert man auf Luecke > 0.2 und beide Enden >= 3
|
|
263
|
+
* Vertraege, bleiben 3 von 25, davon zwei in einem Wegwerf-Trial und einer Demo. Eine Regel
|
|
264
|
+
* haette zwei erfundene Schwellen gebraucht, um EINEN Befund zu produzieren.
|
|
265
|
+
*
|
|
266
|
+
* Was bleibt, ist die Zahl. `graph_metrics` traegt sie in jeder Modulzeile, ein Mensch liest
|
|
267
|
+
* sie neben `instability` und deutet sie — Praezedenz MT-03 (CR-SM-223) und `cohesion` eine
|
|
268
|
+
* Zeile weiter oben: messen, nicht urteilen, ohne Katalogeintrag und ohne Nenner-Anteil.
|
|
269
|
+
*
|
|
270
|
+
* `null`, wo `instability` es auch ist — ohne Kopplung gibt es keine Richtung.
|
|
271
|
+
*/
|
|
272
|
+
uphillDependencies: z.number().nullable(),
|
|
273
|
+
});
|
|
188
274
|
/**
|
|
189
275
|
* The one per-module computation (CR-SM-232) — in GRAPH ORDER, unsorted.
|
|
190
276
|
*
|
|
@@ -312,6 +398,7 @@ function measureModulesUncached(graph) {
|
|
|
312
398
|
}
|
|
313
399
|
row.uphillDependencies = uphill;
|
|
314
400
|
}
|
|
401
|
+
z.array(ModuleMetrics).parse(rows); // CR-SM-361: der Erzeuger prueft seinen Vertrag (einmal je Graph, gecacht)
|
|
315
402
|
return rows;
|
|
316
403
|
}
|
|
317
404
|
/**
|
|
@@ -451,7 +538,9 @@ export function allocationCohesion(graph) {
|
|
|
451
538
|
}
|
|
452
539
|
export const MT_RULES = [
|
|
453
540
|
{ id: 'MT-01', name: 'Module instability', severity: 'warning', evaluate: mt01Instability, domain: ['MOD'] },
|
|
454
|
-
|
|
541
|
+
// CR-SM-357: warning — MT-02 ist Steuerregel (STEER_RULES), und eine Steuerregel darf nicht info sein.
|
|
542
|
+
// Die info-Stufe unterhalb der Schwelle bleibt als Vorwarnung (Masse 0), das Urteil der Regel ist warning.
|
|
543
|
+
{ id: 'MT-02', name: 'Module cohesion (LCOM4)', severity: 'warning', evaluate: mt02Lcom4, domain: ['MOD'] },
|
|
455
544
|
// CR-SM-327: dieselbe Masszahl an der FUNC-Whitebox. Eigene ID, weil die Grundgesamtheit
|
|
456
545
|
// die Readiness-Dimension bestimmt (Begruendung an `mt04WhiteboxLcom4`).
|
|
457
546
|
{ id: 'MT-04', name: 'Whitebox cohesion (LCOM4)', severity: 'info', evaluate: mt04WhiteboxLcom4, domain: ['FUNC'] },
|
|
@@ -17,20 +17,20 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { OntologyGraph } from './ontology.js';
|
|
19
19
|
import type { RuleViolation } from './rules.js';
|
|
20
|
-
/** ND-01: FUNC pairs with similarity >= 0.85 ->
|
|
20
|
+
/** ND-01: FUNC pairs with similarity >= 0.85 -> warning. */
|
|
21
21
|
export declare function nd01FuncNearDuplicate(graph: OntologyGraph): RuleViolation[];
|
|
22
|
-
/** ND-02: SCHEMA pairs with similarity >= 0.85 ->
|
|
22
|
+
/** ND-02: SCHEMA pairs with similarity >= 0.85 -> warning. */
|
|
23
23
|
export declare function nd02SchemaNearDuplicate(graph: OntologyGraph): RuleViolation[];
|
|
24
24
|
export declare const ND_RULES: readonly [{
|
|
25
25
|
readonly id: "ND-01";
|
|
26
26
|
readonly name: "FuncNearDuplicate";
|
|
27
|
-
readonly severity: "
|
|
27
|
+
readonly severity: "warning";
|
|
28
28
|
readonly evaluate: typeof nd01FuncNearDuplicate;
|
|
29
29
|
readonly domain: readonly ["FUNC"];
|
|
30
30
|
}, {
|
|
31
31
|
readonly id: "ND-02";
|
|
32
32
|
readonly name: "SchemaNearDuplicate";
|
|
33
|
-
readonly severity: "
|
|
33
|
+
readonly severity: "warning";
|
|
34
34
|
readonly evaluate: typeof nd02SchemaNearDuplicate;
|
|
35
35
|
readonly domain: readonly ["SCHEMA"];
|
|
36
36
|
}];
|
|
@@ -9,7 +9,7 @@ function nearDuplicates(graph, sim, ruleId, what, fixHint) {
|
|
|
9
9
|
const pct = Math.round(similarity * 100);
|
|
10
10
|
return {
|
|
11
11
|
rule_id: ruleId,
|
|
12
|
-
severity: '
|
|
12
|
+
severity: 'warning',
|
|
13
13
|
element_id: b,
|
|
14
14
|
message: `${b}${elB?.name ? ' (' + elB.name + ')' : ''} is ${pct}% similar to ${a}${elA?.name ? ' (' + elA.name + ')' : ''} — potential duplicate ${what}`,
|
|
15
15
|
fix_hint: fixHint,
|
|
@@ -17,17 +17,17 @@ function nearDuplicates(graph, sim, ruleId, what, fixHint) {
|
|
|
17
17
|
};
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
/** ND-01: FUNC pairs with similarity >= 0.85 ->
|
|
20
|
+
/** ND-01: FUNC pairs with similarity >= 0.85 -> warning. */
|
|
21
21
|
export function nd01FuncNearDuplicate(graph) {
|
|
22
22
|
return nearDuplicates(graph, funcSimilarity(graph), 'ND-01', 'function', 'Merge into single FUNC or differentiate responsibilities');
|
|
23
23
|
}
|
|
24
|
-
/** ND-02: SCHEMA pairs with similarity >= 0.85 ->
|
|
24
|
+
/** ND-02: SCHEMA pairs with similarity >= 0.85 -> warning. */
|
|
25
25
|
export function nd02SchemaNearDuplicate(graph) {
|
|
26
26
|
return nearDuplicates(graph, schemaSimilarity(graph), 'ND-02', 'schema', 'Merge schemas or differentiate field sets');
|
|
27
27
|
}
|
|
28
28
|
export const ND_RULES = [
|
|
29
|
-
{ id: 'ND-01', name: 'FuncNearDuplicate', severity: '
|
|
30
|
-
{ id: 'ND-02', name: 'SchemaNearDuplicate', severity: '
|
|
29
|
+
{ id: 'ND-01', name: 'FuncNearDuplicate', severity: 'warning', evaluate: nd01FuncNearDuplicate, domain: ['FUNC'] },
|
|
30
|
+
{ id: 'ND-02', name: 'SchemaNearDuplicate', severity: 'warning', evaluate: nd02SchemaNearDuplicate, domain: ['SCHEMA'] },
|
|
31
31
|
];
|
|
32
32
|
/** Evaluate all near-duplicate rules. */
|
|
33
33
|
export function evaluateNDRules(graph) {
|
package/dist/se/ontology.d.ts
CHANGED
|
@@ -196,6 +196,32 @@ export declare const RealRefSchema: z.ZodObject<{
|
|
|
196
196
|
lang: z.ZodOptional<z.ZodString>;
|
|
197
197
|
}, z.core.$strip>;
|
|
198
198
|
export type RealRef = z.infer<typeof RealRefSchema>;
|
|
199
|
+
/**
|
|
200
|
+
* Was ein Attributvertrag an einem Element aussagt — dreiwertig (CR-SM-360).
|
|
201
|
+
*
|
|
202
|
+
* `absent` : das Attribut fehlt (auch als `null`-Grabstein — Attribute sind nur mergebar).
|
|
203
|
+
* `invalid` : es ist da, aber verletzt den Vertrag; `error` sagt wie.
|
|
204
|
+
* `bound` : es ist da und gueltig.
|
|
205
|
+
*
|
|
206
|
+
* Bis hierher parste jeder Leser selbst (25 Stellen in contracts und graphcode), und die Leser
|
|
207
|
+
* wichen ab: einer meldete ungueltige testRefs, die anderen behandelten „ungueltig" still wie
|
|
208
|
+
* „fehlt" — die Klasse aus CR-GC-338. Ein Aufrufer, der beides gleich behandeln will, tut das jetzt
|
|
209
|
+
* sichtbar (`state !== 'bound'`), statt dass der Unterschied beim Parsen verloren geht.
|
|
210
|
+
*/
|
|
211
|
+
export type BindingRead<T> = {
|
|
212
|
+
state: 'absent';
|
|
213
|
+
} | {
|
|
214
|
+
state: 'invalid';
|
|
215
|
+
error: string;
|
|
216
|
+
} | {
|
|
217
|
+
state: 'bound';
|
|
218
|
+
value: T;
|
|
219
|
+
};
|
|
220
|
+
type Attributes = Record<string, unknown> | undefined;
|
|
221
|
+
/** Der EINE Leser von `attributes.testRefs` (CR-SM-360). */
|
|
222
|
+
export declare function readTestRefs(attributes: Attributes): BindingRead<TestRefs>;
|
|
223
|
+
/** Der EINE Leser von `attributes.realRef` (CR-SM-360). */
|
|
224
|
+
export declare function readRealRef(attributes: Attributes): BindingRead<RealRef>;
|
|
199
225
|
/**
|
|
200
226
|
* The five judgment-work artifact ids (CR-GC-221's creation keys) that graphcode's
|
|
201
227
|
* skills produce and that a phase gate checks for presence+freshness (CR-SM-227).
|
|
@@ -222,6 +248,7 @@ export type AnalysisArtifactId = z.infer<typeof AnalysisArtifactId>;
|
|
|
222
248
|
*/
|
|
223
249
|
export declare const AnalysisFreshnessStampSchema: z.ZodObject<{
|
|
224
250
|
graphVersion: z.ZodNumber;
|
|
251
|
+
crRefs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
225
252
|
}, z.core.$strip>;
|
|
226
253
|
export type AnalysisFreshnessStamp = z.infer<typeof AnalysisFreshnessStampSchema>;
|
|
227
254
|
/**
|
|
@@ -408,3 +435,4 @@ export interface AttributeSpec {
|
|
|
408
435
|
* the prescribed keys that rules, views, and extractors rely on.
|
|
409
436
|
*/
|
|
410
437
|
export declare const ELEMENT_ATTRIBUTES: Partial<Record<ElementType, AttributeSpec[]>>;
|
|
438
|
+
export {};
|
package/dist/se/ontology.js
CHANGED
|
@@ -167,6 +167,22 @@ export const RealRefSchema = z.object({
|
|
|
167
167
|
symbol: z.string().optional(),
|
|
168
168
|
lang: z.string().optional(),
|
|
169
169
|
});
|
|
170
|
+
/** Der EINE Leser von `attributes.testRefs` (CR-SM-360). */
|
|
171
|
+
export function readTestRefs(attributes) {
|
|
172
|
+
const raw = attributes?.testRefs;
|
|
173
|
+
if (raw === undefined || raw === null)
|
|
174
|
+
return { state: 'absent' };
|
|
175
|
+
const r = TestRefsSchema.safeParse(raw);
|
|
176
|
+
return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
|
|
177
|
+
}
|
|
178
|
+
/** Der EINE Leser von `attributes.realRef` (CR-SM-360). */
|
|
179
|
+
export function readRealRef(attributes) {
|
|
180
|
+
const raw = attributes?.realRef;
|
|
181
|
+
if (raw === undefined || raw === null)
|
|
182
|
+
return { state: 'absent' };
|
|
183
|
+
const r = RealRefSchema.safeParse(raw);
|
|
184
|
+
return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
|
|
185
|
+
}
|
|
170
186
|
/**
|
|
171
187
|
* The five judgment-work artifact ids (CR-GC-221's creation keys) that graphcode's
|
|
172
188
|
* skills produce and that a phase gate checks for presence+freshness (CR-SM-227).
|
|
@@ -186,6 +202,13 @@ export const AnalysisArtifactId = z.enum(['conops', 'fmea', 'trade', 'implplan',
|
|
|
186
202
|
*/
|
|
187
203
|
export const AnalysisFreshnessStampSchema = z.object({
|
|
188
204
|
graphVersion: z.number().int().nonnegative(),
|
|
205
|
+
/**
|
|
206
|
+
* CR-SM-355: die CRs, die aus dem Task hervorgegangen sind (Trade: die Entscheidungs-CRs,
|
|
207
|
+
* Assumption Review: die promovierten Annahmen). Der Verweis liegt dort, wo der Task endet,
|
|
208
|
+
* atomar mit dem Stempel; TR-01 / IR-01 pruefen ihn. Optional, weil conops/fmea/implplan keine
|
|
209
|
+
* Massnahmen-CRs erzeugen.
|
|
210
|
+
*/
|
|
211
|
+
crRefs: z.array(z.string()).optional(),
|
|
189
212
|
});
|
|
190
213
|
/**
|
|
191
214
|
* Eine benannte Abweichung (CR-SM-349): ein Fund, den der Mensch bewusst offen laesst.
|
package/dist/se/readiness.js
CHANGED
|
@@ -151,7 +151,7 @@ export const RULE_TO_DIMENSION = {
|
|
|
151
151
|
// req
|
|
152
152
|
'BQ-01': 'req', 'BQ-02': 'req', 'BQ-04': 'req',
|
|
153
153
|
'BQ-06': 'req', 'BQ-07': 'req',
|
|
154
|
-
'RD-01': 'req', 'RD-02': 'req',
|
|
154
|
+
'RD-01': 'req', 'RD-02': 'req',
|
|
155
155
|
// RD-04 is decomposition *breadth* — an architecture concern, not a requirement one
|
|
156
156
|
'RD-04': 'arch',
|
|
157
157
|
// CR-SM-311: die Untergrenze derselben Breite — ebenfalls Architektur
|
|
@@ -168,7 +168,6 @@ export const RULE_TO_DIMENSION = {
|
|
|
168
168
|
'R-30': 'arch', 'R-31': 'arch',
|
|
169
169
|
// uc
|
|
170
170
|
'UC-01': 'uc', 'UC-02': 'uc', 'UC-03': 'uc', 'UC-04': 'uc',
|
|
171
|
-
'UC-05': 'uc', 'UC-06': 'uc',
|
|
172
171
|
'R-15': 'uc', 'R-16': 'uc', 'R-17': 'uc',
|
|
173
172
|
// FC-04 (CR-SM-226): FCHAIN actor-bounded (trigger+consumer) — same dimension as FC-01..03.
|
|
174
173
|
'FC-02': 'uc', 'FC-03': 'uc', 'FC-04': 'uc',
|
|
@@ -206,7 +205,9 @@ export const RULE_TO_DIMENSION = {
|
|
|
206
205
|
'FM-01': 'req', 'FM-02': 'req', 'FM-03': 'ver',
|
|
207
206
|
// CR-SM-227: analysis-artifact freshness presence — closest topical fit per artifact.
|
|
208
207
|
'AF-01': 'req', 'AF-03': 'req', // ConOps/Assumption-Review: requirements-adjacent judgment work
|
|
208
|
+
'IR-01': 'req', // CR-SM-355: Annahmen-Review promoviert zu CR — Task-Ausgang von irr
|
|
209
209
|
'AF-02': 'arch', // Trade Study: architecture decision record
|
|
210
|
+
'TR-01': 'arch', // CR-SM-355: Trade-Entscheidung als CR — Task-Ausgang von trade
|
|
210
211
|
'AF-04': 'ver', // FMEA: risk/verification, same bucket as FM-03
|
|
211
212
|
'AF-05': 'ms', // Implementation Plan: milestone/planning
|
|
212
213
|
// NFR budget
|
|
@@ -237,16 +238,19 @@ export const PhaseGate = z.enum(['SRR', 'PDR', 'CDR', 'TRR']);
|
|
|
237
238
|
* single-owner convention as RULE_TO_DIMENSION.
|
|
238
239
|
*/
|
|
239
240
|
export const RULE_TO_PHASE = {
|
|
240
|
-
// CR-SM-227: Analysis-Freshness-Legs (AF-01..05) — Familie-Review 2026-08-05
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
|
|
241
|
+
// CR-SM-227: Analysis-Freshness-Legs (AF-01..05) — Familie-Review 2026-08-05; revidiert durch
|
|
242
|
+
// CR-SM-354 (2026-09-22) gegen NASA NPR 7123.1D Appendix G: das Operations Concept ist ein
|
|
243
|
+
// SRR-Eintrittskriterium (AF-01 → SRR), Integrationsplan und Master Schedule werden zur PDR
|
|
244
|
+
// baselined (AF-05 → PDR). Trade / Assumption Review PDR und FMEA CDR bleiben.
|
|
245
|
+
'AF-01': 'SRR', // ConOps
|
|
246
|
+
'AF-02': 'PDR', 'AF-03': 'PDR', // Trade Study, Assumption Review
|
|
244
247
|
'AF-04': 'CDR', // FMEA
|
|
245
|
-
'AF-05': '
|
|
248
|
+
'AF-05': 'PDR', // Implementation Plan
|
|
249
|
+
'TR-01': 'PDR', 'IR-01': 'PDR', // CR-SM-355: Task-Ausgaenge von trade / irr — dasselbe Gate wie ihr Eintritt
|
|
246
250
|
// SRR — requirements/scope clarity, system+UC boundary definition.
|
|
247
251
|
'BQ-01': 'SRR', 'BQ-02': 'SRR', 'BQ-04': 'SRR', 'BQ-06': 'SRR', 'BQ-07': 'SRR',
|
|
248
|
-
'RD-01': 'SRR', 'RD-02': 'SRR',
|
|
249
|
-
'UC-01': 'SRR', 'UC-02': 'SRR', 'UC-03': 'SRR', 'UC-04': 'SRR',
|
|
252
|
+
'RD-01': 'SRR', 'RD-02': 'SRR',
|
|
253
|
+
'UC-01': 'SRR', 'UC-02': 'SRR', 'UC-03': 'SRR', 'UC-04': 'SRR',
|
|
250
254
|
'R-16': 'SRR', 'R-17': 'SRR',
|
|
251
255
|
'FC-02': 'SRR',
|
|
252
256
|
'CL-01': 'SRR',
|
|
@@ -266,11 +270,12 @@ export const RULE_TO_PHASE = {
|
|
|
266
270
|
'ND-01': 'PDR',
|
|
267
271
|
'BW-02': 'PDR', 'CR-01': 'PDR', 'IO-01': 'PDR', 'IO-02': 'PDR', // CR-SM-226: extended to all FCHAIN FUNC-pairs, added to the phase axis.
|
|
268
272
|
// CDR — critical design/schema completeness.
|
|
269
|
-
'
|
|
273
|
+
'SC-02': 'CDR', // SC-04 entfiel mit CR-SM-271 — der FLOW-ohne-SCHEMA-Fall haengt an R-18/PDR
|
|
270
274
|
'ND-02': 'CDR',
|
|
271
275
|
'NFR-01': 'CDR',
|
|
272
276
|
// TRR — test readiness.
|
|
273
277
|
'R-01': 'TRR', 'R-05': 'TRR', 'R-32': 'TRR', 'R-19': 'TRR', 'R-20': 'TRR', 'R-21': 'TRR',
|
|
278
|
+
'R-26': 'TRR', // CR-SM-354: dieselbe Aussage wie R-20 (Praesenz von Code) — as-built ist TRR, nicht CDR
|
|
274
279
|
// CR-SM-231: eine doppelt beanspruchte Testdatei laesst den TRR-Gate dieselbe Evidenz
|
|
275
280
|
// doppelt zaehlen — der Befund gehoert genau an dieses Gate.
|
|
276
281
|
'R-29': 'TRR',
|
|
@@ -294,13 +299,13 @@ export const RULE_TASKS = ['kern', 'conops', 'trade', 'irr', 'fmea', 'plan', 'an
|
|
|
294
299
|
/** Die Regeln je Task (ohne Kern). Jede Regel hoechstens einmal — der Test haelt das fest. */
|
|
295
300
|
export const TASK_OWNED_RULES = {
|
|
296
301
|
conops: ['CL-01'],
|
|
297
|
-
trade: [],
|
|
298
|
-
irr: [],
|
|
302
|
+
trade: ['TR-01'], // CR-SM-355: Trade-Entscheidung als CR
|
|
303
|
+
irr: ['IR-01'], // CR-SM-355: Annahmen-Review promoviert zu CR
|
|
299
304
|
fmea: ['FM-01', 'FM-02', 'FM-03'],
|
|
300
305
|
plan: ['MS-01', 'MS-02', 'MS-03', 'CR-R01', 'CR-R02', 'CR-R03'],
|
|
301
306
|
// Annahme (CR-SM-350): BQ bekommt einen eigenen Task, vorerst ohne automatischen Eintrittspunkt.
|
|
302
307
|
anforderungsqualitaet: ['BQ-01', 'BQ-02', 'BQ-04', 'BQ-06', 'BQ-07'],
|
|
303
|
-
realisierung: ['R-19', 'R-20', 'R-26', 'R-
|
|
308
|
+
realisierung: ['R-19', 'R-20', 'R-26', 'R-29', 'R-32', 'VR-01', 'RC-01', 'RC-02', 'RC-03', 'RC-04', 'RC-05', 'RC-06', 'RC-07', 'RC-08', 'RC-09'],
|
|
304
309
|
};
|
|
305
310
|
/**
|
|
306
311
|
* Der Eintrittspunkt eines Tasks im Kern: die Regel, die "dieses Artefakt fehlt" sagt. `null` = der
|
package/dist/se/rule-help.js
CHANGED
|
@@ -300,7 +300,15 @@ export const RULE_HELP = {
|
|
|
300
300
|
},
|
|
301
301
|
'RC-07': {
|
|
302
302
|
plain: "A change request is marked done in the model but still open in the folder (or the other way round), or an open change request has no entry in the model → the folder decides; fix the model.",
|
|
303
|
-
se: "`CR` node whose `status` (closed = done
|
|
303
|
+
se: "`CR` node whose `status` (closed = done; CR-SM-362) contradicts `docs/cr/open|done/`, or an OPEN CR file with no `CR` node (anchored at the first `SYS`). Nodes without a file are not reported — history. Absent `crFiles` is SILENCE (CR-SM-329).",
|
|
304
|
+
},
|
|
305
|
+
'RC-08': {
|
|
306
|
+
plain: "A data contract in the model points at a type, which vanishes at runtime → write it as a Zod schema, so whoever produces the data can prove it and whoever reads it can check it.",
|
|
307
|
+
se: "`SCHEMA` whose `realRef` symbol is declared but is not a Zod schema (extractor field `zodSymbols`). Every producer needs a checkable schema — \"nobody parses it\" only means the reader is not known yet. Absent `zodSymbols` is SILENCE. Owns the type-binding cause: RC-04 stays quiet on such a SCHEMA (CR-SM-358).",
|
|
308
|
+
},
|
|
309
|
+
'RC-09': {
|
|
310
|
+
plain: "Code somewhere reads a data contract on its own, but the model does not know that place → go through the one translator the model names, or add the new translator to the model.",
|
|
311
|
+
se: "Zod-bound `SCHEMA` parsed (import + `.parse`/`.safeParse`) in a file that is neither its definition nor the `realRef` of a `FUNC` io-connected to a `FLOW` of this SCHEMA. The first code → model check: a second translator of one contract is a parallel path (spike CR-GC-637 — such paths share a contract, they are not similar). One finding per SCHEMA; test files exempt; runs only with `fileScope: 'all'` (CR-SM-358).",
|
|
304
312
|
},
|
|
305
313
|
'RD-01': {
|
|
306
314
|
plain: "A smallest-piece feature has nothing built to fulfil it → add what implements it.",
|
|
@@ -311,10 +319,6 @@ export const RULE_HELP = {
|
|
|
311
319
|
plain: "You split a feature into smaller features but you're also building the big one directly → build only the small pieces, not both.",
|
|
312
320
|
se: "Parent `REQ` (has `compose`→`REQ` children) carrying a direct `FUNC` `satisfy`; the satisfy belongs on the children.",
|
|
313
321
|
},
|
|
314
|
-
'RD-03': {
|
|
315
|
-
plain: "You split a feature into pieces, but all the pieces are handled by the same one thing — the split may be pointless → consider merging them.",
|
|
316
|
-
se: "Parent `REQ` whose children all share one satisfier.",
|
|
317
|
-
},
|
|
318
322
|
'RD-04': {
|
|
319
323
|
plain: "One thing has more than 9 parts directly under it → group them, so each level stays readable.",
|
|
320
324
|
se: "Decomposition breadth above `metricPolicy.decompositionBreadth.warning` children on one level — `FUNC` `compose` `FUNC`, `SYS` `compose` `MOD`, a `MOD`'s sub-`MOD`s plus its own leaf `FUNC` (`allocate`), and the root `FUNC` forest anchored at `SYS` (CR-SM-282). Default 9, the upper end of 7±2 (CR-SM-296). CR-SM-311: a module level counts its own functions again — breadth, not size; module coupling is R-04. The lower bound is RD-05.",
|
|
@@ -347,16 +351,19 @@ export const RULE_HELP = {
|
|
|
347
351
|
se: "`UC` description shorter than 10 characters or containing TBD/TODO/FIXME/placeholder/XXX. Description IS the goal (CR-150).",
|
|
348
352
|
prompt: "se:author-uc",
|
|
349
353
|
},
|
|
350
|
-
'UC-05': {
|
|
351
|
-
plain: "A scenario does not say what must be true once it has finished → add that as a requirement.",
|
|
352
|
-
se: "`UC` with no `compose`d `REQ` carrying `kinds:[\"postcondition\"]`.",
|
|
353
|
-
},
|
|
354
|
-
'UC-06': {
|
|
355
|
-
plain: "A scenario does not say what must be true before it can start → add that as a requirement.",
|
|
356
|
-
se: "`UC` with no `compose`d `REQ` carrying `kinds:[\"precondition\"]`.",
|
|
357
|
-
},
|
|
358
354
|
'VR-01': {
|
|
359
355
|
plain: "A test exists but no result was ever recorded, so nobody knows if it passed → record the outcome.",
|
|
360
356
|
se: "`TEST` with no `testResult` attribute — assumed pending, never assumed green.",
|
|
361
357
|
},
|
|
358
|
+
// CR-SM-355: Task-Ausgaenge — sichtbar nur im Task trade / irr.
|
|
359
|
+
'TR-01': {
|
|
360
|
+
plain: "The trade study is stamped, but the decision it produced is not on record as a CR (or the CR it names is missing or carries no decides edge) → record the decision as a CR and list its id in the stamp.",
|
|
361
|
+
se: "Task outcome rule (CR-SM-355): `analysisFreshness.trade.crRefs` names existing CR elements, each carrying a `relation` edge labelled `decides` (the SP-1 decision record).",
|
|
362
|
+
prompt: "se-trade",
|
|
363
|
+
},
|
|
364
|
+
'IR-01': {
|
|
365
|
+
plain: "The assumption review names a CR that does not exist → fix the reference or add the CR; an empty list is fine when no assumption was load-bearing.",
|
|
366
|
+
se: "Task outcome rule (CR-SM-355): every id in `analysisFreshness.assumption-review.crRefs` resolves to a CR element; empty is a legitimate outcome (low-risk assumptions stay in the record).",
|
|
367
|
+
prompt: "se-irr",
|
|
368
|
+
},
|
|
362
369
|
};
|
package/dist/se/rules.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @sigloch/contracts/se — single source of truth for SE validation rules.
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod/v4';
|
|
7
|
-
import { ElementType, TraceType,
|
|
7
|
+
import { ElementType, TraceType, readTestRefs, readRealRef } from './ontology.js';
|
|
8
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';
|
|
@@ -663,35 +663,6 @@ function decompositionConsistency(graph) {
|
|
|
663
663
|
context: { element_type: req.type, element_name: req.name },
|
|
664
664
|
}));
|
|
665
665
|
}
|
|
666
|
-
// ---------------------------------------------------------------------------
|
|
667
|
-
// RD-03: No premature decomposition (all children same satisfy target)
|
|
668
|
-
// ---------------------------------------------------------------------------
|
|
669
|
-
function noPrematureDecomposition(graph) {
|
|
670
|
-
const idx = indexOf(graph);
|
|
671
|
-
const reqs = idx.elementsOfType('REQ');
|
|
672
|
-
const parentReqs = reqs.filter(req => idx.out(req.id, 'compose').some(t => idx.typeOf(t.target) === 'REQ'));
|
|
673
|
-
return parentReqs
|
|
674
|
-
.filter(parent => {
|
|
675
|
-
const childIds = graph.traces
|
|
676
|
-
.filter(t => t.source === parent.id && t.type === 'compose' &&
|
|
677
|
-
idx.typeOf(t.target) === 'REQ')
|
|
678
|
-
.map(t => t.target);
|
|
679
|
-
if (childIds.length < 2)
|
|
680
|
-
return false;
|
|
681
|
-
const satisfyTargets = childIds.map(cid => idx.in(cid, 'satisfy').filter(t => t.source !== cid).map(t => t.source));
|
|
682
|
-
// All children satisfied by the same single source
|
|
683
|
-
const allSame = satisfyTargets.every(targets => targets.length === 1 && targets[0] === satisfyTargets[0]?.[0]);
|
|
684
|
-
return allSame && satisfyTargets[0]?.length === 1;
|
|
685
|
-
})
|
|
686
|
-
.map(req => ({
|
|
687
|
-
rule_id: 'RD-03',
|
|
688
|
-
severity: 'info',
|
|
689
|
-
element_id: req.id,
|
|
690
|
-
message: `${req.id} children all have the same satisfy target — decomposition may be unnecessary`,
|
|
691
|
-
fix_hint: 'Consider merging sub-REQs back into parent if they share the same verifiable level',
|
|
692
|
-
context: { element_type: req.type, element_name: req.name },
|
|
693
|
-
}));
|
|
694
|
-
}
|
|
695
666
|
function breadthCounts(graph) {
|
|
696
667
|
const idx = indexOf(graph);
|
|
697
668
|
// CR-SM-264: `typeOf` und `byId` baute diese Regel je Aufruf selbst — der Index hat beide.
|
|
@@ -919,7 +890,7 @@ function msDanglingDependency(graph) {
|
|
|
919
890
|
.filter(t => !msIds.has(t.target))
|
|
920
891
|
.map(t => ({
|
|
921
892
|
rule_id: 'MS-02',
|
|
922
|
-
severity: '
|
|
893
|
+
severity: 'warning',
|
|
923
894
|
element_id: t.source,
|
|
924
895
|
message: `${t.source} depends on non-existent milestone ${t.target}`,
|
|
925
896
|
fix_hint: 'Fix the depends-on relation target to reference an existing MS element',
|
|
@@ -1162,7 +1133,7 @@ function testMustHaveRunnableBinding(graph) {
|
|
|
1162
1133
|
.filter(e => e.attributes?.concept !== true) // concept-only TESTs are exempt
|
|
1163
1134
|
// CR-SM-231: `testRefs` (Array, min 1) statt `testRef` (Objekt). Semantik unveraendert —
|
|
1164
1135
|
// Praesenz und Form, keine Datei-Existenz; die pruefst RC-02.
|
|
1165
|
-
.filter(e =>
|
|
1136
|
+
.filter(e => readTestRefs(e.attributes).state !== 'bound')
|
|
1166
1137
|
.map(test => ({
|
|
1167
1138
|
rule_id: 'R-19',
|
|
1168
1139
|
severity: 'warning',
|
|
@@ -1194,12 +1165,12 @@ function testFileExclusivity(graph) {
|
|
|
1194
1165
|
for (const el of graph.elements) {
|
|
1195
1166
|
if (el.type !== 'TEST')
|
|
1196
1167
|
continue;
|
|
1197
|
-
const parsed =
|
|
1198
|
-
if (
|
|
1168
|
+
const parsed = readTestRefs(el.attributes);
|
|
1169
|
+
if (parsed.state !== 'bound')
|
|
1199
1170
|
continue; // keine/ungueltige Bindung → R-19-Gebiet
|
|
1200
1171
|
// Innerhalb EINES Knotens ist dieselbe Datei zweimal kein Konflikt, sondern eine
|
|
1201
1172
|
// Redundanz — sie wuerde sonst als Selbstkollision gemeldet.
|
|
1202
|
-
for (const file of new Set(parsed.
|
|
1173
|
+
for (const file of new Set(parsed.value.map(r => r.file))) {
|
|
1203
1174
|
const owners = claimedBy.get(file) ?? [];
|
|
1204
1175
|
owners.push(el.id);
|
|
1205
1176
|
claimedBy.set(file, owners);
|
|
@@ -1244,7 +1215,7 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
1244
1215
|
const composeFuncChildren = (id) => graph.traces
|
|
1245
1216
|
.filter(t => t.source === id && t.type === 'compose' && funcById.has(t.target))
|
|
1246
1217
|
.map(t => t.target);
|
|
1247
|
-
const hasCodeRef = (el) =>
|
|
1218
|
+
const hasCodeRef = (el) => readRealRef(el.attributes).state === 'bound';
|
|
1248
1219
|
const isExempt = (el) => el.attributes?.concept === true || el.attributes?.external === true;
|
|
1249
1220
|
// A FUNC is bound iff it is exempt, has a codeRef, or is a parent whose
|
|
1250
1221
|
// compose→FUNC children are all bound (recursive, cycle-safe).
|
|
@@ -1501,7 +1472,7 @@ function schemaMustHaveSchemaRef(graph) {
|
|
|
1501
1472
|
const idx = indexOf(graph);
|
|
1502
1473
|
return idx.elementsOfType('SCHEMA')
|
|
1503
1474
|
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
1504
|
-
.filter(e =>
|
|
1475
|
+
.filter(e => readRealRef(e.attributes).state !== 'bound')
|
|
1505
1476
|
.map(sc => ({
|
|
1506
1477
|
rule_id: 'R-26',
|
|
1507
1478
|
severity: 'warning',
|
|
@@ -1526,7 +1497,7 @@ function schemaMustHaveContractTest(graph) {
|
|
|
1526
1497
|
const tests = idx.elementsOfType('TEST');
|
|
1527
1498
|
return idx.elementsOfType('SCHEMA')
|
|
1528
1499
|
.filter(sc => sc.attributes?.concept !== true)
|
|
1529
|
-
.filter(sc => { const r =
|
|
1500
|
+
.filter(sc => { const r = readRealRef(sc.attributes); return r.state === 'bound' && r.value.symbol !== undefined; })
|
|
1530
1501
|
.filter(sc => !graph.traces.some(t => t.type === 'verify' && t.target === sc.id && typeOf.get(t.source) === 'TEST'))
|
|
1531
1502
|
.map(sc => ({
|
|
1532
1503
|
rule_id: 'R-32',
|
|
@@ -1686,11 +1657,10 @@ export const V3_RULES = [
|
|
|
1686
1657
|
{ id: 'R-32', name: 'SCHEMA must have contract TEST', severity: 'warning', evaluate: schemaMustHaveContractTest, domain: ['SCHEMA'] },
|
|
1687
1658
|
{ id: 'RD-01', name: 'Unresolved requirement', severity: 'warning', evaluate: unresolvedRequirement, domain: ['REQ'] },
|
|
1688
1659
|
{ id: 'RD-02', name: 'Decomposition consistency', severity: 'warning', evaluate: decompositionConsistency, domain: ['REQ'] },
|
|
1689
|
-
{ id: 'RD-03', name: 'No premature decomposition', severity: 'info', evaluate: noPrematureDecomposition, domain: ['REQ'] },
|
|
1690
1660
|
{ id: 'RD-04', name: 'Decomposition breadth', severity: 'warning', evaluate: decompositionBreadth, domain: ['FUNC', 'MOD', 'SYS'] },
|
|
1691
1661
|
{ id: 'RD-05', name: 'Decomposition too narrow', severity: 'warning', evaluate: decompositionNarrow, domain: ['FUNC', 'MOD', 'SYS'] },
|
|
1692
1662
|
{ id: 'MS-01', name: 'Milestone empty scope', severity: 'warning', evaluate: msEmptyScope, domain: ['MS'] },
|
|
1693
|
-
{ id: 'MS-02', name: 'Milestone dangling dependency', severity: '
|
|
1663
|
+
{ id: 'MS-02', name: 'Milestone dangling dependency', severity: 'warning', evaluate: msDanglingDependency, domain: ['MS'] },
|
|
1694
1664
|
{ id: 'R-30', name: 'FUNC leaf must belong to a function chain', severity: 'warning', evaluate: funcMustBeInEffectChain, domain: ['FUNC'] },
|
|
1695
1665
|
{ id: 'R-31', name: 'FUNC must be wired (io input + output)', severity: 'warning', evaluate: funcMustBeWired, domain: ['FUNC'] },
|
|
1696
1666
|
];
|
|
@@ -8,7 +8,5 @@ export declare function uc01HasRequirements(graph: OntologyGraph): RuleViolation
|
|
|
8
8
|
export declare function uc02HasActor(graph: OntologyGraph): RuleViolation[];
|
|
9
9
|
export declare function uc03HasScenario(graph: OntologyGraph): RuleViolation[];
|
|
10
10
|
export declare function uc04GoalDefined(graph: OntologyGraph): RuleViolation[];
|
|
11
|
-
export declare function uc05HasPostcondition(graph: OntologyGraph): RuleViolation[];
|
|
12
|
-
export declare function uc06HasPrecondition(graph: OntologyGraph): RuleViolation[];
|
|
13
11
|
export declare const UC_RULES: RuleDefinition[];
|
|
14
12
|
export declare function evaluateUCRules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
|