@sigloch/contracts 3.3.0 → 4.0.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.
Files changed (42) hide show
  1. package/dist/harness/index.d.ts +2 -2
  2. package/dist/se/action-priority.d.ts +101 -0
  3. package/dist/se/action-priority.js +124 -0
  4. package/dist/se/analysis-freshness-rules.d.ts +5 -0
  5. package/dist/se/analysis-freshness-rules.js +5 -5
  6. package/dist/se/ao-rules.d.ts +5 -39
  7. package/dist/se/ao-rules.js +20 -13
  8. package/dist/se/conformance-rules.d.ts +2 -2
  9. package/dist/se/conformance-rules.js +36 -30
  10. package/dist/se/cr-quality-rules.d.ts +2 -1
  11. package/dist/se/cr-quality-rules.js +9 -7
  12. package/dist/se/evaluate-all.d.ts +18 -3
  13. package/dist/se/evaluate-all.js +29 -26
  14. package/dist/se/fchain-quality-rules.d.ts +2 -1
  15. package/dist/se/fchain-quality-rules.js +8 -6
  16. package/dist/se/fmea-rules.d.ts +3 -11
  17. package/dist/se/fmea-rules.js +53 -16
  18. package/dist/se/format-e-parser.d.ts +14 -1
  19. package/dist/se/format-e-parser.js +8 -3
  20. package/dist/se/index.d.ts +4 -2
  21. package/dist/se/index.js +4 -2
  22. package/dist/se/metric-rules.d.ts +13 -5
  23. package/dist/se/metric-rules.js +23 -13
  24. package/dist/se/near-duplicate-rules.d.ts +2 -0
  25. package/dist/se/near-duplicate-rules.js +2 -2
  26. package/dist/se/ontology.d.ts +55 -4
  27. package/dist/se/ontology.js +46 -6
  28. package/dist/se/policy.d.ts +65 -0
  29. package/dist/se/policy.js +100 -0
  30. package/dist/se/quality-rules.d.ts +2 -1
  31. package/dist/se/quality-rules.js +9 -7
  32. package/dist/se/readiness.d.ts +9 -1
  33. package/dist/se/readiness.js +18 -2
  34. package/dist/se/rules.d.ts +25 -5
  35. package/dist/se/rules.js +112 -47
  36. package/dist/se/schema-quality-rules.d.ts +2 -1
  37. package/dist/se/schema-quality-rules.js +6 -4
  38. package/dist/se/uc-quality-rules.d.ts +2 -1
  39. package/dist/se/uc-quality-rules.js +10 -8
  40. package/dist/se/view-rules.d.ts +2 -6
  41. package/dist/se/view-rules.js +40 -13
  42. package/package.json +2 -1
