@sigloch/contracts 6.1.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.
@@ -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>;
@@ -186,6 +179,18 @@ export interface RuleDefinition {
186
179
  */
187
180
  domain: readonly string[];
188
181
  }
182
+ /** Find the MOD a FUNC/SCHEMA is allocated to. */
183
+ /**
184
+ * CR-SM-256: die EINE Definition von "zerlegt". Ein FUNC mit FUNC-Kindern ist ein Rollup
185
+ * seiner Kinder — ein Blackbox-Block, durch den nichts fliesst und der keine Anforderung
186
+ * erfuellt, die nicht schon ein Blatt erfuellt. Die Pflichten liegen vollstaendig auf den
187
+ * Blaettern (so schon R-30 seit CR-SM-249, so R-20 seit CR-210).
188
+ *
189
+ * Bewusst EIN Helper statt drei Kopien: eine zweite Definition von "Blatt" waere ein
190
+ * zweiter Weg zu derselben Aussage, und der billigere gewinnt immer. CR-SM-263 haengt als
191
+ * vierter Nutzer daran (MT-02, `metric-rules.ts`) — deshalb exportiert.
192
+ */
193
+ export declare function decomposedFuncs(graph: OntologyGraph): Set<string>;
189
194
  export declare const V3_RULES: RuleDefinition[];
190
195
  /** Run all rules against a graph. CR-SM-236: `policy` ist Pflicht — wie bei `evaluateAllRules`. */
191
196
  export declare function evaluateRules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];