@sigloch/contracts 6.3.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,8 @@
1
+ import { normalizeReqKinds } from './ontology.js';
2
+ /** Die Kinds, die eine Wirkkette bzw. ein Verhalten traegt. */
3
+ const BEHAVIOURAL_KINDS = ['functional', 'precondition', 'postcondition'];
4
+ /** Die Kinds, die eine Struktur (Modul/System) traegt. */
5
+ const STRUCTURAL_KINDS = ['non-functional', 'risk', 'mitigation'];
1
6
  export const TRACE_PATTERNS = [
2
7
  // ── compose (parent → child) ──
3
8
  { source: 'SYS', target: 'SYS', type: 'compose', cardinality: '0..*', description: 'System decomposes into subsystems' },
@@ -15,11 +20,25 @@ export const TRACE_PATTERNS = [
15
20
  { source: 'FLOW', target: 'ACTOR', type: 'io', description: 'Flow delivers to actor' },
16
21
  { source: 'FUNC', target: 'FLOW', type: 'io', description: 'Function outputs to flow' },
17
22
  { source: 'FLOW', target: 'FUNC', type: 'io', description: 'Flow feeds into function' },
18
- { source: 'ACTOR', target: 'UC', type: 'io', description: 'Actor triggers use case' },
19
- { source: 'FLOW', target: 'UC', type: 'io', description: 'Flow feeds use case' },
20
- { source: 'MOD', target: 'MOD', type: 'io', description: 'Module communicates with module (ACL)' },
23
+ // CR-SM-266 D1: `ACTOR -io-> UC` ENTFAELLT (BREAKING). Der tragende Pfad existiert und ist
24
+ // typisiert: `ACTOR -io-> FLOW -io-> FUNC`, FUNC in der FCHAIN des UC. Die Direktkante war
25
+ // die TYPLOSE Abkuerzung daneben ein Actor-Beitrag ohne FLOW traegt kein SCHEMA und
26
+ // entzieht sich damit derselben Typgarantie, die fuer jeden anderen Fluss gilt. Migration
27
+ // je Bestandskante: FLOW einfuehren oder Kante loeschen (70 Kanten ueber 6 Graphen).
28
+ // CR-SM-266 D2: `MOD -io-> MOD` ENTFAELLT (BREAKING). Modul-Kopplung ist vollstaendig
29
+ // ABLEITBAR: `FUNC -allocate-> MOD` x `FUNC -io-> FLOW -io-> FUNC` ergibt die
30
+ // Modul-Ebenen-Projektion. Eine BEHAUPTETE Kopplungskante kann von der tatsaechlichen
31
+ // driften, eine berechnete nicht — und die behauptete gewann, weil sie billiger war.
32
+ // Soll-Architektur wird als REQ (non-functional) modelliert, nicht als Kante.
33
+ // CR-SM-266 D4: `FLOW -io-> UC` ENTFAELLT (BREAKING). Zweitweg neben `FLOW -io-> FUNC` mit
34
+ // FUNC in der FCHAIN des UC. Die UC-Grenze ERGIBT SICH aus den FLOWs der Kettenglieder; eine
35
+ // direkte FLOW-UC-Kante umgeht die Kettenzuordnung und damit IO-01 und R-21.
21
36
  // ── satisfy (implementation) ──
22
- { source: 'FUNC', target: 'REQ', type: 'satisfy', description: 'Function implements requirement' },
37
+ {
38
+ source: 'FUNC', target: 'REQ', type: 'satisfy',
39
+ where: { on: 'target', field: 'kinds', allowed: BEHAVIOURAL_KINDS },
40
+ description: 'Function implements requirement — behavioural kinds only',
41
+ },
23
42
  // CR-GC-366: `FUNC -satisfy-> UC` ENTFAELLT (BREAKING). Es war ein zweiter, redundanter Weg
24
43
  // vom Verhalten zum Use Case neben dem einzig tragenden `UC -compose-> FCHAIN -compose-> FUNC`.
25
44
  // Zwei Wege = zwei Wahrheiten: eine FUNC konnte einen UC bedienen, ohne in dessen Wirkkette zu
@@ -29,14 +48,38 @@ export const TRACE_PATTERNS = [
29
48
  // stand ohnehin in einer FCHAIN); die restlichen 13 sind der Migrationsanlass, nicht ein
30
49
  // Argument fuer das Pattern. Ersatz: die FUNC in die FCHAIN des UC aufnehmen (R-30).
31
50
  // CR-154: NFR satisfy — non-functional REQs can be satisfied by chains, modules, or the system
51
+ //
52
+ // CR-SM-266 B: die drei strukturellen satisfy-Patterns tragen jetzt ein `where`. Ohne es
53
+ // konnte ein MOD oder SYS ein FUNKTIONALES REQ erfuellen, ohne dass eine Wirkkette es traegt
54
+ // — der Zweitweg aus CR-GC-366, eine Ebene hoeher. Die FCHAIN behaelt bewusst ALLE Kinds:
55
+ // sie IST die Wirkkette, an ihr haengen IO-01 und R-21, sie ist also nie die Abkuerzung.
32
56
  { source: 'FCHAIN', target: 'REQ', type: 'satisfy', description: 'Function chain satisfies end-to-end NFR (e.g. latency)' },
33
- { source: 'MOD', target: 'REQ', type: 'satisfy', description: 'Module satisfies budget NFR (e.g. uptime, memory)' },
34
- { source: 'SYS', target: 'REQ', type: 'satisfy', description: 'System satisfies system-level NFR (e.g. availability)' },
57
+ {
58
+ source: 'MOD', target: 'REQ', type: 'satisfy',
59
+ where: { on: 'target', field: 'kinds', allowed: STRUCTURAL_KINDS },
60
+ description: 'Module satisfies budget NFR (e.g. uptime, memory) — structural kinds only',
61
+ },
62
+ {
63
+ source: 'SYS', target: 'REQ', type: 'satisfy',
64
+ where: { on: 'target', field: 'kinds', allowed: STRUCTURAL_KINDS },
65
+ description: 'System satisfies system-level NFR (e.g. availability) — structural kinds only',
66
+ },
35
67
  // ── verify (INCOSE: test verifies requirement) ──
36
68
  { source: 'TEST', target: 'REQ', type: 'verify', description: 'Test verifies requirement' },
37
69
  // ── allocate (deployment/assignment) ──
38
- { source: 'FUNC', target: 'MOD', type: 'allocate', description: 'Function deployed in module' },
39
- { source: 'FLOW', target: 'SCHEMA', type: 'relation', description: 'Flow data format defined by schema' },
70
+ // CR-SM-266b A/C: OBERGRENZEN als Grammatik. Beide Kanten sind Besitz-Aussagen, und Besitz
71
+ // ist nicht mehrfach vergebbar eine FUNC wohnt in EINEM Modul, ein FLOW traegt EINEN
72
+ // Datenvertrag. Zwei Allokationen sind kein "staerkeres" Modell, sondern eine Frage ohne
73
+ // Antwort ("in welchem Modul liegt der Code?"), und zwei SCHEMA an einem FLOW heben genau
74
+ // die Typgarantie auf, fuer die der geteilte SCHEMA-Knoten existiert (CR-SM-266 C): zwei
75
+ // Vertraege koennen divergieren, einer kann es nicht.
76
+ //
77
+ // Nur die OBERE Grenze steht hier, bewusst als `0..1` und nicht `1..1`. Die untere haben
78
+ // R-22 (FUNC muss alloziert sein) und SC-04 (FLOW hat SCHEMA) laengst — sie hier zu
79
+ // wiederholen waere die Doppelzaehlung aus Gate 4: eine Ursache, zwei Befunde, ein Nenner
80
+ // doppelt belastet. Die beiden Haelften ergeben zusammen das `[1..1]` des CR.
81
+ { source: 'FUNC', target: 'MOD', type: 'allocate', cardinality: '0..1', description: 'Function deployed in exactly one module' },
82
+ { source: 'FLOW', target: 'SCHEMA', type: 'relation', cardinality: '0..1', description: 'Flow data format defined by exactly one schema' },
40
83
  // CR-228: REQ→MOD allocate removed — a MOD does not own a REQ. Physical NFRs use
41
84
  // MOD→satisfy→REQ, behavioral NFRs FCHAIN→satisfy→REQ; structural constraints are
42
85
  // rules, not REQs. R-18 now flags any REQ→MOD allocate edge as invalid.
@@ -44,24 +87,75 @@ export const TRACE_PATTERNS = [
44
87
  { source: 'MS', target: 'FUNC', type: 'compose', description: 'Milestone includes function' },
45
88
  { source: 'MS', target: 'REQ', type: 'compose', description: 'Milestone includes requirement' },
46
89
  { source: 'MS', target: 'UC', type: 'compose', description: 'Milestone includes use case' },
47
- { source: 'MS', target: 'MS', type: 'compose', description: 'Sub-milestone' },
48
- { source: 'MS', target: 'MS', type: 'relation', label: 'depends-on', description: 'Milestone dependency' },
90
+ // CR-SM-266 D3: `MS -compose-> MS` ENTFAELLT (BREAKING). Zwei Kantenarten zwischen denselben
91
+ // zwei Meilensteinen sagten dasselbe auf zwei Arten Schachtelung und Abfolge sind an einem
92
+ // Meilenstein nicht unterscheidbar, MS-01 zaehlte den Scope einmal ueber `CR -relation-> MS`
93
+ // (seit CR-SM-245 der einzige Weg) und die compose-Kante trug nichts mehr bei. `relation`
94
+ // bleibt als EINZIGE MS-MS-Kante, Semantik: Ordnung/Abfolge (Vorgaenger → Nachfolger).
95
+ // Migration: keine — 0 solche Kanten in allen 9 aktiven Familie-Graphen (gemessen 2026-08-25).
96
+ { source: 'MS', target: 'MS', type: 'relation', label: 'depends-on', description: 'Milestone order: predecessor → successor' },
49
97
  { source: 'CR', target: 'MS', type: 'relation', description: 'CR assigned to milestone' },
50
98
  // ── CR traceability (CR-155: CR tracks mutated elements) ──
51
99
  { source: 'CR', target: 'UC', type: 'relation', description: 'CR affects use case' },
52
100
  { source: 'CR', target: 'REQ', type: 'relation', description: 'CR affects requirement' },
53
101
  { source: 'CR', target: 'FUNC', type: 'relation', description: 'CR affects function' },
54
102
  { source: 'CR', target: 'MOD', type: 'relation', description: 'CR affects module' },
55
- // ── produces (audit lineage) ──
56
- { source: 'SESSION', target: '*', type: 'produces', category: 'audit', description: 'Session produced/modified element' },
103
+ // CR-SM-266 D5: `SESSION -produces-> *` ENTFAELLT ersatzlos, mit ihm der TraceType `produces`
104
+ // und der ElementType `SESSION`. Der urspruengliche Entwurf hielt das Pattern fuer die
105
+ // Provenance-Kante und wollte es behalten; die MESSUNG hat das widerlegt: 0 produces-Kanten
106
+ // und 0 SESSION-Knoten in ALLEN 9 aktiven Familie-Graphen, auch im graphcode-Selbstmodell
107
+ // nach 195 gegateten Versionen. Die reale Provenance lebt seit CR-GC-347 in
108
+ // `.graphcode/audit.jsonl` — audit_trail/audit_stats lesen die DATEI, nie den Graphen.
109
+ // Einziger Schreiber war ein toter Pfad (CR-195d in graph-api-core), Leser mit Wirkung: keiner.
110
+ // Nicht auf `relation` umgebogen: eine Kante ohne Schreiber UND ohne Leser wird gestrichen,
111
+ // nicht umbenannt.
57
112
  ];
113
+ /**
114
+ * Erfuellt das betroffene Kantenende das Praedikat?
115
+ *
116
+ * TEILMENGEN-Semantik, nicht Schnittmenge: JEDER deklarierte Kind muss in der erlaubten Liste
117
+ * liegen. Die schwaechere Lesart ("mindestens einer passt") waere das Loch, das der ganze
118
+ * Abschnitt schliessen will — ein funktionales REQ zusaetzlich als `non-functional` zu
119
+ * deklarieren haette gereicht, damit ein MOD es wieder erfuellen darf, und der billige
120
+ * Zweitweg waere ueber ein Attribut zurueck.
121
+ *
122
+ * Der leere Fall LEHNT AB (`kinds.length === 0`): ohne Deklaration ist nicht entscheidbar, ob
123
+ * die Kante zulaessig ist, und "unentscheidbar" darf nicht "erlaubt" heissen — sonst wird das
124
+ * WEGLASSEN des Attributs zum Umgehungsweg. Das ist die durchsetzende Haelfte von REQ-X06
125
+ * (`kinds` ist Pflicht am REQ); die meldende Haelfte haengt an BQ-07.
126
+ */
127
+ function satisfiesPredicate(where, trace) {
128
+ // `normalizeReqKinds` statt direktem Zugriff: drei Familie-Graphen tragen `kinds` als String
129
+ // statt als Liste, und ein `.every()` darauf WIRFT — mitten im Regellauf, statt einen Befund
130
+ // zu melden. Die Normalisierung heilt die Drift nicht, sie macht sie nur urteilsfaehig.
131
+ const kinds = normalizeReqKinds(where.on === 'source' ? trace.sourceKinds : trace.targetKinds);
132
+ if (kinds.length === 0)
133
+ return false;
134
+ return kinds.every(k => where.allowed.includes(k));
135
+ }
136
+ /**
137
+ * Die Obergrenze eines Kardinalitaets-Ausdrucks — `Infinity`, wo keine gilt.
138
+ *
139
+ * Getrennt von `isValidTrace`, weil es eine andere ART von Bedingung ist: `isValidTrace`
140
+ * urteilt ueber EINE Kante und braucht nur deren Enden, eine Kardinalitaet urteilt ueber die
141
+ * MENGE der Kanten eines Knotens. Der urspruengliche CR-Entwurf wollte beides in
142
+ * `isValidTrace` erledigen; das geht nicht, und die Korrektur steht im Impact-Audit.
143
+ */
144
+ export function maxOccurs(cardinality) {
145
+ if (cardinality === undefined)
146
+ return Infinity;
147
+ return cardinality === '1' || cardinality === '0..1' ? 1 : Infinity;
148
+ }
149
+ /** Die Patterns mit einer echten Obergrenze — die Arbeitsliste des Kardinalitaets-Beins. */
150
+ export const BOUNDED_PATTERNS = TRACE_PATTERNS.filter(p => maxOccurs(p.cardinality) < Infinity);
58
151
  /**
59
152
  * Check if a trace matches a valid pattern in the meta-model.
60
- * Uses element types (not IDs) for validation.
153
+ * Uses element types (not IDs) plus — where a pattern declares one — an attribute predicate.
61
154
  */
62
155
  export function isValidTrace(trace, patterns = TRACE_PATTERNS) {
63
156
  return patterns.some(p => (p.source === '*' || p.source === trace.source) &&
64
157
  (p.target === '*' || p.target === trace.target) &&
65
158
  p.type === trace.type &&
66
- (!p.label || p.label === trace.label));
159
+ (!p.label || p.label === trace.label) &&
160
+ (!p.where || satisfiesPredicate(p.where, trace)));
67
161
  }
@@ -21,7 +21,6 @@ export declare const ElementType: z.ZodEnum<{
21
21
  TEST: "TEST";
22
22
  MOD: "MOD";
23
23
  SCHEMA: "SCHEMA";
24
- SESSION: "SESSION";
25
24
  CR: "CR";
26
25
  MS: "MS";
27
26
  }>;
@@ -38,20 +37,48 @@ export declare const TraceType: z.ZodEnum<{
38
37
  verify: "verify";
39
38
  allocate: "allocate";
40
39
  relation: "relation";
41
- produces: "produces";
42
40
  }>;
43
41
  export type TraceType = z.infer<typeof TraceType>;
44
- /** REQ kind — 7 values aligned with SysML 2.0 + FMEA (CR-180). */
42
+ /**
43
+ * REQ kind — 6 values aligned with SysML 2.0 + FMEA (CR-180).
44
+ *
45
+ * CR-SM-266 B: `negative` ENTFAELLT. Ein Verbots-REQ ("das System tut X nie") ist auch eine
46
+ * Anforderung — die Implementierung muss Regeln abfragen oder Sicherungen einbauen, und das
47
+ * ist `functional`. Der Wert hatte NULL Leser (kein Regel-, Gate- oder View-Konsument; anders
48
+ * als risk/mitigation → FM-01..03, pre/postcondition → UC-05/06, non-functional → NFR-01) und
49
+ * haette durch die where-Praedikate am satisfy-Pattern per AUSLASSUNG erstmals Wirkung
50
+ * bekommen: er stand in keiner der vier Listen und waere damit nur noch per FCHAIN erfuellbar
51
+ * gewesen. Die sechs verbleibenden Werte partitionieren die where-Listen VOLLSTAENDIG — kein
52
+ * Wert ohne Zuordnung, keine Kante, die durch ein Loch in der Aufzaehlung faellt.
53
+ */
45
54
  export declare const ReqKind: z.ZodEnum<{
46
55
  functional: "functional";
47
56
  "non-functional": "non-functional";
48
57
  risk: "risk";
49
- negative: "negative";
50
58
  mitigation: "mitigation";
51
59
  precondition: "precondition";
52
60
  postcondition: "postcondition";
53
61
  }>;
54
62
  export type ReqKind = z.infer<typeof ReqKind>;
63
+ /**
64
+ * `kinds` in der Form, die WIRKLICH auf Platte liegt — als Liste.
65
+ *
66
+ * Gefunden beim Migrations-Audit zu CR-SM-266 (2026-08-25), nicht von Hand: drei Graphen der
67
+ * Familie tragen `kinds` als blossen STRING statt als Liste (`graph-view-edit`
68
+ * `REQ-edit-genesis: 'functional'`, dazu gc_test-graphview und graphify), einer davon mit dem
69
+ * Wert `'security'`, den das Enum gar nicht kennt. Ursache ist der Format-E-Weg: dort reist
70
+ * das Feld als `@kinds a,b` und wird erst vom Konsumenten gehoben (graph-api-core, CR-195d).
71
+ *
72
+ * Seit die where-Praedikate `kinds` LESEN (CR-SM-266 B), ist diese Drift tragend: ein
73
+ * `kinds.every(...)` auf einem String wirft, und eine geworfene Exception in `isValidTrace`
74
+ * reisst den ganzen Regellauf statt einen Befund zu melden. Die SSOT muss auch bei kaputten
75
+ * Daten ein URTEIL liefern, keinen Absturz.
76
+ *
77
+ * Normalisieren heisst hier ausdruecklich NICHT raten: `'a,b'` wird zu `['a','b']`, weil genau
78
+ * das die Schreibform ist — ein unbekannter Wert wie `'security'` ueberlebt die Normalisierung
79
+ * und faellt danach am Enum-Vergleich durch. Die Drift wird sichtbar, nicht geheilt.
80
+ */
81
+ export declare function normalizeReqKinds(raw: unknown): readonly string[];
55
82
  export declare const AsilLevel: z.ZodEnum<{
56
83
  QM: "QM";
57
84
  A: "A";
@@ -222,7 +249,6 @@ export declare const OntologyElement: z.ZodObject<{
222
249
  TEST: "TEST";
223
250
  MOD: "MOD";
224
251
  SCHEMA: "SCHEMA";
225
- SESSION: "SESSION";
226
252
  CR: "CR";
227
253
  MS: "MS";
228
254
  }>;
@@ -245,7 +271,6 @@ export declare const OntologyElement: z.ZodObject<{
245
271
  functional: "functional";
246
272
  "non-functional": "non-functional";
247
273
  risk: "risk";
248
- negative: "negative";
249
274
  mitigation: "mitigation";
250
275
  precondition: "precondition";
251
276
  postcondition: "postcondition";
@@ -265,12 +290,6 @@ export type OntologyElement = z.infer<typeof OntologyElement>;
265
290
  * A trace (edge) in the SE ontology graph.
266
291
  * `label` provides semantic context for 'relation' edges (e.g. 'derives', 'depends-on').
267
292
  */
268
- /** Trace category: modeling traces are user-visible, audit traces are internal. */
269
- export declare const TraceCategory: z.ZodEnum<{
270
- modeling: "modeling";
271
- audit: "audit";
272
- }>;
273
- export type TraceCategory = z.infer<typeof TraceCategory>;
274
293
  export declare const Trace: z.ZodObject<{
275
294
  source: z.ZodString;
276
295
  target: z.ZodString;
@@ -281,12 +300,7 @@ export declare const Trace: z.ZodObject<{
281
300
  verify: "verify";
282
301
  allocate: "allocate";
283
302
  relation: "relation";
284
- produces: "produces";
285
303
  }>;
286
- category: z.ZodOptional<z.ZodEnum<{
287
- modeling: "modeling";
288
- audit: "audit";
289
- }>>;
290
304
  label: z.ZodOptional<z.ZodString>;
291
305
  weight: z.ZodDefault<z.ZodNumber>;
292
306
  attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -308,7 +322,6 @@ export declare const OntologyGraph: z.ZodObject<{
308
322
  TEST: "TEST";
309
323
  MOD: "MOD";
310
324
  SCHEMA: "SCHEMA";
311
- SESSION: "SESSION";
312
325
  CR: "CR";
313
326
  MS: "MS";
314
327
  }>;
@@ -331,7 +344,6 @@ export declare const OntologyGraph: z.ZodObject<{
331
344
  functional: "functional";
332
345
  "non-functional": "non-functional";
333
346
  risk: "risk";
334
- negative: "negative";
335
347
  mitigation: "mitigation";
336
348
  precondition: "precondition";
337
349
  postcondition: "postcondition";
@@ -356,12 +368,7 @@ export declare const OntologyGraph: z.ZodObject<{
356
368
  verify: "verify";
357
369
  allocate: "allocate";
358
370
  relation: "relation";
359
- produces: "produces";
360
371
  }>;
361
- category: z.ZodOptional<z.ZodEnum<{
362
- modeling: "modeling";
363
- audit: "audit";
364
- }>>;
365
372
  label: z.ZodOptional<z.ZodString>;
366
373
  weight: z.ZodDefault<z.ZodNumber>;
367
374
  attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -372,7 +379,7 @@ export declare const OntologyGraph: z.ZodObject<{
372
379
  export type OntologyGraph = z.infer<typeof OntologyGraph>;
373
380
  /** Human-readable descriptions for each ElementType. */
374
381
  export declare const ELEMENT_DESCRIPTIONS: Record<ElementType, string>;
375
- /** Element types used in modeling (user-visible). Excludes audit types (SESSION, CR). */
382
+ /** Element types used in modeling (user-visible). Excludes the planning type CR. */
376
383
  export declare const MODELING_ELEMENT_TYPES: ElementType[];
377
384
  export interface AttributeSpec {
378
385
  key: string;
@@ -13,7 +13,7 @@ import { z } from 'zod/v4';
13
13
  export const ElementType = z.enum([
14
14
  'SYS', 'UC', 'ACTOR', 'FCHAIN',
15
15
  'FUNC', 'FLOW', 'REQ', 'TEST',
16
- 'MOD', 'SCHEMA', 'SESSION', 'CR',
16
+ 'MOD', 'SCHEMA', 'CR',
17
17
  'MS',
18
18
  ]);
19
19
  /**
@@ -22,13 +22,49 @@ export const ElementType = z.enum([
22
22
  * verify=test coverage, allocate=function-to-module, relation=generic link.
23
23
  */
24
24
  export const TraceType = z.enum([
25
- 'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation', 'produces',
25
+ 'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation',
26
26
  ]);
27
- /** REQ kind — 7 values aligned with SysML 2.0 + FMEA (CR-180). */
27
+ /**
28
+ * REQ kind — 6 values aligned with SysML 2.0 + FMEA (CR-180).
29
+ *
30
+ * CR-SM-266 B: `negative` ENTFAELLT. Ein Verbots-REQ ("das System tut X nie") ist auch eine
31
+ * Anforderung — die Implementierung muss Regeln abfragen oder Sicherungen einbauen, und das
32
+ * ist `functional`. Der Wert hatte NULL Leser (kein Regel-, Gate- oder View-Konsument; anders
33
+ * als risk/mitigation → FM-01..03, pre/postcondition → UC-05/06, non-functional → NFR-01) und
34
+ * haette durch die where-Praedikate am satisfy-Pattern per AUSLASSUNG erstmals Wirkung
35
+ * bekommen: er stand in keiner der vier Listen und waere damit nur noch per FCHAIN erfuellbar
36
+ * gewesen. Die sechs verbleibenden Werte partitionieren die where-Listen VOLLSTAENDIG — kein
37
+ * Wert ohne Zuordnung, keine Kante, die durch ein Loch in der Aufzaehlung faellt.
38
+ */
28
39
  export const ReqKind = z.enum([
29
- 'functional', 'non-functional', 'risk', 'negative',
40
+ 'functional', 'non-functional', 'risk',
30
41
  'mitigation', 'precondition', 'postcondition',
31
42
  ]);
43
+ /**
44
+ * `kinds` in der Form, die WIRKLICH auf Platte liegt — als Liste.
45
+ *
46
+ * Gefunden beim Migrations-Audit zu CR-SM-266 (2026-08-25), nicht von Hand: drei Graphen der
47
+ * Familie tragen `kinds` als blossen STRING statt als Liste (`graph-view-edit`
48
+ * `REQ-edit-genesis: 'functional'`, dazu gc_test-graphview und graphify), einer davon mit dem
49
+ * Wert `'security'`, den das Enum gar nicht kennt. Ursache ist der Format-E-Weg: dort reist
50
+ * das Feld als `@kinds a,b` und wird erst vom Konsumenten gehoben (graph-api-core, CR-195d).
51
+ *
52
+ * Seit die where-Praedikate `kinds` LESEN (CR-SM-266 B), ist diese Drift tragend: ein
53
+ * `kinds.every(...)` auf einem String wirft, und eine geworfene Exception in `isValidTrace`
54
+ * reisst den ganzen Regellauf statt einen Befund zu melden. Die SSOT muss auch bei kaputten
55
+ * Daten ein URTEIL liefern, keinen Absturz.
56
+ *
57
+ * Normalisieren heisst hier ausdruecklich NICHT raten: `'a,b'` wird zu `['a','b']`, weil genau
58
+ * das die Schreibform ist — ein unbekannter Wert wie `'security'` ueberlebt die Normalisierung
59
+ * und faellt danach am Enum-Vergleich durch. Die Drift wird sichtbar, nicht geheilt.
60
+ */
61
+ export function normalizeReqKinds(raw) {
62
+ if (raw == null)
63
+ return [];
64
+ if (Array.isArray(raw))
65
+ return raw.map(k => String(k).trim()).filter(Boolean);
66
+ return String(raw).split(',').map(k => k.trim()).filter(Boolean);
67
+ }
32
68
  export const AsilLevel = z.enum(['QM', 'A', 'B', 'C', 'D']);
33
69
  /** INCOSE TIAD verification method (only relevant for type=TEST). */
34
70
  export const VerificationMethod = z.enum(['test', 'inspection', 'analysis', 'demonstration']);
@@ -175,14 +211,24 @@ export const OntologyElement = z.object({
175
211
  * A trace (edge) in the SE ontology graph.
176
212
  * `label` provides semantic context for 'relation' edges (e.g. 'derives', 'depends-on').
177
213
  */
178
- /** Trace category: modeling traces are user-visible, audit traces are internal. */
179
- export const TraceCategory = z.enum(['modeling', 'audit']);
214
+ /*
215
+ * CR-SM-266 D5: `TraceCategory` und `Trace.category` ENTFALLEN ersatzlos.
216
+ *
217
+ * Die Unterscheidung modeling/audit trug genau EIN Pattern (`SESSION -produces-> *`), und das
218
+ * ist mit D5 weg. Was blieb, waere ein SCHLUPFLOCH gewesen, kein Vertrag: R-08 und R-18 haben
219
+ * `category === 'audit'` uebersprungen (`filter(t => t.category !== 'audit')`), also haette
220
+ * JEDE Kante mit diesem Attribut die komplette Pattern-Matrix umgangen — die
221
+ * Referenzintegritaet gleich mit. Ein selbstgesetztes Attribut, das eine Strukturpruefung
222
+ * abschaltet, ist genau die Klasse, die CR-SM-263 und CR-SM-257 abgelehnt haben, und hier
223
+ * zusaetzlich der billige Zweitweg aus CR-GC-366: die Kante gaebe es weiterhin, nur ungeprueft.
224
+ * Gemessen: 0 Kanten in allen 9 aktiven Familie-Graphen tragen `category` ueberhaupt — die
225
+ * Entfernung nimmt niemandem etwas weg. Provenance lebt seit CR-GC-347 in
226
+ * `.graphcode/audit.jsonl`, ausserhalb des Graphen.
227
+ */
180
228
  export const Trace = z.object({
181
229
  source: z.string(),
182
230
  target: z.string(),
183
231
  type: TraceType,
184
- /** 'modeling' (default, user-visible) or 'audit' (internal, e.g. produces traces). Absent = modeling. */
185
- category: TraceCategory.optional(),
186
232
  label: z.string().optional(),
187
233
  weight: z.number().default(1),
188
234
  attributes: z.record(z.string(), z.unknown()).optional(),
@@ -208,12 +254,11 @@ export const ELEMENT_DESCRIPTIONS = {
208
254
  TEST: 'Testfall',
209
255
  MOD: 'Modul (SW-Paket oder HW-Baugruppe)',
210
256
  SCHEMA: 'Datenschema (Zod)',
211
- SESSION: 'Audit-Session',
212
257
  CR: 'Change Request',
213
258
  MS: 'Meilenstein',
214
259
  };
215
- /** Element types used in modeling (user-visible). Excludes audit types (SESSION, CR). */
216
- export const MODELING_ELEMENT_TYPES = ElementType.options.filter(t => t !== 'SESSION' && t !== 'CR');
260
+ /** Element types used in modeling (user-visible). Excludes the planning type CR. */
261
+ export const MODELING_ELEMENT_TYPES = ElementType.options.filter(t => t !== 'CR');
217
262
  /**
218
263
  * Documented attributes per ElementType.
219
264
  * `attributes` is Record<string,unknown> on the schema level, but these are
@@ -1,3 +1,4 @@
1
+ import { normalizeReqKinds } from './ontology.js';
1
2
  // ---------------------------------------------------------------------------
2
3
  // Weasel words list (BQ-01)
3
4
  // ---------------------------------------------------------------------------
@@ -162,12 +163,22 @@ export function bq06Conforming(graph) {
162
163
  const placeholderPattern = /\b(TBD|TBR|TODO|placeholder|to be determined)\b|needs\s.*review/i;
163
164
  const BQ07_MIN_DESC_LENGTH = 20;
164
165
  export function bq07Complete(graph) {
166
+ // CR-SM-266 REQ-X06: `kinds` ist Pflicht am REQ — hier als DRITTER Grund derselben Regel,
167
+ // bewusst ohne neue Regel-ID (Gate 6 des Grammatik-Reviews: eine ID kostet dauerhaft einen
168
+ // Nenner-Anteil, eine Katalogzeile, eine readiness-Zuordnung und einen Golden-File-Eintrag).
169
+ // BQ-07 heisst "Complete" und sammelt bereits mehrere Unvollstaendigkeits-Gruende in EINEN
170
+ // Befund; ein REQ ohne Klassifikation ist genau das — unvollstaendig, nicht falsch.
171
+ //
172
+ // Die DURCHSETZENDE Haelfte liegt woanders: `satisfiesPredicate` (meta-model.ts) lehnt jede
173
+ // satisfy-Kante auf ein REQ ohne `kinds` ab, weil dort nicht entscheidbar ist, ob sie
174
+ // zulaessig ist. Das ist die Fehlmessung und deshalb R-18/error; das blosse Fehlen der
175
+ // Angabe ist ein Vollstaendigkeitssignal und deshalb hier/warning — dieselbe Trennung, die
176
+ // CR-SM-262 fuer RC-06 begruendet hat.
177
+ const isIncomplete = (req) => req.description.trim().length < BQ07_MIN_DESC_LENGTH ||
178
+ placeholderPattern.test(req.description) ||
179
+ normalizeReqKinds(req.kinds).length === 0;
165
180
  return reqElements(graph)
166
- .filter(req => {
167
- const tooShort = req.description.trim().length < BQ07_MIN_DESC_LENGTH;
168
- const hasPlaceholder = placeholderPattern.test(req.description);
169
- return tooShort || hasPlaceholder;
170
- })
181
+ .filter(isIncomplete)
171
182
  .map(req => {
172
183
  const reasons = [];
173
184
  if (req.description.trim().length < BQ07_MIN_DESC_LENGTH) {
@@ -176,6 +187,9 @@ export function bq07Complete(graph) {
176
187
  if (placeholderPattern.test(req.description)) {
177
188
  reasons.push('contains placeholder');
178
189
  }
190
+ if (normalizeReqKinds(req.kinds).length === 0) {
191
+ reasons.push('no kinds declared');
192
+ }
179
193
  return {
180
194
  rule_id: 'BQ-07',
181
195
  severity: 'warning',
@@ -30,9 +30,10 @@ export declare const ReadinessScore: z.ZodObject<{
30
30
  cr: "cr";
31
31
  ms: "ms";
32
32
  }>;
33
- score: z.ZodNumber;
33
+ score: z.ZodNullable<z.ZodNumber>;
34
34
  violations: z.ZodNumber;
35
35
  applicable: z.ZodNumber;
36
+ coreApplicable: z.ZodNumber;
36
37
  ready: z.ZodBoolean;
37
38
  }, z.core.$strip>;
38
39
  export type ReadinessScoreType = z.infer<typeof ReadinessScore>;
@@ -57,9 +58,10 @@ export declare const ReadinessReport: z.ZodObject<{
57
58
  cr: "cr";
58
59
  ms: "ms";
59
60
  }>;
60
- score: z.ZodNumber;
61
+ score: z.ZodNullable<z.ZodNumber>;
61
62
  violations: z.ZodNumber;
62
63
  applicable: z.ZodNumber;
64
+ coreApplicable: z.ZodNumber;
63
65
  ready: z.ZodBoolean;
64
66
  }, z.core.$strip>>;
65
67
  timestamp: z.ZodISODateTime;
@@ -20,11 +20,33 @@ export const ReadinessDimension = z.enum([
20
20
  ]);
21
21
  export const ReadinessScore = z.object({
22
22
  dimension: ReadinessDimension,
23
- score: z.number().min(0).max(1), // 1 - (violations / applicable)
23
+ /**
24
+ * `1 - (violations / applicable)` — oder **`null`**, wenn die Dimension nicht messbar ist
25
+ * (CR-SM-270).
26
+ *
27
+ * `null` heisst nie 0 %, so wie schon bei `graph_metrics` (CR-GC-326): "a value that is not
28
+ * measurable is not zero percent". Der Anlass war die Gegenrichtung — ein Modell ohne einen
29
+ * einzigen Use Case meldete `uc: 0,997, ready: true`, weil 306 FUNC ueber eine einzige
30
+ * Fremdtyp-Regel (FC-03) den Nenner fuellten, waehrend die dreizehn Regeln, die wirklich Use
31
+ * Cases pruefen, nichts zu pruefen hatten und ihr SCHWEIGEN sich als Erfolg las. Der Score
32
+ * zeigte damit in die falsche Richtung: je mehr importierte FUNCs ein Repo ohne Wozu-Ebene
33
+ * hat, desto reifer sah seine uc-Readiness aus.
34
+ */
35
+ score: z.number().min(0).max(1).nullable(),
24
36
  violations: z.number().int(),
25
37
  applicable: z.number().int(),
38
+ /**
39
+ * CR-SM-270: die Kernmenge der Dimension — die Elemente der Typen, die die MEHRHEIT ihrer
40
+ * Regeln prueft (uc → UC/FCHAIN/ACTOR, arch → FUNC/MOD). Ist sie 0, ist `score` `null`.
41
+ *
42
+ * Sie steht im Bericht, weil sonst niemand das `null` erklaeren kann: `applicable` allein
43
+ * unterscheidet "nichts zu pruefen" (0) nicht von "nur Fremdtypen" (307). Genau die
44
+ * Unterscheidung, an der der dokumentierte Ausweg aus CR-SM-237 gescheitert ist.
45
+ */
46
+ coreApplicable: z.number().int(),
26
47
  // CR-SM-235: `score >= readyThreshold`. Die Schwelle steht bewusst NICHT hier — sie ist
27
48
  // Eingabe von `computeReadiness`, und eine Zahl im Kommentar wäre die dritte Meinung dazu.
49
+ // CR-SM-270: bei `score === null` ist `ready` immer `false` — nicht messbar ist nicht bereit.
28
50
  ready: z.boolean(),
29
51
  });
30
52
  /**
@@ -26,7 +26,6 @@ export declare const ViolationCandidate: z.ZodObject<{
26
26
  TEST: "TEST";
27
27
  MOD: "MOD";
28
28
  SCHEMA: "SCHEMA";
29
- SESSION: "SESSION";
30
29
  CR: "CR";
31
30
  MS: "MS";
32
31
  }>;
@@ -46,7 +45,6 @@ export declare const ViolationContext: z.ZodObject<{
46
45
  TEST: "TEST";
47
46
  MOD: "MOD";
48
47
  SCHEMA: "SCHEMA";
49
- SESSION: "SESSION";
50
48
  CR: "CR";
51
49
  MS: "MS";
52
50
  }>>;
@@ -64,7 +62,6 @@ export declare const ViolationContext: z.ZodObject<{
64
62
  TEST: "TEST";
65
63
  MOD: "MOD";
66
64
  SCHEMA: "SCHEMA";
67
- SESSION: "SESSION";
68
65
  CR: "CR";
69
66
  MS: "MS";
70
67
  }>;
@@ -79,7 +76,6 @@ export declare const ViolationContext: z.ZodObject<{
79
76
  verify: "verify";
80
77
  allocate: "allocate";
81
78
  relation: "relation";
82
- produces: "produces";
83
79
  }>;
84
80
  }, z.core.$strip>>>;
85
81
  parent_module: z.ZodOptional<z.ZodString>;
@@ -108,7 +104,6 @@ export declare const RuleViolation: z.ZodObject<{
108
104
  TEST: "TEST";
109
105
  MOD: "MOD";
110
106
  SCHEMA: "SCHEMA";
111
- SESSION: "SESSION";
112
107
  CR: "CR";
113
108
  MS: "MS";
114
109
  }>>;
@@ -126,7 +121,6 @@ export declare const RuleViolation: z.ZodObject<{
126
121
  TEST: "TEST";
127
122
  MOD: "MOD";
128
123
  SCHEMA: "SCHEMA";
129
- SESSION: "SESSION";
130
124
  CR: "CR";
131
125
  MS: "MS";
132
126
  }>;
@@ -141,7 +135,6 @@ export declare const RuleViolation: z.ZodObject<{
141
135
  verify: "verify";
142
136
  allocate: "allocate";
143
137
  relation: "relation";
144
- produces: "produces";
145
138
  }>;
146
139
  }, z.core.$strip>>>;
147
140
  parent_module: z.ZodOptional<z.ZodString>;