@@ -118,8 +118,8 @@ export declare const RuleViolationSchema: z.ZodObject<{
118
118
  ruleId: z.ZodString;
119
119
  severity: z.ZodEnum<{
120
120
  error: "error";
121
- warning: "warning";
122
121
  info: "info";
122
+ warning: "warning";
123
123
  }>;
124
124
  message: z.ZodString;
125
125
  elementId: z.ZodOptional<z.ZodString>;
@@ -142,8 +142,8 @@ export declare const MutateResultSchema: z.ZodObject<{
142
142
  ruleId: z.ZodString;
143
143
  severity: z.ZodEnum<{
144
144
  error: "error";
145
- warning: "warning";
146
145
  info: "info";
146
+ warning: "warning";
147
147
  }>;
148
148
  message: z.ZodString;
149
149
  elementId: z.ZodOptional<z.ZodString>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * CR-SM-229 — Action Priority (AP) ersetzt RPN, ohne die geschützte Tabelle auszuliefern.
3
+ *
4
+ * Das AIAG-VDA FMEA Handbook (1st Edition, 2019) hat RPN **ersetzt**, nicht ergänzt. Der Skill
5
+ * `se-fmea` sagt das in Step 5 wörtlich („Risk Analysis → Action Priority (AP), not RPN"); der
6
+ * Regelcode folgte ihm bis hierher nicht.
7
+ *
8
+ * **RPN irrt gerichtet.** S 10 / O 2 / D 2 ergibt RPN 40 → „niedriges Risiko", während AP dort
9
+ * immer `High` sagt: ein gutes Detection-Rating rechnet eine sicherheitskritische Schwere weg.
10
+ * Genau dafür wurde AP eingeführt.
11
+ *
12
+ * ## Warum die Tabelle hier nicht steht
13
+ *
14
+ * Die normative AP-Tabelle (5 Severity- × 5 Occurrence- × 4 Detection-Bänder = 100 Zellen) steht
15
+ * ausschließlich im Handbook. Urheberrecht schützt keine Verfahren — **die Methode anzuwenden ist
16
+ * frei**. Die 100 Zellwerte sind aber normative Wertung (jemand hat entschieden, dass S 7 / O 4 /
17
+ * D 6 „Medium" ist), keine Fakten; sie in ein publiziertes npm-Paket zu legen wäre die verbotene
18
+ * Reproduktion.
19
+ *
20
+ * Deshalb: **die Logik wird ausgeliefert, die Zellwerte nie.** Eine Funktion, zwei Konsumenten
21
+ * (FM-03 und der graphcode-Exporter) — kein zweiter Klassifikationspfad. Die Tabelle wandert auch
22
+ * nicht in den Graphen (SYS-Attribute o. ä.): der Graph wird exportiert und verteilt, das wäre
23
+ * dieselbe Reproduktion durch die Hintertür.
24
+ *
25
+ * > Keine Rechtsberatung. Vor einem kommerziellen Release anwaltlich bestätigen lassen; in DE/EU
26
+ * > kommt zusätzlich das Datenbankherstellerrecht (§ 87a UrhG) in Betracht.
27
+ *
28
+ * ## Offene Fachfrage, hier nicht entschieden
29
+ *
30
+ * DFMEA und FMEA-MSR haben verschiedene AP-Tabellen. `ApTableSchema` muss die Variante
31
+ * mitführen, sobald MSR relevant wird.
32
+ */
33
+ import { z } from 'zod/v4';
34
+ /** Die drei Stufen des AIAG-VDA-Verfahrens. */
35
+ export declare const ActionPriority: z.ZodEnum<{
36
+ High: "High";
37
+ Medium: "Medium";
38
+ Low: "Low";
39
+ }>;
40
+ export type ActionPriority = z.infer<typeof ActionPriority>;
41
+ /**
42
+ * Form einer AP-Tabelle, **nicht ihr Inhalt**.
43
+ *
44
+ * Ein Konsument lädt sie zur Laufzeit aus einer lokalen, nie eingecheckten Datei (z. B.
45
+ * `.graphcode/ap-table.json`, dort mit `.gitignore`-Eintrag). Das Schema erzwingt, dass sie
46
+ * vollständig ist: 5 × 5 × 4 Bänder und für jede der 100 Kombinationen eine Stufe. Eine
47
+ * unvollständige Tabelle wird abgelehnt, statt still auf die Interimslogik zurückzufallen —
48
+ * ein halb geladenes Urteil ist schlimmer als ein bekanntermaßen ersatzweises.
49
+ */
50
+ export declare const ApTableSchema: z.ZodObject<{
51
+ severityBands: z.ZodArray<z.ZodObject<{
52
+ from: z.ZodNumber;
53
+ to: z.ZodNumber;
54
+ }, z.core.$strip>>;
55
+ occurrenceBands: z.ZodArray<z.ZodObject<{
56
+ from: z.ZodNumber;
57
+ to: z.ZodNumber;
58
+ }, z.core.$strip>>;
59
+ detectionBands: z.ZodArray<z.ZodObject<{
60
+ from: z.ZodNumber;
61
+ to: z.ZodNumber;
62
+ }, z.core.$strip>>;
63
+ cells: z.ZodArray<z.ZodArray<z.ZodArray<z.ZodEnum<{
64
+ High: "High";
65
+ Medium: "Medium";
66
+ Low: "Low";
67
+ }>>>>;
68
+ }, z.core.$strip>;
69
+ export type ApTable = z.infer<typeof ApTableSchema>;
70
+ /**
71
+ * Welches Verfahren die Einstufung gerade trägt.
72
+ *
73
+ * Treibt die Kennzeichnung beim Konsumenten (graphcode rendert die AP-Spalte als `AP*` mit
74
+ * generierter Fußnote, solange `'rpn-interim'` gilt). Hinweis und Berechnung können nicht
75
+ * auseinanderlaufen, weil beide aus derselben Quelle kommen.
76
+ */
77
+ export declare function apMethod(table?: ApTable): 'aiag-vda' | 'rpn-interim';
78
+ /**
79
+ * Action Priority für ein S/O/D-Tripel.
80
+ *
81
+ * **Mit `table`** gilt die echte Zuordnung des Handbooks.
82
+ *
83
+ * **Ohne `table`** gilt der *markierte Übergang* — nur die von allen geprüften Quellen
84
+ * bestätigten Invarianten, sonst RPN-Bänder:
85
+ *
86
+ * ```
87
+ * s >= 9 → 'High' // bestätigt
88
+ * s === 1 → 'Low' // bestätigt
89
+ * sonst rpn > interimRpn → 'High' // übernimmt die bisherige FM-03-Grenze
90
+ * rpn >= 40 → 'Medium'
91
+ * sonst → 'Low'
92
+ * ```
93
+ *
94
+ * Das ist „bestätigtes AP, wo bekannt; RPN, wo nicht" — **nicht geratenes AP**. Die
95
+ * S-9-10-Klausel entfernt die gefährliche Irrtumsrichtung (hohe Schwere, gutes Detection) und
96
+ * kostet nichts.
97
+ *
98
+ * `interimRpn` ist die Schwelle aus `MetricPolicy.riskRpn` (CR-SM-236) — dieselbe Zahl, die
99
+ * FM-03 vorher direkt benutzt hat. Sie steht damit weiter an genau einer Stelle.
100
+ */
101
+ export declare function actionPriority(s: number, o: number, d: number, interimRpn: number, table?: ApTable): ActionPriority;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * CR-SM-229 — Action Priority (AP) ersetzt RPN, ohne die geschützte Tabelle auszuliefern.
3
+ *
4
+ * Das AIAG-VDA FMEA Handbook (1st Edition, 2019) hat RPN **ersetzt**, nicht ergänzt. Der Skill
5
+ * `se-fmea` sagt das in Step 5 wörtlich („Risk Analysis → Action Priority (AP), not RPN"); der
6
+ * Regelcode folgte ihm bis hierher nicht.
7
+ *
8
+ * **RPN irrt gerichtet.** S 10 / O 2 / D 2 ergibt RPN 40 → „niedriges Risiko", während AP dort
9
+ * immer `High` sagt: ein gutes Detection-Rating rechnet eine sicherheitskritische Schwere weg.
10
+ * Genau dafür wurde AP eingeführt.
11
+ *
12
+ * ## Warum die Tabelle hier nicht steht
13
+ *
14
+ * Die normative AP-Tabelle (5 Severity- × 5 Occurrence- × 4 Detection-Bänder = 100 Zellen) steht
15
+ * ausschließlich im Handbook. Urheberrecht schützt keine Verfahren — **die Methode anzuwenden ist
16
+ * frei**. Die 100 Zellwerte sind aber normative Wertung (jemand hat entschieden, dass S 7 / O 4 /
17
+ * D 6 „Medium" ist), keine Fakten; sie in ein publiziertes npm-Paket zu legen wäre die verbotene
18
+ * Reproduktion.
19
+ *
20
+ * Deshalb: **die Logik wird ausgeliefert, die Zellwerte nie.** Eine Funktion, zwei Konsumenten
21
+ * (FM-03 und der graphcode-Exporter) — kein zweiter Klassifikationspfad. Die Tabelle wandert auch
22
+ * nicht in den Graphen (SYS-Attribute o. ä.): der Graph wird exportiert und verteilt, das wäre
23
+ * dieselbe Reproduktion durch die Hintertür.
24
+ *
25
+ * > Keine Rechtsberatung. Vor einem kommerziellen Release anwaltlich bestätigen lassen; in DE/EU
26
+ * > kommt zusätzlich das Datenbankherstellerrecht (§ 87a UrhG) in Betracht.
27
+ *
28
+ * ## Offene Fachfrage, hier nicht entschieden
29
+ *
30
+ * DFMEA und FMEA-MSR haben verschiedene AP-Tabellen. `ApTableSchema` muss die Variante
31
+ * mitführen, sobald MSR relevant wird.
32
+ */
33
+ import { z } from 'zod/v4';
34
+ /** Die drei Stufen des AIAG-VDA-Verfahrens. */
35
+ export const ActionPriority = z.enum(['High', 'Medium', 'Low']);
36
+ /** Ein Rating von 1 bis 10 — die Skala, auf der S, O und D erhoben werden. */
37
+ const RatingSchema = z.number().int().min(1).max(10);
38
+ /**
39
+ * Ein Band der Tabelle: ein Rating-Bereich und die Zuordnung darunter.
40
+ *
41
+ * Die Bandgrenzen selbst sind KEINE geschützte Wertung, sondern die Achsenbeschriftung des
42
+ * Verfahrens (S 9–10 / 7–8 / 4–6 / 2–3 / 1 usw.). Geschützt ist, welche Stufe in einer Zelle
43
+ * steht — und genau das steht hier nicht.
44
+ */
45
+ const BandSchema = z.object({
46
+ from: RatingSchema,
47
+ to: RatingSchema,
48
+ }).refine((b) => b.from <= b.to, { message: 'from must be <= to' });
49
+ /**
50
+ * Form einer AP-Tabelle, **nicht ihr Inhalt**.
51
+ *
52
+ * Ein Konsument lädt sie zur Laufzeit aus einer lokalen, nie eingecheckten Datei (z. B.
53
+ * `.graphcode/ap-table.json`, dort mit `.gitignore`-Eintrag). Das Schema erzwingt, dass sie
54
+ * vollständig ist: 5 × 5 × 4 Bänder und für jede der 100 Kombinationen eine Stufe. Eine
55
+ * unvollständige Tabelle wird abgelehnt, statt still auf die Interimslogik zurückzufallen —
56
+ * ein halb geladenes Urteil ist schlimmer als ein bekanntermaßen ersatzweises.
57
+ */
58
+ export const ApTableSchema = z.object({
59
+ severityBands: z.array(BandSchema).length(5),
60
+ occurrenceBands: z.array(BandSchema).length(5),
61
+ detectionBands: z.array(BandSchema).length(4),
62
+ /**
63
+ * `cells[si][oi][di]` — Index in die drei Bandlisten, Wert ist die Stufe.
64
+ * 5 × 5 × 4 = 100 Zellen, alle Pflicht.
65
+ */
66
+ cells: z.array(z.array(z.array(ActionPriority).length(4)).length(5)).length(5),
67
+ });
68
+ /**
69
+ * Welches Verfahren die Einstufung gerade trägt.
70
+ *
71
+ * Treibt die Kennzeichnung beim Konsumenten (graphcode rendert die AP-Spalte als `AP*` mit
72
+ * generierter Fußnote, solange `'rpn-interim'` gilt). Hinweis und Berechnung können nicht
73
+ * auseinanderlaufen, weil beide aus derselben Quelle kommen.
74
+ */
75
+ export function apMethod(table) {
76
+ return table ? 'aiag-vda' : 'rpn-interim';
77
+ }
78
+ function bandIndex(bands, rating) {
79
+ return bands.findIndex((b) => rating >= b.from && rating <= b.to);
80
+ }
81
+ /**
82
+ * Action Priority für ein S/O/D-Tripel.
83
+ *
84
+ * **Mit `table`** gilt die echte Zuordnung des Handbooks.
85
+ *
86
+ * **Ohne `table`** gilt der *markierte Übergang* — nur die von allen geprüften Quellen
87
+ * bestätigten Invarianten, sonst RPN-Bänder:
88
+ *
89
+ * ```
90
+ * s >= 9 → 'High' // bestätigt
91
+ * s === 1 → 'Low' // bestätigt
92
+ * sonst rpn > interimRpn → 'High' // übernimmt die bisherige FM-03-Grenze
93
+ * rpn >= 40 → 'Medium'
94
+ * sonst → 'Low'
95
+ * ```
96
+ *
97
+ * Das ist „bestätigtes AP, wo bekannt; RPN, wo nicht" — **nicht geratenes AP**. Die
98
+ * S-9-10-Klausel entfernt die gefährliche Irrtumsrichtung (hohe Schwere, gutes Detection) und
99
+ * kostet nichts.
100
+ *
101
+ * `interimRpn` ist die Schwelle aus `MetricPolicy.riskRpn` (CR-SM-236) — dieselbe Zahl, die
102
+ * FM-03 vorher direkt benutzt hat. Sie steht damit weiter an genau einer Stelle.
103
+ */
104
+ export function actionPriority(s, o, d, interimRpn, table) {
105
+ if (table) {
106
+ const si = bandIndex(table.severityBands, s);
107
+ const oi = bandIndex(table.occurrenceBands, o);
108
+ const di = bandIndex(table.detectionBands, d);
109
+ // Ein Rating ausserhalb aller Baender ist ein Datenfehler, kein 'Low'. Der markierte
110
+ // Uebergang ist die ehrlichere Antwort als eine erfundene Zelle.
111
+ if (si >= 0 && oi >= 0 && di >= 0)
112
+ return table.cells[si][oi][di];
113
+ }
114
+ if (s >= 9)
115
+ return 'High';
116
+ if (s === 1)
117
+ return 'Low';
118
+ const rpn = s * o * d;
119
+ if (rpn > interimRpn)
120
+ return 'High';
121
+ if (rpn >= 40)
122
+ return 'Medium';
123
+ return 'Low';
124
+ }
@@ -14,26 +14,31 @@ export declare const AF_RULES: readonly [{
14
14
  readonly name: "ConOps freshness stamp present";
15
15
  readonly severity: "warning";
16
16
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
17
+ readonly domain: readonly ["SYS"];
17
18
  }, {
18
19
  readonly id: "AF-02";
19
20
  readonly name: "Trade Study freshness stamp present";
20
21
  readonly severity: "warning";
21
22
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
23
+ readonly domain: readonly ["SYS"];
22
24
  }, {
23
25
  readonly id: "AF-03";
24
26
  readonly name: "Assumption Review freshness stamp present";
25
27
  readonly severity: "warning";
26
28
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
29
+ readonly domain: readonly ["SYS"];
27
30
  }, {
28
31
  readonly id: "AF-04";
29
32
  readonly name: "FMEA freshness stamp present";
30
33
  readonly severity: "warning";
31
34
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
35
+ readonly domain: readonly ["SYS"];
32
36
  }, {
33
37
  readonly id: "AF-05";
34
38
  readonly name: "Implementation Plan freshness stamp present";
35
39
  readonly severity: "warning";
36
40
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
41
+ readonly domain: readonly ["SYS"];
37
42
  }];
38
43
  /** Evaluate all analysis-freshness-presence rules. */
39
44
  export declare function evaluateAFRules(graph: OntologyGraph): RuleViolation[];
@@ -32,11 +32,11 @@ const assumptionReviewFreshnessPresent = analysisFreshnessPresence('AF-03', 'ass
32
32
  const fmeaFreshnessPresent = analysisFreshnessPresence('AF-04', 'fmea');
33
33
  const implplanFreshnessPresent = analysisFreshnessPresence('AF-05', 'implplan');
34
34
  export const AF_RULES = [
35
- { id: 'AF-01', name: 'ConOps freshness stamp present', severity: 'warning', evaluate: conopsFreshnessPresent },
36
- { id: 'AF-02', name: 'Trade Study freshness stamp present', severity: 'warning', evaluate: tradeFreshnessPresent },
37
- { id: 'AF-03', name: 'Assumption Review freshness stamp present', severity: 'warning', evaluate: assumptionReviewFreshnessPresent },
38
- { id: 'AF-04', name: 'FMEA freshness stamp present', severity: 'warning', evaluate: fmeaFreshnessPresent },
39
- { id: 'AF-05', name: 'Implementation Plan freshness stamp present', severity: 'warning', evaluate: implplanFreshnessPresent },
35
+ { id: 'AF-01', name: 'ConOps freshness stamp present', severity: 'warning', evaluate: conopsFreshnessPresent, domain: ['SYS'] },
36
+ { id: 'AF-02', name: 'Trade Study freshness stamp present', severity: 'warning', evaluate: tradeFreshnessPresent, domain: ['SYS'] },
37
+ { id: 'AF-03', name: 'Assumption Review freshness stamp present', severity: 'warning', evaluate: assumptionReviewFreshnessPresent, domain: ['SYS'] },
38
+ { id: 'AF-04', name: 'FMEA freshness stamp present', severity: 'warning', evaluate: fmeaFreshnessPresent, domain: ['SYS'] },
39
+ { id: 'AF-05', name: 'Implementation Plan freshness stamp present', severity: 'warning', evaluate: implplanFreshnessPresent, domain: ['SYS'] },
40
40
  ];
41
41
  /** Evaluate all analysis-freshness-presence rules. */
42
42
  export function evaluateAFRules(graph) {
@@ -3,7 +3,8 @@
3
3
  * Severity: info (optimization hints [OH], not blocking violations).
4
4
  */
5
5
  import type { OntologyGraph } from './ontology.js';
6
- import type { RuleViolation } from './rules.js';
6
+ import type { RuleDefinition, RuleViolation } from './rules.js';
7
+ import type { MetricPolicy } from './policy.js';
7
8
  /**
8
9
  * AO-D01: Relay Node Detection.
9
10
  * A FUNC is a relay if it (a) satisfies no REQ, (b) has >=2 outgoing io to FUNCs,
@@ -15,45 +16,10 @@ export declare function aoD01RelayNode(graph: OntologyGraph): RuleViolation[];
15
16
  * FUNC A sends io to both B and C, where B and C connect to the same SCHEMA elements.
16
17
  */
17
18
  export declare function aoD03DuplicatePath(graph: OntologyGraph): RuleViolation[];
18
- export declare function cr01CrossingFlowCount(graph: OntologyGraph): RuleViolation[];
19
+ export declare function cr01CrossingFlowCount(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
19
20
  export declare function rt01PhysicalBoundaryIntegrity(graph: OntologyGraph): RuleViolation[];
20
21
  export declare function ph01PhysicalModCompleteness(graph: OntologyGraph): RuleViolation[];
21
22
  export declare function ca01CapabilityAllocation(graph: OntologyGraph): RuleViolation[];
22
23
  export declare function io01CrossModuleCompleteness(graph: OntologyGraph): RuleViolation[];
23
- export declare const AO_RULES: readonly [{
24
- readonly id: "AO-D01";
25
- readonly name: "RelayNodeDetection";
26
- readonly severity: "info";
27
- readonly evaluate: typeof aoD01RelayNode;
28
- }, {
29
- readonly id: "AO-D03";
30
- readonly name: "DuplicatePathDetection";
31
- readonly severity: "info";
32
- readonly evaluate: typeof aoD03DuplicatePath;
33
- }, {
34
- readonly id: "CR-01";
35
- readonly name: "CrossingFlowCount";
36
- readonly severity: "warning";
37
- readonly evaluate: typeof cr01CrossingFlowCount;
38
- }, {
39
- readonly id: "RT-01";
40
- readonly name: "PhysicalBoundaryIntegrity";
41
- readonly severity: "error";
42
- readonly evaluate: typeof rt01PhysicalBoundaryIntegrity;
43
- }, {
44
- readonly id: "PH-01";
45
- readonly name: "PhysicalModCompleteness";
46
- readonly severity: "info";
47
- readonly evaluate: typeof ph01PhysicalModCompleteness;
48
- }, {
49
- readonly id: "CA-01";
50
- readonly name: "CapabilityAllocation";
51
- readonly severity: "error";
52
- readonly evaluate: typeof ca01CapabilityAllocation;
53
- }, {
54
- readonly id: "IO-01";
55
- readonly name: "FuncPairIOCompleteness";
56
- readonly severity: "warning";
57
- readonly evaluate: typeof io01CrossModuleCompleteness;
58
- }];
59
- export declare function evaluateAORules(graph: OntologyGraph): RuleViolation[];
24
+ export declare const AO_RULES: RuleDefinition[];
25
+ export declare function evaluateAORules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
@@ -135,9 +135,16 @@ export function aoD03DuplicatePath(graph) {
135
135
  // ---------------------------------------------------------------------------
136
136
  // CR-01: Crossing Flow Count (CR-191)
137
137
  // For each module pair (A, B): count io paths FUNC_A → FUNC_B
138
+ //
139
+ // CR-SM-236: die Schwelle ist Eingabe. `policy.crossingFlows === null` heisst messen statt
140
+ // urteilen — die Regel schweigt dann vollstaendig, auch unterhalb der Schwelle. Vorher gab es
141
+ // diesen Aus-Zustand nicht: jedes Modulpaar mit >= 1 Kreuzung erzeugte eine info-Meldung.
138
142
  // ---------------------------------------------------------------------------
139
- export function cr01CrossingFlowCount(graph) {
143
+ export function cr01CrossingFlowCount(graph, policy) {
140
144
  const violations = [];
145
+ const steps = policy.crossingFlows;
146
+ if (steps === null)
147
+ return violations;
141
148
  const mods = graph.elements.filter(e => e.type === 'MOD');
142
149
  // Build MOD → Set<FUNC-id> mapping
143
150
  const modFuncs = new Map();
@@ -168,13 +175,13 @@ export function cr01CrossingFlowCount(graph) {
168
175
  for (const { modA, modB, count } of pairCounts.values()) {
169
176
  if (count <= 0)
170
177
  continue;
171
- const severity = count > 2 ? 'warning' : 'info';
178
+ const over = count >= steps.warning;
172
179
  violations.push({
173
180
  rule_id: 'CR-01',
174
- severity,
181
+ severity: over ? 'warning' : 'info',
175
182
  element_id: modA,
176
183
  message: `${modA} ↔ ${modB}: ${count} crossing io flow(s)`,
177
- fix_hint: count > 2 ? 'Reduce coupling between modules or introduce a mediator' : 'Consider if coupling is intentional',
184
+ fix_hint: over ? 'Reduce coupling between modules or introduce a mediator' : 'Consider if coupling is intentional',
178
185
  });
179
186
  }
180
187
  return violations;
@@ -330,14 +337,14 @@ export function io01CrossModuleCompleteness(graph) {
330
337
  return violations;
331
338
  }
332
339
  export const AO_RULES = [
333
- { id: 'AO-D01', name: 'RelayNodeDetection', severity: 'info', evaluate: aoD01RelayNode },
334
- { id: 'AO-D03', name: 'DuplicatePathDetection', severity: 'info', evaluate: aoD03DuplicatePath },
335
- { id: 'CR-01', name: 'CrossingFlowCount', severity: 'warning', evaluate: cr01CrossingFlowCount },
336
- { id: 'RT-01', name: 'PhysicalBoundaryIntegrity', severity: 'error', evaluate: rt01PhysicalBoundaryIntegrity },
337
- { id: 'PH-01', name: 'PhysicalModCompleteness', severity: 'info', evaluate: ph01PhysicalModCompleteness },
338
- { id: 'CA-01', name: 'CapabilityAllocation', severity: 'error', evaluate: ca01CapabilityAllocation },
339
- { id: 'IO-01', name: 'FuncPairIOCompleteness', severity: 'warning', evaluate: io01CrossModuleCompleteness },
340
+ { id: 'AO-D01', name: 'RelayNodeDetection', severity: 'info', evaluate: aoD01RelayNode, domain: ['FUNC'] },
341
+ { id: 'AO-D03', name: 'DuplicatePathDetection', severity: 'info', evaluate: aoD03DuplicatePath, domain: ['FUNC'] },
342
+ { id: 'CR-01', name: 'CrossingFlowCount', severity: 'warning', evaluate: cr01CrossingFlowCount, domain: ['MOD'] },
343
+ { id: 'RT-01', name: 'PhysicalBoundaryIntegrity', severity: 'error', evaluate: rt01PhysicalBoundaryIntegrity, domain: ['FUNC'] },
344
+ { id: 'PH-01', name: 'PhysicalModCompleteness', severity: 'info', evaluate: ph01PhysicalModCompleteness, domain: ['MOD'] },
345
+ { id: 'CA-01', name: 'CapabilityAllocation', severity: 'error', evaluate: ca01CapabilityAllocation, domain: ['FUNC'] },
346
+ { id: 'IO-01', name: 'FuncPairIOCompleteness', severity: 'warning', evaluate: io01CrossModuleCompleteness, domain: ['MOD'] },
340
347
  ];
341
- export function evaluateAORules(graph) {
342
- return AO_RULES.flatMap(r => r.evaluate(graph));
348
+ export function evaluateAORules(graph, policy) {
349
+ return AO_RULES.flatMap(r => r.evaluate(graph, policy));
343
350
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * RC conformance rules (CR-GC-253) — graph↔code resolution, the RESOLUTION
3
- * twins of the presence rules R-20 (FUNC realRef) / R-19 (testRef).
3
+ * twins of the presence rules R-20 (FUNC realRef) / R-19 (testRefs).
4
4
  *
5
5
  * Rules stay in THIS library (one rule base per onto set — no rule definitions
6
6
  * in executor codebases). They are pure functions over (graph, facts): all
@@ -8,7 +8,7 @@
8
8
  * from the repo), so this module stays I/O-free and browser-bundlable.
9
9
  *
10
10
  * CodeFacts semantics: `files` is keyed by repo-relative path and MUST contain
11
- * an entry for every file referenced by a realRef/testRef the extractor saw.
11
+ * an entry for every file referenced by a realRef/testRefs entry the extractor saw.
12
12
  * A MISSING key is treated like `exists:false` — an extractor gap must surface
13
13
  * loudly as a violation, never as a silent pass.
14
14
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * RC conformance rules (CR-GC-253) — graph↔code resolution, the RESOLUTION
3
- * twins of the presence rules R-20 (FUNC realRef) / R-19 (testRef).
3
+ * twins of the presence rules R-20 (FUNC realRef) / R-19 (testRefs).
4
4
  *
5
5
  * Rules stay in THIS library (one rule base per onto set — no rule definitions
6
6
  * in executor codebases). They are pure functions over (graph, facts): all
@@ -8,14 +8,14 @@
8
8
  * from the repo), so this module stays I/O-free and browser-bundlable.
9
9
  *
10
10
  * CodeFacts semantics: `files` is keyed by repo-relative path and MUST contain
11
- * an entry for every file referenced by a realRef/testRef the extractor saw.
11
+ * an entry for every file referenced by a realRef/testRefs entry the extractor saw.
12
12
  * A MISSING key is treated like `exists:false` — an extractor gap must surface
13
13
  * loudly as a violation, never as a silent pass.
14
14
  *
15
15
  * @sigloch/contracts/se — single source of truth for SE validation rules.
16
16
  */
17
17
  import { z } from 'zod/v4';
18
- import { RealRefSchema, TestRefSchema } from './ontology.js';
18
+ import { RealRefSchema, TestRefsSchema } from './ontology.js';
19
19
  /** Parser facts about one source file (extracted by the executor). */
20
20
  export const FileFactsSchema = z.object({
21
21
  /** File exists on disk (repo-relative path). */
@@ -92,8 +92,13 @@ function codeRefMustResolve(graph, facts) {
92
92
  }
93
93
  return violations;
94
94
  }
95
- // RC-02: every valid testRef must resolve — file on disk and, when `case` is
95
+ // RC-02: every valid testRefs entry must resolve — file on disk and, when `case` is
96
96
  // set, the case name declared as an it/test/describe in that file.
97
+ //
98
+ // CR-SM-231: iteriert die Eintraege. Ein TEST mit zwei Dateien, von denen eine fehlt, ist
99
+ // **ein** Befund an **einer** Adresse — die Meldung nennt deshalb den konkreten Pfad und
100
+ // nicht nur die Knoten-ID. Ohne das waere „TEST-x hat eine kaputte Bindung" bei n Dateien
101
+ // nicht handhabbar.
97
102
  function testRefMustResolve(graph, facts) {
98
103
  const violations = [];
99
104
  for (const el of graph.elements) {
@@ -101,33 +106,34 @@ function testRefMustResolve(graph, facts) {
101
106
  continue;
102
107
  if (el.attributes?.concept === true)
103
108
  continue;
104
- const parsed = TestRefSchema.safeParse(el.attributes?.testRef);
109
+ const parsed = TestRefsSchema.safeParse(el.attributes?.testRefs);
105
110
  if (!parsed.success)
106
111
  continue; // no/invalid binding → R-19 territory
107
- const ref = parsed.data;
108
- if (missingFile(facts, ref.file)) {
109
- violations.push({
110
- rule_id: 'RC-02',
111
- severity: 'error',
112
- element_id: el.id,
113
- message: `${el.id} testRef.file '${ref.file}' does not exist on disk`,
114
- fix_hint: 'The test file was moved or deleted — rebind the TEST (graph_realize) to the current file',
115
- context: { element_type: el.type, element_name: el.name },
116
- });
117
- continue;
118
- }
119
- // `case` is a name FILTER (vitest -t semantics): it matches by substring,
120
- // so it must occur within some declared it/test/describe name — not equal it.
121
- const caseName = ref.case;
122
- if (caseName !== undefined && !facts.files[ref.file].testCases.some((name) => name.includes(caseName))) {
123
- violations.push({
124
- rule_id: 'RC-02',
125
- severity: 'error',
126
- element_id: el.id,
127
- message: `${el.id} testRef.case '${ref.case}' is not declared in '${ref.file}'`,
128
- fix_hint: 'The test case was renamed or removed — rebind the TEST case name',
129
- context: { element_type: el.type, element_name: el.name },
130
- });
112
+ for (const ref of parsed.data) {
113
+ if (missingFile(facts, ref.file)) {
114
+ violations.push({
115
+ rule_id: 'RC-02',
116
+ severity: 'error',
117
+ element_id: el.id,
118
+ message: `${el.id} testRefs entry file '${ref.file}' does not exist on disk`,
119
+ fix_hint: 'The test file was moved or deleted — rebind the TEST (graph_realize) to the current file, or drop the entry',
120
+ context: { element_type: el.type, element_name: el.name },
121
+ });
122
+ continue;
123
+ }
124
+ // `case` is a name FILTER (vitest -t semantics): it matches by substring,
125
+ // so it must occur within some declared it/test/describe name — not equal it.
126
+ const caseName = ref.case;
127
+ if (caseName !== undefined && !facts.files[ref.file].testCases.some((name) => name.includes(caseName))) {
128
+ violations.push({
129
+ rule_id: 'RC-02',
130
+ severity: 'error',
131
+ element_id: el.id,
132
+ message: `${el.id} testRefs entry case '${ref.case}' is not declared in '${ref.file}'`,
133
+ fix_hint: 'The test case was renamed or removed — rebind the TEST case name',
134
+ context: { element_type: el.type, element_name: el.name },
135
+ });
136
+ }
131
137
  }
132
138
  }
133
139
  return violations;
@@ -356,7 +362,7 @@ function importDriftConformance(graph, facts) {
356
362
  /** All RC conformance rules — evaluated by executors that can supply CodeFacts. */
357
363
  export const CODE_CONFORMANCE_RULES = [
358
364
  { id: 'RC-01', name: 'FUNC realRef resolves to a declared symbol', severity: 'error', evaluate: codeRefMustResolve },
359
- { id: 'RC-02', name: 'testRef resolves to a runnable test', severity: 'error', evaluate: testRefMustResolve },
365
+ { id: 'RC-02', name: 'testRefs entries resolve to runnable tests', severity: 'error', evaluate: testRefMustResolve },
360
366
  { id: 'RC-03', name: 'SCHEMA realRef resolves to a declared export', severity: 'error', evaluate: schemaRefMustResolve },
361
367
  { id: 'RC-04', name: 'SCHEMA realRef is parsed at its interface', severity: 'warning', evaluate: schemaRefMustBeUsed },
362
368
  { id: 'RC-05', name: 'cross-module import drift', severity: 'warning', evaluate: importDriftConformance },
@@ -4,5 +4,6 @@
4
4
  */
5
5
  import type { OntologyGraph } from './ontology.js';
6
6
  import type { RuleDefinition, RuleViolation } from './rules.js';
7
+ import type { MetricPolicy } from './policy.js';
7
8
  export declare const CR_RULES: RuleDefinition[];
8
- export declare function evaluateCRRules(graph: OntologyGraph): RuleViolation[];
9
+ export declare function evaluateCRRules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
@@ -130,12 +130,14 @@ function crShouldHaveMilestone(graph) {
130
130
  // Exports
131
131
  // ---------------------------------------------------------------------------
132
132
  export const CR_RULES = [
133
- { id: 'CR-R01', name: 'CR must track', severity: 'error', evaluate: crMustTrack },
134
- { id: 'CR-R02', name: 'Done requires commit', severity: 'error', evaluate: crDoneRequiresCommit },
135
- { id: 'CR-R03', name: 'No concurrent mutation', severity: 'warning', evaluate: noConcurrentMutation },
136
- { id: 'CR-R04', name: 'CR must have FUNC', severity: 'warning', evaluate: crMustHaveFunc },
137
- { id: 'MS-03', name: 'CR without milestone', severity: 'info', evaluate: crShouldHaveMilestone },
133
+ { id: 'CR-R01', name: 'CR must track', severity: 'error', evaluate: crMustTrack, domain: ['CR'] },
134
+ { id: 'CR-R02', name: 'Done requires commit', severity: 'error', evaluate: crDoneRequiresCommit, domain: ['CR'] },
135
+ { id: 'CR-R03', name: 'No concurrent mutation', severity: 'warning', evaluate: noConcurrentMutation, domain: ['CR'] },
136
+ { id: 'CR-R04', name: 'CR must have FUNC', severity: 'warning', evaluate: crMustHaveFunc, domain: ['CR'] },
137
+ { id: 'MS-03', name: 'CR without milestone', severity: 'info', evaluate: crShouldHaveMilestone, domain: ['CR'] },
138
138
  ];
139
- export function evaluateCRRules(graph) {
140
- return CR_RULES.flatMap(rule => rule.evaluate(graph));
139
+ // CR-SM-236: `policy` wird durchgereicht, auch wo diese Familie heute keine Schwelle hat —
140
+ // ein Sonderweg je Familie waere genau der zweite Pfad, den der Regelsatz verbietet.
141
+ export function evaluateCRRules(graph, policy) {
142
+ return CR_RULES.flatMap(rule => rule.evaluate(graph, policy));
141
143
  }
@@ -4,14 +4,29 @@
4
4
  */
5
5
  import type { OntologyGraph } from './ontology.js';
6
6
  import type { RuleViolation } from './rules.js';
7
- /** All prescribed rule definitions (single source of truth for the catalog). */
7
+ import type { MetricPolicy } from './policy.js';
8
+ /**
9
+ * All prescribed rule definitions (single source of truth for the catalog).
10
+ *
11
+ * CR-SM-235: `domain` reicht mit durch — die Grundgesamtheit, ueber die eine Regel feuert.
12
+ * Konsumenten, die einen Anteil bilden (`se-steering`s `computeApplicable`), lesen den Nenner
13
+ * hier statt eine eigene Tabelle zu fuehren. Eine zweite Tabelle kann nicht hinterherhinken,
14
+ * wenn es keine zweite gibt.
15
+ */
8
16
  export declare const ALL_RULE_DEFS: ReadonlyArray<{
9
17
  id: string;
10
18
  name: string;
11
19
  severity: 'error' | 'warning' | 'info';
20
+ domain: readonly string[];
12
21
  }>;
13
22
  /** Profile groupings derived from rule prefixes. */
14
23
  export type ProfileId = 'default' | 'se' | 'coding';
15
24
  export declare function getRuleDefsForProfile(profile: ProfileId): typeof ALL_RULE_DEFS;
16
- /** Evaluate all rules against a graph. Single call replaces the individual evaluator calls. */
17
- export declare function evaluateAllRules(graph: OntologyGraph): RuleViolation[];
25
+ /**
26
+ * Evaluate all rules against a graph. Single call replaces the individual evaluator calls.
27
+ *
28
+ * CR-SM-233: `policy` ist **Pflicht und ohne Fallback** — ein Aufruf ohne Policy ist ein
29
+ * Typfehler, keine stille 0.7. Wer keine eigene Quelle hat (Konfiguration, Host), nimmt
30
+ * `DEFAULT_METRIC_POLICY` sichtbar an der Aufrufstelle.
31
+ */
32
+ export declare function evaluateAllRules(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];