@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.
@@ -1,10 +1,13 @@
1
+ import { indexOf } from './graph-index.js';
1
2
  // ---------------------------------------------------------------------------
2
3
  // UC-01: UC must have compose→REQ trace (CR-165: satisfy UC→REQ is invalid per meta-model)
3
4
  // ---------------------------------------------------------------------------
4
5
  export function uc01HasRequirements(graph) {
5
- return graph.elements
6
- .filter(e => e.type === 'UC')
7
- .filter(uc => !graph.traces.some(t => t.source === uc.id && t.type === 'compose' && graph.elements.some(e => e.id === t.target && e.type === 'REQ')))
6
+ // CR-SM-264: compose-Kanten je UC aus dem Index. Vorher ein Vollscan ueber alle Traces je UC,
7
+ // darin ein Vollscan ueber alle Elemente je Treffer — `UC x T x E`.
8
+ const idx = indexOf(graph);
9
+ return idx.elementsOfType('UC')
10
+ .filter(uc => !idx.out(uc.id, 'compose').some(t => idx.typeOf(t.target) === 'REQ'))
8
11
  .map(uc => ({
9
12
  rule_id: 'UC-01',
10
13
  severity: 'error',
@@ -18,20 +21,56 @@ export function uc01HasRequirements(graph) {
18
21
  // UC-02: UC FCHAIN must have an ACTOR with io trace
19
22
  // ---------------------------------------------------------------------------
20
23
  export function uc02HasActor(graph) {
21
- return graph.elements
22
- .filter(e => e.type === 'UC')
23
- .filter(uc => {
24
- // Check if any ACTOR has io trace to this UC or to a FCHAIN/FLOW in this UC
25
- return !graph.traces.some(t => t.type === 'io' &&
26
- graph.elements.some(e => e.id === t.source && e.type === 'ACTOR') &&
27
- (t.target === uc.id || graph.traces.some(ct => ct.source === uc.id && ct.type === 'compose' && ct.target === t.target)));
28
- })
24
+ // CR-SM-261: dieselbe Frage, einmal statt je UC neu.
25
+ //
26
+ // Die alte Fassung scannte je UC alle Traces, darin je io-Trace alle ELEMENTE (die
27
+ // ACTOR-Suche) und noch einmal alle Traces (die compose-Suche) `UC × T × (E + T)`. Das
28
+ // Profil aus CR-SM-260 hat sie mit 50,8 % des gesamten Regellaufs und einem Wachstum von
29
+ // n^3,05 gemessen, kubisch statt quadratisch und damit die teuerste Regel des Katalogs.
30
+ //
31
+ // Die Umkehrung: erst die Menge der io-Ziele bilden, die von einem ACTOR ausgehen (einmal
32
+ // ueber Elemente und Traces), dann je UC nur noch Mitgliedschaft pruefen. Dieselbe Aussage,
33
+ // dieselben Befunde in derselben Reihenfolge — die Iteration ueber `graph.elements` bleibt
34
+ // die aeussere Schleife.
35
+ // CR-SM-266 D1/D4: die Regel prueft ERREICHBARKEIT statt Adjazenz.
36
+ //
37
+ // Bis hierher suchte sie einen io-Ziel-Treffer direkt am UC oder an dessen compose-Kind —
38
+ // also genau die beiden Kanten `ACTOR -io-> UC` und (ueber die FCHAIN) den Direktweg. Beide
39
+ // Patterns sind mit D1/D4 entfallen; die Regel waere damit UNERFUELLBAR geworden: sie haette
40
+ // einen Zustand verlangt, den R-18 im selben Lauf als ungueltige Kante ablehnt — der
41
+ // gegenlaeufige Fall aus Gate 4 des Grammatik-Reviews, und fuer den Nutzer eine Sackgasse
42
+ // statt eines Fehlers.
43
+ //
44
+ // Der tragende Pfad ist jetzt der einzige: `ACTOR -io-> FLOW -io-> FUNC`, FUNC in einer
45
+ // FCHAIN des UC. Zwei Sprungweiten (Actor→FLOW, FLOW→FUNC), danach Mengen-Mitgliedschaft —
46
+ // die CR-SM-261-Optimierung bleibt damit erhalten (kein Scan je UC, alles ueber den Index
47
+ // aus CR-SM-264), und die Iteration ueber `elementsOfType('UC')` bleibt die aeussere
48
+ // Schleife, also ist die Befundreihenfolge weiter die Graph-Reihenfolge (Gate 5).
49
+ const idx = indexOf(graph);
50
+ const actorIds = idx.idsOfType('ACTOR');
51
+ // Schritt 1: die FLOWs, die ein ACTOR speist.
52
+ const actorFlows = new Set(idx.tracesOfType('io').filter(t => actorIds.has(t.source)).map(t => t.target));
53
+ // Schritt 2: die FUNCs, die an einem solchen FLOW haengen — in BEIDE Richtungen. Ein UC ist
54
+ // auch dann an einen Actor angebunden, wenn die Kette IHM etwas liefert (FUNC -io-> FLOW
55
+ // -io-> ACTOR), nicht nur wenn sie von ihm getriggert wird. Die alte Fassung sah nur die
56
+ // Trigger-Richtung, weil `ACTOR -io-> UC` nur so herum existierte.
57
+ const actorFacingFuncs = new Set();
58
+ for (const t of idx.tracesOfType('io')) {
59
+ if (actorFlows.has(t.source))
60
+ actorFacingFuncs.add(t.target);
61
+ if (actorIds.has(t.target) && idx.typeOf(t.source) === 'FLOW') {
62
+ for (const producer of idx.in(t.source, 'io'))
63
+ actorFacingFuncs.add(producer.source);
64
+ }
65
+ }
66
+ return idx.elementsOfType('UC')
67
+ .filter(uc => !idx.out(uc.id, 'compose').some(chain => idx.out(chain.target, 'compose').some(member => actorFacingFuncs.has(member.target))))
29
68
  .map(uc => ({
30
69
  rule_id: 'UC-02',
31
70
  severity: 'error',
32
71
  element_id: uc.id,
33
- message: `${uc.id} has no ACTOR with io trace`,
34
- fix_hint: 'Link an ACTOR via io trace to this UC or its FCHAIN',
72
+ message: `${uc.id} is not reachable from any ACTOR`,
73
+ fix_hint: 'Wire an ACTOR to a FLOW that feeds (or is fed by) a FUNC in one of this UC\'s function chains',
35
74
  context: { element_type: uc.type, element_name: uc.name },
36
75
  }));
37
76
  }
@@ -39,10 +78,9 @@ export function uc02HasActor(graph) {
39
78
  // UC-03: UC must have compose→FCHAIN (scenario)
40
79
  // ---------------------------------------------------------------------------
41
80
  export function uc03HasScenario(graph) {
42
- return graph.elements
43
- .filter(e => e.type === 'UC')
44
- .filter(uc => !graph.traces.some(t => t.source === uc.id && t.type === 'compose' &&
45
- graph.elements.some(e => e.id === t.target && e.type === 'FCHAIN')))
81
+ const idx = indexOf(graph);
82
+ return idx.elementsOfType('UC')
83
+ .filter(uc => !idx.out(uc.id, 'compose').some(t => idx.typeOf(t.target) === 'FCHAIN'))
46
84
  .map(uc => ({
47
85
  rule_id: 'UC-03',
48
86
  severity: 'warning',
@@ -77,10 +115,12 @@ export function uc04GoalDefined(graph) {
77
115
  // UC-05: UC should have postcondition REQ (CR-165: compose only, satisfy UC→REQ invalid)
78
116
  // ---------------------------------------------------------------------------
79
117
  export function uc05HasPostcondition(graph) {
80
- return graph.elements
81
- .filter(e => e.type === 'UC')
82
- .filter(uc => !graph.traces.some(t => t.source === uc.id && t.type === 'compose' &&
83
- graph.elements.some(e => e.id === t.target && e.type === 'REQ' && e.kinds?.includes('postcondition'))))
118
+ const idx = indexOf(graph);
119
+ return idx.elementsOfType('UC')
120
+ .filter(uc => !idx.out(uc.id, 'compose').some(t => {
121
+ const target = idx.byId.get(t.target);
122
+ return target?.type === 'REQ' && target.kinds?.includes('postcondition');
123
+ }))
84
124
  .map(uc => ({
85
125
  rule_id: 'UC-05',
86
126
  severity: 'info',
@@ -94,10 +134,12 @@ export function uc05HasPostcondition(graph) {
94
134
  // UC-06: UC should have precondition REQ (CR-165: compose only, satisfy UC→REQ invalid)
95
135
  // ---------------------------------------------------------------------------
96
136
  export function uc06HasPrecondition(graph) {
97
- return graph.elements
98
- .filter(e => e.type === 'UC')
99
- .filter(uc => !graph.traces.some(t => t.source === uc.id && t.type === 'compose' &&
100
- graph.elements.some(e => e.id === t.target && e.type === 'REQ' && e.kinds?.includes('precondition'))))
137
+ const idx = indexOf(graph);
138
+ return idx.elementsOfType('UC')
139
+ .filter(uc => !idx.out(uc.id, 'compose').some(t => {
140
+ const target = idx.byId.get(t.target);
141
+ return target?.type === 'REQ' && target.kinds?.includes('precondition');
142
+ }))
101
143
  .map(uc => ({
102
144
  rule_id: 'UC-06',
103
145
  severity: 'info',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigloch/contracts",
3
- "version": "6.1.0",
3
+ "version": "9.1.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",