@sigloch/contracts 10.12.0 → 10.13.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/conformance-rules.js +40 -3
- package/dist/se/cr-quality-rules.js +72 -0
- package/dist/se/evaluate-all.d.ts +8 -1
- package/dist/se/evaluate-all.js +32 -29
- package/dist/se/fchain-quality-rules.d.ts +2 -1
- package/dist/se/fchain-quality-rules.js +132 -0
- package/dist/se/fmea-rules.js +19 -9
- package/dist/se/grammar-snapshot.d.ts +9 -9
- package/dist/se/grammar-snapshot.js +23 -9
- package/dist/se/index.d.ts +3 -3
- package/dist/se/index.js +3 -3
- package/dist/se/meta-model.js +27 -16
- package/dist/se/ontology.d.ts +42 -21
- package/dist/se/ontology.js +52 -16
- package/dist/se/readiness.d.ts +14 -0
- package/dist/se/readiness.js +30 -3
- package/dist/se/rule-help.js +19 -7
- package/dist/se/rules.d.ts +16 -0
- package/dist/se/rules.js +55 -26
- package/package.json +1 -1
package/dist/se/ontology.js
CHANGED
|
@@ -25,21 +25,25 @@ export const TraceType = z.enum([
|
|
|
25
25
|
'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation',
|
|
26
26
|
]);
|
|
27
27
|
/**
|
|
28
|
-
* REQ kind —
|
|
28
|
+
* REQ kind — GENAU EIN Wert je REQ, aus zwei (CR-SM-366).
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
30
|
+
* `functional` -> erfuellt von FUNC. `non-functional` -> erfuellt von MOD (lokales Budget), SYS
|
|
31
|
+
* (Systemebene) oder FCHAIN (Ende-zu-Ende entlang der Kette). Ohne kinds kein Erfueller (BQ-07,
|
|
32
|
+
* `satisfiesPredicate`). Ein REQ, das beides ist, ist falsch zerlegt -> teilen; die
|
|
33
|
+
* Teilmengen-Semantik der where-Praedikate laesst es von niemandem erfuellen.
|
|
34
|
+
*
|
|
35
|
+
* Entfallen (Major, CR-SM-366):
|
|
36
|
+
* - `precondition` / `postcondition`: die Vorbedingung steckt im Eingangs-FLOW der Kette, die
|
|
37
|
+
* Nachbedingung ist das UC-Ziel — beides prueft R-21 (Integrationstest je Kette). Ihre Leser
|
|
38
|
+
* UC-05/06 sind seit CR-SM-357 gestrichen.
|
|
39
|
+
* - `risk` / `mitigation`: die FMEA-ROLLE einer Anforderung, keine Art — `REQ.attributes.role`
|
|
40
|
+
* (CR-SM-365), satisfy-neutral.
|
|
41
|
+
* Ein Altwert wird nicht still verworfen: `normalizeReqKinds` laesst ihn stehen, das Schema
|
|
42
|
+
* lehnt ihn ab, und R-18 meldet jede satisfy-Kante darauf als `kinds-mismatch` mit `declared`.
|
|
43
|
+
*
|
|
44
|
+
* Vorher CR-SM-266 B: `negative` entfiel (ein Verbots-REQ ist `functional`).
|
|
38
45
|
*/
|
|
39
|
-
export const ReqKind = z.enum([
|
|
40
|
-
'functional', 'non-functional', 'risk',
|
|
41
|
-
'mitigation', 'precondition', 'postcondition',
|
|
42
|
-
]);
|
|
46
|
+
export const ReqKind = z.enum(['functional', 'non-functional']);
|
|
43
47
|
/**
|
|
44
48
|
* `kinds` in der Form, die WIRKLICH auf Platte liegt — als Liste.
|
|
45
49
|
*
|
|
@@ -183,6 +187,36 @@ export function readRealRef(attributes) {
|
|
|
183
187
|
const r = RealRefSchema.safeParse(raw);
|
|
184
188
|
return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
|
|
185
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* CR-SM-364: die Kopplung eines Uebergangs — `FLOW.attributes.sync`. Fehlend heisst UNBEKANNT,
|
|
192
|
+
* nie stillschweigend `sync`: eine Kettenkennzahl (synchrone Tiefe, Engstellen-Grad) weist eine
|
|
193
|
+
* unmarkierte Kette als „nicht bewertbar" aus, statt sie ueber eine Annahme zu beurteilen.
|
|
194
|
+
*/
|
|
195
|
+
export const FlowSync = z.enum(['sync', 'async']);
|
|
196
|
+
/** Der EINE Leser von `FLOW.attributes.sync` (CR-SM-364) — ungueltig ist nicht fehlend. */
|
|
197
|
+
export function readFlowSync(attributes) {
|
|
198
|
+
const raw = attributes?.sync;
|
|
199
|
+
if (raw === undefined || raw === null)
|
|
200
|
+
return { state: 'absent' };
|
|
201
|
+
const r = FlowSync.safeParse(raw);
|
|
202
|
+
return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* CR-SM-365: die ROLLE einer Anforderung im FMEA-Sinn — `REQ.attributes.role`. `risk` und
|
|
206
|
+
* `mitigation` waren bis hierher `kinds`-Werte und damit satisfy-relevant (MOD/SYS erfuellten
|
|
207
|
+
* sie). Sie sind aber keine ART der Anforderung, sondern ihre Motivation: eine Gegenmassnahme ist
|
|
208
|
+
* funktional oder nicht-funktional wie jede andere REQ. Die Rolle ist satisfy-neutral — die
|
|
209
|
+
* Grammatik (`isValidTrace`) liest sie nicht; FM-01..03 lesen NUR sie.
|
|
210
|
+
*/
|
|
211
|
+
export const ReqRole = z.enum(['risk', 'mitigation']);
|
|
212
|
+
/** Der EINE Leser von `REQ.attributes.role` (CR-SM-365) — ungueltig ist nicht fehlend. */
|
|
213
|
+
export function readReqRole(attributes) {
|
|
214
|
+
const raw = attributes?.role;
|
|
215
|
+
if (raw === undefined || raw === null)
|
|
216
|
+
return { state: 'absent' };
|
|
217
|
+
const r = ReqRole.safeParse(raw);
|
|
218
|
+
return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
|
|
219
|
+
}
|
|
186
220
|
/**
|
|
187
221
|
* The five judgment-work artifact ids (CR-GC-221's creation keys) that graphcode's
|
|
188
222
|
* skills produce and that a phase gate checks for presence+freshness (CR-SM-227).
|
|
@@ -263,7 +297,7 @@ export const OntologyElement = z.object({
|
|
|
263
297
|
description: z.string(),
|
|
264
298
|
/** INCOSE TIAD verification method — only for TEST elements (CR-057). */
|
|
265
299
|
method: VerificationMethod.optional(),
|
|
266
|
-
/** REQ kinds:
|
|
300
|
+
/** REQ kinds: exactly ONE of functional|non-functional (CR-SM-366); a list for the on-disk form. Only for REQ elements. */
|
|
267
301
|
kinds: z.array(ReqKind).optional(),
|
|
268
302
|
attributes: z.record(z.string(), z.unknown()).optional(),
|
|
269
303
|
status: z.enum(['draft', 'reviewed', 'open', 'done']).default('draft'),
|
|
@@ -335,6 +369,7 @@ export const ELEMENT_ATTRIBUTES = {
|
|
|
335
369
|
{ key: 'concept', type: 'boolean', description: 'Concept-only TEST: no run artifact yet; exempt from the R-19 testRefs-binding requirement (CR-GC-205)' },
|
|
336
370
|
],
|
|
337
371
|
REQ: [
|
|
372
|
+
{ key: 'role', type: 'enum', enumValues: ReqRole.options, description: "FMEA role: 'risk' | 'mitigation' — satisfy-neutral, read by FM-01..03 via readReqRole. A mitigation is functional or non-functional like any REQ; the role is its motivation, not its kind (CR-SM-365)" },
|
|
338
373
|
{ key: 'severity', type: 'number', description: 'FMEA severity (1-10)' },
|
|
339
374
|
{ key: 'occurrence', type: 'number', description: 'FMEA occurrence (1-10)' },
|
|
340
375
|
{ key: 'detection', type: 'number', description: 'FMEA detection (1-10)' },
|
|
@@ -356,9 +391,10 @@ export const ELEMENT_ATTRIBUTES = {
|
|
|
356
391
|
{ key: 'rationale', type: 'string', description: 'Reason for the change' },
|
|
357
392
|
{ key: 'spike', type: 'boolean', description: 'CR is a spike/exploration' },
|
|
358
393
|
],
|
|
394
|
+
// CR-SM-364: genau ein Realisierungsattribut. `protocol`/`qos` entfielen — freie Strings, in
|
|
395
|
+
// 0 von 186 Familien-FLOWs gesetzt, ohne Leser. Abweichende Kopplung je Verbraucher ⇒ FLOW teilen.
|
|
359
396
|
FLOW: [
|
|
360
|
-
{ key: '
|
|
361
|
-
{ key: 'qos', type: 'string', description: 'Quality of Service level' },
|
|
397
|
+
{ key: 'sync', type: 'enum', enumValues: FlowSync.options, description: "Coupling of the transition: 'sync' | 'async'. Missing = unknown, never an implicit 'sync' — read with readFlowSync (CR-SM-364)" },
|
|
362
398
|
],
|
|
363
399
|
MOD: [
|
|
364
400
|
{ key: 'path', type: 'string', description: 'Source file or glob the module owns, e.g. src/harness.ts — anchors the MOD<->file mapping for graph<->code conformance (CR-GC-205)' },
|
package/dist/se/readiness.d.ts
CHANGED
|
@@ -170,6 +170,20 @@ export type PhaseGateType = z.infer<typeof PhaseGate>;
|
|
|
170
170
|
* single-owner convention as RULE_TO_DIMENSION.
|
|
171
171
|
*/
|
|
172
172
|
export declare const RULE_TO_PHASE: Record<string, PhaseGateType>;
|
|
173
|
+
/**
|
|
174
|
+
* CR-SM-343 — die VORBEDINGUNG einer Regel: der Elementtyp, von dem der Graph mindestens ein
|
|
175
|
+
* Element tragen muss, damit die Regel ueberhaupt ausgewertet ist.
|
|
176
|
+
*
|
|
177
|
+
* Der Nenner einer Dimension ist `Σ Grundgesamtheit` ueber ihre Regeln (`domain`). Eine Regel,
|
|
178
|
+
* die mangels Gegenstand schweigt, truege dort ihre ganze Grundgesamtheit bei und liefert null
|
|
179
|
+
* Verstoesse — der Score STIEGE, weil nicht geprueft wurde. CR-R05 fragt nach einem Bauauftrag;
|
|
180
|
+
* ein Graph ohne einen einzigen CR (`cr: docs`) hat keinen Plan im Graphen, also ist die Frage
|
|
181
|
+
* dort NICHT GESTELLT, nicht bestanden. Wer einen Anteil bildet, fragt `ruleApplies` und laesst
|
|
182
|
+
* eine nicht anwendbare Regel aus Zaehler UND Nenner.
|
|
183
|
+
*/
|
|
184
|
+
export declare const RULE_PRECONDITION: Readonly<Record<string, string>>;
|
|
185
|
+
/** Ist die Regel an einem Graphen mit diesen Typ-Zaehlungen ausgewertet? (CR-SM-343) */
|
|
186
|
+
export declare function ruleApplies(ruleId: string, countByType: Readonly<Record<string, number>>): boolean;
|
|
173
187
|
/**
|
|
174
188
|
* Die Eigentuemer-Spalte (CR-SM-350, Entscheidung 2026-09-22): welcher TASK eine Regel bearbeitet.
|
|
175
189
|
*
|
package/dist/se/readiness.js
CHANGED
|
@@ -152,6 +152,9 @@ export const RULE_TO_DIMENSION = {
|
|
|
152
152
|
'BQ-01': 'req', 'BQ-02': 'req', 'BQ-04': 'req',
|
|
153
153
|
'BQ-06': 'req', 'BQ-07': 'req',
|
|
154
154
|
'RD-01': 'req', 'RD-02': 'req',
|
|
155
|
+
// CR-SM-343: CR-R05 (Blatt-REQ ohne Bauauftrag) — ein Mangel der Anforderungsdeckung, nicht
|
|
156
|
+
// der CR-Hygiene; zaehlt nur, wenn der Graph ueberhaupt CR-Knoten traegt (RULE_PRECONDITION).
|
|
157
|
+
'CR-R05': 'req',
|
|
155
158
|
// RD-04 is decomposition *breadth* — an architecture concern, not a requirement one
|
|
156
159
|
'RD-04': 'arch',
|
|
157
160
|
// CR-SM-311: die Untergrenze derselben Breite — ebenfalls Architektur
|
|
@@ -171,6 +174,7 @@ export const RULE_TO_DIMENSION = {
|
|
|
171
174
|
'R-15': 'uc', 'R-16': 'uc', 'R-17': 'uc',
|
|
172
175
|
// FC-04 (CR-SM-226): FCHAIN actor-bounded (trigger+consumer) — same dimension as FC-01..03.
|
|
173
176
|
'FC-02': 'uc', 'FC-03': 'uc', 'FC-04': 'uc',
|
|
177
|
+
'FC-05': 'uc', // CR-SM-363: Zusammenhang der Kette — dieselbe Dimension wie FC-04
|
|
174
178
|
// arch
|
|
175
179
|
'R-02': 'arch', 'R-10': 'arch', 'R-12': 'arch',
|
|
176
180
|
// alloc
|
|
@@ -262,7 +266,7 @@ export const RULE_TO_PHASE = {
|
|
|
262
266
|
'R-02': 'PDR', 'R-08': 'PDR', 'R-10': 'PDR', 'R-12': 'PDR', 'R-18': 'PDR',
|
|
263
267
|
'R-04': 'PDR', 'R-22': 'PDR', 'R-23': 'PDR',
|
|
264
268
|
'R-15': 'PDR',
|
|
265
|
-
'FC-03': 'PDR', 'FC-04': 'PDR',
|
|
269
|
+
'FC-03': 'PDR', 'FC-04': 'PDR', 'FC-05': 'PDR',
|
|
266
270
|
// CR-GC-366: Anschluss des Funktionsbaus — gehoert an dasselbe Gate wie R-15/IO-01,
|
|
267
271
|
// die auf derselben Kette aufsetzen.
|
|
268
272
|
'R-30': 'PDR', 'R-31': 'PDR',
|
|
@@ -282,7 +286,27 @@ export const RULE_TO_PHASE = {
|
|
|
282
286
|
'VR-01': 'TRR',
|
|
283
287
|
'FM-03': 'TRR',
|
|
284
288
|
'CR-R02': 'TRR',
|
|
289
|
+
'CR-R05': 'TRR', // CR-SM-343: dieselbe Phase wie AF-05 (Implementierungsplan)
|
|
285
290
|
};
|
|
291
|
+
/**
|
|
292
|
+
* CR-SM-343 — die VORBEDINGUNG einer Regel: der Elementtyp, von dem der Graph mindestens ein
|
|
293
|
+
* Element tragen muss, damit die Regel ueberhaupt ausgewertet ist.
|
|
294
|
+
*
|
|
295
|
+
* Der Nenner einer Dimension ist `Σ Grundgesamtheit` ueber ihre Regeln (`domain`). Eine Regel,
|
|
296
|
+
* die mangels Gegenstand schweigt, truege dort ihre ganze Grundgesamtheit bei und liefert null
|
|
297
|
+
* Verstoesse — der Score STIEGE, weil nicht geprueft wurde. CR-R05 fragt nach einem Bauauftrag;
|
|
298
|
+
* ein Graph ohne einen einzigen CR (`cr: docs`) hat keinen Plan im Graphen, also ist die Frage
|
|
299
|
+
* dort NICHT GESTELLT, nicht bestanden. Wer einen Anteil bildet, fragt `ruleApplies` und laesst
|
|
300
|
+
* eine nicht anwendbare Regel aus Zaehler UND Nenner.
|
|
301
|
+
*/
|
|
302
|
+
export const RULE_PRECONDITION = {
|
|
303
|
+
'CR-R05': 'CR',
|
|
304
|
+
};
|
|
305
|
+
/** Ist die Regel an einem Graphen mit diesen Typ-Zaehlungen ausgewertet? (CR-SM-343) */
|
|
306
|
+
export function ruleApplies(ruleId, countByType) {
|
|
307
|
+
const required = RULE_PRECONDITION[ruleId];
|
|
308
|
+
return required === undefined || (countByType[required] ?? 0) > 0;
|
|
309
|
+
}
|
|
286
310
|
/**
|
|
287
311
|
* Die Eigentuemer-Spalte (CR-SM-350, Entscheidung 2026-09-22): welcher TASK eine Regel bearbeitet.
|
|
288
312
|
*
|
|
@@ -302,10 +326,13 @@ export const TASK_OWNED_RULES = {
|
|
|
302
326
|
trade: ['TR-01'], // CR-SM-355: Trade-Entscheidung als CR
|
|
303
327
|
irr: ['IR-01'], // CR-SM-355: Annahmen-Review promoviert zu CR
|
|
304
328
|
fmea: ['FM-01', 'FM-02', 'FM-03'],
|
|
305
|
-
|
|
329
|
+
// CR-SM-377: CR-R05 (Blatt-REQ ohne Bauauftrag, CR-SM-343) fragt nach einem CR — die Antwort
|
|
330
|
+
// schreibt der Plan-Task (se-plan), nicht die Generierungsschleife. FC-05 bleibt Kern: die
|
|
331
|
+
// Kette zu schliessen ist Spezifikationsarbeit wie FC-04.
|
|
332
|
+
plan: ['MS-01', 'MS-02', 'MS-03', 'CR-R01', 'CR-R02', 'CR-R03', 'CR-R05'],
|
|
306
333
|
// Annahme (CR-SM-350): BQ bekommt einen eigenen Task, vorerst ohne automatischen Eintrittspunkt.
|
|
307
334
|
anforderungsqualitaet: ['BQ-01', 'BQ-02', 'BQ-04', 'BQ-06', 'BQ-07'],
|
|
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'],
|
|
335
|
+
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', 'RC-10'],
|
|
309
336
|
};
|
|
310
337
|
/**
|
|
311
338
|
* Der Eintrittspunkt eines Tasks im Kern: die Regel, die "dieses Artefakt fehlt" sagt. `null` = der
|
package/dist/se/rule-help.js
CHANGED
|
@@ -115,6 +115,11 @@ export const RULE_HELP = {
|
|
|
115
115
|
plain: "Several open changes touch the same thing, so they will collide → sequence them or merge them.",
|
|
116
116
|
se: "One element tracked by more than one `CR` with `status` open/in-progress.",
|
|
117
117
|
},
|
|
118
|
+
'CR-R05': {
|
|
119
|
+
plain: "This smallest-piece requirement has no work order — no change request points at it or at the function that fulfils it, and that function is not built yet, so no plan will ever build it → attach it to the change request that builds it.",
|
|
120
|
+
se: "Leaf `REQ` (no `compose`→`REQ` children) with no build order: no `CR -relation->` on the REQ itself nor on a `FUNC` that `satisfy`s it (CR-SM-343; the REQ side of CR-R01). A `FUNC` that is already built — valid `realRef` or `external`, not `concept` — covers the REQ without a CR (CR-SM-375). A CR on a `MOD`/`SYS` carrier does not count — a module is a container; such REQs take a direct `CR -relation-> REQ`. Silent, and not scored, on a graph without any `CR` node (`RULE_PRECONDITION`).",
|
|
121
|
+
prompt: "se-plan",
|
|
122
|
+
},
|
|
118
123
|
'FC-02': {
|
|
119
124
|
plain: "A scenario that is not broken into sub-scenarios has no described sequence of steps → add one, even if the steps are done by hand.",
|
|
120
125
|
se: "Leaf `UC` (no `UC -compose-> UC`) with no `FCHAIN`. A chain may consist of EXISTING FUNCs an actor strings together — it costs a node plus compose edges, not code.",
|
|
@@ -128,19 +133,23 @@ export const RULE_HELP = {
|
|
|
128
133
|
plain: "A sequence either has nobody starting it or nothing coming back out → wire both ends to whoever uses it.",
|
|
129
134
|
se: "`FCHAIN` lacking an entry (`ACTOR -io-> FLOW -io-> FUNC∈chain`) or an exit (`FUNC∈chain -io-> FLOW -io-> ACTOR`). Stricter than FC-01: both directions, at FUNC/FLOW level, no UC-level bypass.",
|
|
130
135
|
},
|
|
136
|
+
'FC-05': {
|
|
137
|
+
plain: "The steps of this sequence do not hand their results to each other — it falls apart into pieces, so nobody can say how long it is or where it gets stuck → add the missing step, connect the pieces with a data flow, or split it into separate sequences.",
|
|
138
|
+
se: "`FCHAIN` whose member `FUNC`s do not form one (weakly) connected component along producer → consumer (`FUNC -io-> FLOW -io-> FUNC`, both ends members). Per side component the cause: `missing-link` (main and side part are joined through a `FUNC` outside the chain — compose it) or `bag` (no FUNC joins them: a missing FLOW between members, or parallel services at a shared source — split). One finding per chain, components in `context.side_components` (CR-SM-242). Silent while any member lacks an io input or output (R-31 first); entry/exit belong to FC-04 (CR-SM-363).",
|
|
139
|
+
},
|
|
131
140
|
'FM-01': {
|
|
132
141
|
plain: "A requirement is marked as a risk but carries no risk ratings → rate how bad, how likely and how detectable it is (1-10 each).",
|
|
133
|
-
se: "
|
|
142
|
+
se: "`REQ` with `role:\"risk\"` missing `severity` / `occurrence` / `detection` attributes (AIAG-VDA). The role is the requirement's motivation, not its kind — satisfy-neutral (CR-SM-365).",
|
|
134
143
|
prompt: "se-fmea",
|
|
135
144
|
},
|
|
136
145
|
'FM-02': {
|
|
137
146
|
plain: "A known risk has nothing planned against it → write the countermeasure as its own requirement.",
|
|
138
|
-
se: "
|
|
147
|
+
se: "`REQ` with `role:\"risk\"` and no `compose`d `REQ` with `role:\"mitigation\"`. The mitigation keeps its own kind (functional or non-functional) and is satisfied like any REQ of that kind (CR-SM-365).",
|
|
139
148
|
prompt: "se-fmea",
|
|
140
149
|
},
|
|
141
150
|
'FM-03': {
|
|
142
151
|
plain: "A high risk has no test that actually passed → add a test proving the countermeasure works.",
|
|
143
|
-
se: "
|
|
152
|
+
se: "`REQ` with `role:\"risk\"`, Action Priority High (AIAG-VDA, RPN band as interim) and no verifying `TEST` whose every `testRefs` entry has `result:\"passed\"`.",
|
|
144
153
|
prompt: "se-fmea",
|
|
145
154
|
},
|
|
146
155
|
'IO-01': {
|
|
@@ -247,8 +256,8 @@ export const RULE_HELP = {
|
|
|
247
256
|
se: "Realized `FUNC` with no valid `realRef` `{file, symbol}` (graph↔code binding); else `concept:true` / `external:true`.",
|
|
248
257
|
},
|
|
249
258
|
'R-21': {
|
|
250
|
-
plain: "Two functions hand data over, but they are not in one chain together — or they are, and
|
|
251
|
-
se: "FUNC↔FUNC handover (`FUNC` ─io→ `FLOW` ─io→ `FUNC`), two findings. (a) endpoints share NO `FCHAIN` → finding at the PRODUCER: no integration scope is declared. (b) endpoints share one, but no shared chain
|
|
259
|
+
plain: "Two functions hand data over, but they are not in one chain together — or they are, and the chain is not covered: some function in it has no requirement, and no end-to-end test checks the chain → give each function of the chain a requirement it fulfils, or give the chain an end-to-end quality requirement and a test for it.",
|
|
260
|
+
se: "FUNC↔FUNC handover (`FUNC` ─io→ `FLOW` ─io→ `FUNC`), two findings. (a) endpoints share NO `FCHAIN` → finding at the PRODUCER: no integration scope is declared. (b) endpoints share one, but no shared chain is covered → finding at the chain, naming the member FUNCs without a REQ. A chain is covered either (1) through its members — EVERY member FUNC (`FCHAIN` ─compose→ `FUNC`) satisfies at least one `REQ`; verifying those REQs is R-01's job (CR-SM-376) — or (2) end-to-end — a verified integration test (`TEST` ─verify→ `REQ` ←satisfy─ `FCHAIN`); an `FCHAIN` satisfies only non-functional REQs (CR-SM-366). SILENT when either endpoint is in no chain at all (that is R-30's statement) and when either is infrastructure (`chains >= policy.criticality.infrastructure`): a handover into a function that runs through every chain is a bus, not a missing test.",
|
|
252
261
|
},
|
|
253
262
|
'R-22': {
|
|
254
263
|
plain: "A function isn't assigned to any building block, so it has no home in the structure → put it on a module.",
|
|
@@ -310,6 +319,10 @@ export const RULE_HELP = {
|
|
|
310
319
|
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
320
|
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).",
|
|
312
321
|
},
|
|
322
|
+
'RC-10': {
|
|
323
|
+
plain: "No file in the code can be assigned to this building block, so nobody can check whether its imports cross boundaries the model does not know → give the block its source folder, or point one of its functions at its code.",
|
|
324
|
+
se: "`MOD` with neither a `path` attribute nor an allocated `FUNC` carrying a bound `realRef` — the only two ways a file resolves to a module. Without either, RC-05 and the boundary measurement are blind for this module by construction (sigllm: 0 RC-05 findings at 19 % import coverage, 8 at 81 %). The message names the cost from `importCoverage`. `concept`/`external` modules are exempt, as for R-20 (CR-SM-344).",
|
|
325
|
+
},
|
|
313
326
|
'RD-01': {
|
|
314
327
|
plain: "A smallest-piece feature has nothing built to fulfil it → add what implements it.",
|
|
315
328
|
se: "Leaf `REQ` (no `compose`→`REQ` children) with no `satisfy` from a `FUNC`/`FCHAIN`/`MOD`/`SYS`.",
|
|
@@ -338,8 +351,7 @@ export const RULE_HELP = {
|
|
|
338
351
|
},
|
|
339
352
|
'UC-02': {
|
|
340
353
|
plain: "A scenario has nobody who triggers it → name who or what starts it.",
|
|
341
|
-
se: "`UC`
|
|
342
|
-
prompt: "se:author-uc",
|
|
354
|
+
se: "`UC` that no `ACTOR` reaches: the one legal path is `ACTOR -io-> FLOW -io-> FUNC` (or back, `FUNC -io-> FLOW -io-> ACTOR`), the FUNC a member of one of the UC's chains (`UC -compose-> FCHAIN -compose-> FUNC`). A direct `ACTOR -io-> UC` is rejected by R-18. No skill prompt (CR-SM-370): `se:author-uc` shows creating UCs and `se:author-actor` says to leave actors unwired — neither is this work.",
|
|
343
355
|
},
|
|
344
356
|
'UC-03': {
|
|
345
357
|
plain: "A scenario says what should be possible but not how it runs → describe the steps as a chain of functions.",
|
package/dist/se/rules.d.ts
CHANGED
|
@@ -81,6 +81,14 @@ 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
|
+
side_components: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
85
|
+
members: z.ZodArray<z.ZodString>;
|
|
86
|
+
cause: z.ZodEnum<{
|
|
87
|
+
"missing-link": "missing-link";
|
|
88
|
+
bag: "bag";
|
|
89
|
+
}>;
|
|
90
|
+
via: z.ZodArray<z.ZodString>;
|
|
91
|
+
}, z.core.$strip>>>;
|
|
84
92
|
trace_rejection: z.ZodOptional<z.ZodObject<{
|
|
85
93
|
reason: z.ZodEnum<{
|
|
86
94
|
"no-pattern": "no-pattern";
|
|
@@ -212,6 +220,14 @@ export declare const RuleViolation: z.ZodObject<{
|
|
|
212
220
|
parent_module: z.ZodOptional<z.ZodString>;
|
|
213
221
|
current_description: z.ZodOptional<z.ZodString>;
|
|
214
222
|
also_affects: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
223
|
+
side_components: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
224
|
+
members: z.ZodArray<z.ZodString>;
|
|
225
|
+
cause: z.ZodEnum<{
|
|
226
|
+
"missing-link": "missing-link";
|
|
227
|
+
bag: "bag";
|
|
228
|
+
}>;
|
|
229
|
+
via: z.ZodArray<z.ZodString>;
|
|
230
|
+
}, z.core.$strip>>>;
|
|
215
231
|
trace_rejection: z.ZodOptional<z.ZodObject<{
|
|
216
232
|
reason: z.ZodEnum<{
|
|
217
233
|
"no-pattern": "no-pattern";
|
package/dist/se/rules.js
CHANGED
|
@@ -35,6 +35,17 @@ export const ViolationContext = z.object({
|
|
|
35
35
|
* unterschlagen.
|
|
36
36
|
*/
|
|
37
37
|
also_affects: z.array(z.string()).optional(),
|
|
38
|
+
/**
|
|
39
|
+
* CR-SM-363 (FC-05): die NEBENKOMPONENTEN einer zerfallenen FCHAIN, je mit Ursache —
|
|
40
|
+
* `missing-link` (Haupt- und Nebenteil sind ueber eine FUNC ausserhalb der Kette verbunden;
|
|
41
|
+
* `via` nennt sie) oder `bag` (nur eine gemeinsame Quelle, parallele Dienste). EIN Befund je
|
|
42
|
+
* Kette (CR-SM-242), die Komponenten hier statt als n Befunde.
|
|
43
|
+
*/
|
|
44
|
+
side_components: z.array(z.object({
|
|
45
|
+
members: z.array(z.string()),
|
|
46
|
+
cause: z.enum(['missing-link', 'bag']),
|
|
47
|
+
via: z.array(z.string()),
|
|
48
|
+
})).optional(),
|
|
38
49
|
/**
|
|
39
50
|
* CR-SM-341: der Ablehnungsgrund von R-18, maschinenlesbar statt nur als Satz.
|
|
40
51
|
*
|
|
@@ -242,24 +253,15 @@ function funcMustSatisfyReq(graph) {
|
|
|
242
253
|
.map(st => idx.byId.get(st.target))
|
|
243
254
|
.filter((e) => !!e)
|
|
244
255
|
: [];
|
|
245
|
-
// Also include constraints whose description mentions this func's name
|
|
246
|
-
const namePattern = fn.name.toLowerCase();
|
|
247
|
-
const matchingConstraints = allReqs.filter(r => r.kinds?.includes('non-functional') && r.description.toLowerCase().includes(namePattern));
|
|
248
256
|
// CR-SM-346 (ITEM-2026-299): der Kandidatenkreis wird am META-MODELL gefiltert, nicht
|
|
249
|
-
// an einer zweiten, hier abgeschriebenen kinds-Liste
|
|
250
|
-
//
|
|
251
|
-
//
|
|
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.
|
|
257
|
+
// an einer zweiten, hier abgeschriebenen kinds-Liste — `isValidTrace` statt Eigenbau: eine
|
|
258
|
+
// Regel, die Kandidaten anbietet, die ihr eigenes Meta-Modell abweist, erzeugt Waste statt
|
|
259
|
+
// Fuehrung. `FUNC -satisfy-> REQ` laesst nur `functional` zu (CR-SM-366).
|
|
258
260
|
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
const candidates = [...new Map([...siblingReqs, ...
|
|
261
|
+
// CR-SM-366: der fruehere `matchingConstraints`-Zweig (non-functional-REQ, deren Text den
|
|
262
|
+
// FUNC-Namen nennt) ist entfallen. Er zog genau die Sorte herein, die das Muster abweist;
|
|
263
|
+
// seit ein REQ genau EIN kind traegt, konnte kein Treffer den Filter je passieren.
|
|
264
|
+
const candidates = [...new Map([...siblingReqs, ...allReqs].map(e => [e.id, e])).values()]
|
|
263
265
|
.filter(r => isValidTrace({ source: 'FUNC', target: 'REQ', type: 'satisfy', targetKinds: r.kinds }));
|
|
264
266
|
return {
|
|
265
267
|
rule_id: 'R-02',
|
|
@@ -827,7 +829,9 @@ function actorMustHaveTrace(graph) {
|
|
|
827
829
|
severity: 'warning',
|
|
828
830
|
element_id: a.id,
|
|
829
831
|
message: `${a.id} has no io traces (disconnected actor)`,
|
|
830
|
-
|
|
832
|
+
// CR-SM-371: ein ACTOR hat genau zwei legale Kanten (ACTOR -io-> FLOW, FLOW -io-> ACTOR);
|
|
833
|
+
// der UC wird ueber den FLOW an einer FUNC seiner Kette erreicht, nie direkt (R-18).
|
|
834
|
+
fix_hint: `Wire the actor through a FLOW via graph_mutate Format-E: \`${a.id} -io-> FLOW-x\` (it sends) or \`FLOW-x -io-> ${a.id}\` (it receives), plus \`FLOW-x -io-> FUNC-y\` or \`FUNC-y -io-> FLOW-x\` for a FUNC in the use case's chain — never an io edge to the UC itself`,
|
|
831
835
|
context: {
|
|
832
836
|
element_type: a.type,
|
|
833
837
|
element_name: a.name,
|
|
@@ -927,7 +931,6 @@ function validTracePattern(graph) {
|
|
|
927
931
|
});
|
|
928
932
|
if (!why)
|
|
929
933
|
return [];
|
|
930
|
-
const { detail, hint } = explainRejection(why, t, src, tgt);
|
|
931
934
|
// CR-SM-341: derselbe Grund, den `detail` in Prosa giesst — hier zusaetzlich als Struktur.
|
|
932
935
|
// Keine zweite Rechnung: `why` liegt vor, die Alternativen befragen `isValidTrace`.
|
|
933
936
|
const shape = {
|
|
@@ -936,6 +939,7 @@ function validTracePattern(graph) {
|
|
|
936
939
|
targetKinds: idx.byId.get(t.target)?.kinds,
|
|
937
940
|
};
|
|
938
941
|
const alt = admittingAlternatives(shape);
|
|
942
|
+
const { detail, hint } = explainRejection(why, t, src, tgt, alt.sources);
|
|
939
943
|
const where = why.reason === 'no-pattern' ? undefined : why.pattern.where;
|
|
940
944
|
return [{
|
|
941
945
|
rule_id: 'R-18',
|
|
@@ -970,7 +974,7 @@ function validTracePattern(graph) {
|
|
|
970
974
|
* `kinds` steht da und passt nicht (Kante heben oder Kind korrigieren). Wer das nicht
|
|
971
975
|
* unterscheiden kann, sucht am falschen Ende.
|
|
972
976
|
*/
|
|
973
|
-
function explainRejection(why, t, src, tgt) {
|
|
977
|
+
function explainRejection(why, t, src, tgt, admittingSources) {
|
|
974
978
|
if (why.reason === 'no-pattern') {
|
|
975
979
|
return {
|
|
976
980
|
detail: `${src} → ${tgt} is not a valid ${t.type} pattern`,
|
|
@@ -991,7 +995,11 @@ function explainRejection(why, t, src, tgt) {
|
|
|
991
995
|
}
|
|
992
996
|
: {
|
|
993
997
|
detail: `${admits}, but ${end} declares {${[...why.declared].sort().join(', ')}}`,
|
|
994
|
-
|
|
998
|
+
// CR-SM-366: den Erfueller NENNEN statt "FCHAIN carries every kind" — das galt nie fuer
|
|
999
|
+
// die Code-Bindung und gilt seit dem FCHAIN-where gar nicht mehr.
|
|
1000
|
+
hint: admittingSources.length > 0
|
|
1001
|
+
? `Correct '${where.field}' on ${end}, or ${t.type} it from ${admittingSources.join(' | ')} — the pattern admitting {${[...why.declared].sort().join(', ')}}`
|
|
1002
|
+
: `Correct '${where.field}' on ${end} to a subset of {${allowed}} — no source type admits {${[...why.declared].sort().join(', ')}} (retired kind? split a mixed REQ)`,
|
|
995
1003
|
};
|
|
996
1004
|
}
|
|
997
1005
|
/**
|
|
@@ -1270,7 +1278,7 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
1270
1278
|
severity: 'warning',
|
|
1271
1279
|
element_id: fn.id,
|
|
1272
1280
|
message: `${fn.id} is not fully realized — unbound compose ${leafList.length === 1 ? 'child' : 'children'}: ${leafList.join(', ')}`,
|
|
1273
|
-
fix_hint: `
|
|
1281
|
+
fix_hint: `Bind the leaves via graph_mutate Format-E (${leafList.map((id) => `\`~ ${id} @realRef {"file":…,"symbol":…}\``).join(', ')}) — the parent inherits their binding`,
|
|
1274
1282
|
context: { element_type: fn.type, element_name: fn.name },
|
|
1275
1283
|
});
|
|
1276
1284
|
}
|
|
@@ -1300,6 +1308,10 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
1300
1308
|
// Co-adjacency at a shared FLOW is not an asserted interface — treating it
|
|
1301
1309
|
// as one produced P·C findings per hub FLOW and made reuse the expensive
|
|
1302
1310
|
// choice. The FCHAIN is the modelled claim; the test is owed on the claim.
|
|
1311
|
+
// CR-SM-376: second way to cover a chain. Since CR-SM-366 an FCHAIN carries
|
|
1312
|
+
// only NFRs; its effect is covered through its members when EVERY member
|
|
1313
|
+
// FUNC (FCHAIN ─compose→ FUNC) satisfies at least one REQ. Verifying those
|
|
1314
|
+
// member REQs is R-01's job, not R-21's.
|
|
1303
1315
|
// ---------------------------------------------------------------------------
|
|
1304
1316
|
function fchainMustHaveIntegrationTest(graph, policy) {
|
|
1305
1317
|
const idx = indexOf(graph);
|
|
@@ -1333,6 +1345,22 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1333
1345
|
testedChains.add(t.source);
|
|
1334
1346
|
}
|
|
1335
1347
|
}
|
|
1348
|
+
// CR-SM-376: der zweite Weg — die Kette ist ueber ihre Glieder gedeckt, wenn JEDE Glied-FUNC
|
|
1349
|
+
// (direkte FCHAIN -compose-> FUNC, dieselbe Definition wie `chainsByFunc`) eine REQ erfuellt.
|
|
1350
|
+
// `unmetMembers` liefert die Glieder ohne REQ; leer = gedeckt. Eine Kette ohne Glied kann hier
|
|
1351
|
+
// nicht auftauchen: ZWEIG 2 hat mindestens die beiden Enden der Uebergabe als Glieder.
|
|
1352
|
+
const funcsWithReq = new Set(idx.tracesOfType('satisfy').filter(t => isFunc(t.source) && typeOf.get(t.target) === 'REQ').map(t => t.source));
|
|
1353
|
+
const membersOf = new Map();
|
|
1354
|
+
for (const [fn, chains] of chainsOf) {
|
|
1355
|
+
for (const ch of chains) {
|
|
1356
|
+
const list = membersOf.get(ch);
|
|
1357
|
+
if (list)
|
|
1358
|
+
list.push(fn);
|
|
1359
|
+
else
|
|
1360
|
+
membersOf.set(ch, [fn]);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
const unmetMembers = (ch) => (membersOf.get(ch) ?? []).filter(fn => !funcsWithReq.has(fn)).sort();
|
|
1336
1364
|
// CR-SM-313: die Infrastruktur-Ausnahme. Die ZAHL kommt aus der Kennzahl (CR-SM-314), nicht
|
|
1337
1365
|
// aus einer zweiten Rechnung — eine Uebergabe an eine Funktion, die quer durch alle Ketten
|
|
1338
1366
|
// laeuft, ist kein fehlender Integrationstest, sondern ein Bus. `null` = keine Ausnahme.
|
|
@@ -1372,8 +1400,9 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1372
1400
|
});
|
|
1373
1401
|
continue;
|
|
1374
1402
|
}
|
|
1375
|
-
// ZWEIG 2 — gemeinsame Kette, aber keine davon
|
|
1376
|
-
|
|
1403
|
+
// ZWEIG 2 — gemeinsame Kette, aber keine davon gedeckt: Befund an der Kette. Gedeckt heisst
|
|
1404
|
+
// (a) verifizierte Ketten-REQ oder (b, CR-SM-376) jede Glied-FUNC erfuellt eine REQ.
|
|
1405
|
+
if (shared.some(ch => testedChains.has(ch) || unmetMembers(ch).length === 0))
|
|
1377
1406
|
continue;
|
|
1378
1407
|
const anchor = shared[0];
|
|
1379
1408
|
const key = `${anchor}|${p}>${c}`;
|
|
@@ -1385,8 +1414,8 @@ function fchainMustHaveIntegrationTest(graph, policy) {
|
|
|
1385
1414
|
rule_id: 'R-21',
|
|
1386
1415
|
severity: 'warning',
|
|
1387
1416
|
element_id: anchor,
|
|
1388
|
-
message: `${anchor} has no integration test covering connection ${p} → ${c}`,
|
|
1389
|
-
fix_hint: '
|
|
1417
|
+
message: `${anchor} has no integration test covering connection ${p} → ${c}; member FUNCs without a REQ: ${unmetMembers(anchor).join(', ')}`,
|
|
1418
|
+
fix_hint: 'Give each member FUNC of the chain a REQ it satisfies (FUNC -satisfy-> REQ), or attach an end-to-end non-functional REQ to the chain (FCHAIN -satisfy-> REQ) and verify it with an integration TEST',
|
|
1390
1419
|
context: { element_type: anchorEl?.type, element_name: anchorEl?.name },
|
|
1391
1420
|
});
|
|
1392
1421
|
}
|
|
@@ -1650,7 +1679,7 @@ export const V3_RULES = [
|
|
|
1650
1679
|
{ id: 'R-19', name: 'Runnable TEST binding', severity: 'warning', evaluate: testMustHaveRunnableBinding, domain: ['TEST'] },
|
|
1651
1680
|
{ id: 'R-29', name: 'Test file exclusivity', severity: 'error', evaluate: testFileExclusivity, domain: ['TEST'] },
|
|
1652
1681
|
{ id: 'R-20', name: 'FUNC realRef binding', severity: 'warning', evaluate: funcMustHaveCodeBinding, domain: ['FUNC'] },
|
|
1653
|
-
{ id: 'R-21', name: 'FUNC↔FUNC handover needs a shared chain
|
|
1682
|
+
{ id: 'R-21', name: 'FUNC↔FUNC handover needs a shared chain covered by member REQs or an integration test', severity: 'warning', evaluate: fchainMustHaveIntegrationTest, domain: ['FCHAIN', 'FUNC'] },
|
|
1654
1683
|
{ id: 'R-22', name: 'FUNC must be allocated to MOD', severity: 'warning', evaluate: funcMustBeAllocated, domain: ['FUNC'] },
|
|
1655
1684
|
{ id: 'R-23', name: 'MOD must have allocated FUNC', severity: 'warning', evaluate: modMustHaveAllocatedFunc, domain: ['MOD'] },
|
|
1656
1685
|
{ id: 'R-26', name: 'SCHEMA must have realRef', severity: 'warning', evaluate: schemaMustHaveSchemaRef, domain: ['SCHEMA'] },
|