@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.
- package/dist/se/ao-rules.js +44 -29
- package/dist/se/conformance-rules.d.ts +29 -0
- package/dist/se/conformance-rules.js +107 -17
- package/dist/se/fchain-quality-rules.js +57 -53
- package/dist/se/flat-graph.d.ts +3 -0
- package/dist/se/flat-graph.js +52 -0
- package/dist/se/format-e-parser.d.ts +14 -2
- package/dist/se/format-e-parser.js +33 -9
- package/dist/se/grammar-snapshot.d.ts +7 -6
- package/dist/se/grammar-snapshot.js +17 -16
- package/dist/se/graph-index.d.ts +56 -0
- package/dist/se/graph-index.js +60 -0
- package/dist/se/index.d.ts +5 -3
- package/dist/se/index.js +5 -3
- package/dist/se/meta-model.d.ts +52 -7
- package/dist/se/meta-model.js +108 -14
- package/dist/se/metric-rules.d.ts +8 -0
- package/dist/se/metric-rules.js +95 -23
- package/dist/se/ontology.d.ts +32 -25
- package/dist/se/ontology.js +56 -11
- package/dist/se/quality-rules.js +19 -5
- package/dist/se/readiness.d.ts +4 -2
- package/dist/se/readiness.js +23 -1
- package/dist/se/rules.d.ts +12 -7
- package/dist/se/rules.js +215 -103
- package/dist/se/uc-quality-rules.js +67 -25
- package/package.json +1 -1
package/dist/se/rules.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod/v4';
|
|
7
7
|
import { ElementType, TraceType, TestRefsSchema, RealRefSchema } from './ontology.js';
|
|
8
|
-
import { isValidTrace } from './meta-model.js';
|
|
8
|
+
import { isValidTrace, BOUNDED_PATTERNS, maxOccurs } from './meta-model.js';
|
|
9
|
+
import { indexOf } from './graph-index.js';
|
|
9
10
|
export const RuleSeverity = z.enum(['error', 'warning', 'info']);
|
|
10
11
|
/** Candidate target for resolving a violation (e.g. a REQ to satisfy, a TEST to link). */
|
|
11
12
|
export const ViolationCandidate = z.object({
|
|
@@ -44,21 +45,25 @@ export const RuleViolation = z.object({
|
|
|
44
45
|
* Blaettern (so schon R-30 seit CR-SM-249, so R-20 seit CR-210).
|
|
45
46
|
*
|
|
46
47
|
* Bewusst EIN Helper statt drei Kopien: eine zweite Definition von "Blatt" waere ein
|
|
47
|
-
* zweiter Weg zu derselben Aussage, und der billigere gewinnt immer.
|
|
48
|
+
* zweiter Weg zu derselben Aussage, und der billigere gewinnt immer. CR-SM-263 haengt als
|
|
49
|
+
* vierter Nutzer daran (MT-02, `metric-rules.ts`) — deshalb exportiert.
|
|
48
50
|
*/
|
|
49
|
-
function decomposedFuncs(graph) {
|
|
50
|
-
const
|
|
51
|
+
export function decomposedFuncs(graph) {
|
|
52
|
+
const idx = indexOf(graph);
|
|
53
|
+
const funcIds = new Set(idx.elementsOfType('FUNC').map(f => f.id));
|
|
51
54
|
return new Set(graph.traces
|
|
52
55
|
.filter(t => t.type === 'compose' && funcIds.has(t.source) && funcIds.has(t.target))
|
|
53
56
|
.map(t => t.source));
|
|
54
57
|
}
|
|
55
58
|
function findParentModule(graph, elementId) {
|
|
56
|
-
const
|
|
59
|
+
const idx = indexOf(graph);
|
|
60
|
+
const trace = idx.out(elementId, 'allocate')[0];
|
|
57
61
|
return trace?.target;
|
|
58
62
|
}
|
|
59
63
|
/** Get all outgoing traces of a given type from an element. */
|
|
60
64
|
function outgoingTraces(graph, elementId, traceType) {
|
|
61
|
-
|
|
65
|
+
const idx = indexOf(graph);
|
|
66
|
+
return traceType === undefined ? idx.outAll(elementId) : idx.out(elementId, traceType);
|
|
62
67
|
}
|
|
63
68
|
/** Tokenize id/name/description into a lowercase token set (drops tokens < 3 chars). */
|
|
64
69
|
function overlapTokens(...parts) {
|
|
@@ -69,35 +74,69 @@ function overlapTokens(...parts) {
|
|
|
69
74
|
.split(/[^a-z0-9]+/)
|
|
70
75
|
.filter(t => t.length >= 3));
|
|
71
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* CR-SM-261: Tokenmenge je Element, einmal je Prozess statt einmal je Vergleich.
|
|
79
|
+
*
|
|
80
|
+
* `toCandidates` rief `overlapTokens` frueher INNERHALB des Sort-Komparators auf. Ein Sort
|
|
81
|
+
* ueber k Kandidaten macht rund k·log k Vergleiche, jeder Vergleich hat beide Seiten neu
|
|
82
|
+
* tokenisiert (join + toLowerCase + Regex-Split) — und das je meldendem Element. Bei R-31 mit
|
|
83
|
+
* 33 Befunden gegen 46 FLOWs sind das ueber 11 000 Tokenisierungen fuer 46 verschiedene
|
|
84
|
+
* Elemente. Das Profil aus CR-SM-260 hat R-31 deshalb mit 25,9 % und n^2,10 gemessen, obwohl
|
|
85
|
+
* die Regel selbst nur Mengen aufbaut: die Kosten lagen vollstaendig in der Kandidatenliste.
|
|
86
|
+
*
|
|
87
|
+
* Der Schluessel ist das Element-OBJEKT, nicht die id — zwei Graphen im selben Prozess (der
|
|
88
|
+
* Probegraph aus `applyRule` neben dem Original) teilen sich ids, aber nicht die Objekte.
|
|
89
|
+
* `WeakMap` gibt die Eintraege frei, sobald der Graph es tut; es entsteht kein Cache, den
|
|
90
|
+
* jemand invalidieren muesste, und damit auch keine zweite Wahrheit ueber ein Element.
|
|
91
|
+
*/
|
|
92
|
+
const TOKEN_CACHE = new WeakMap();
|
|
93
|
+
function tokensOf(e) {
|
|
94
|
+
let t = TOKEN_CACHE.get(e);
|
|
95
|
+
if (t === undefined) {
|
|
96
|
+
t = overlapTokens(e.id, e.name, e.description);
|
|
97
|
+
TOKEN_CACHE.set(e, t);
|
|
98
|
+
}
|
|
99
|
+
return t;
|
|
100
|
+
}
|
|
72
101
|
/**
|
|
73
102
|
* Map elements to ViolationCandidate format. When `ref` is given, rank candidates
|
|
74
103
|
* by id/name/description token overlap with `ref` (descending) so the most relevant
|
|
75
104
|
* target is first — e.g. REQ-bootstrap → TEST-bootstrap (CR-GC-203 item 3). Ranking
|
|
76
105
|
* is a hint, not an auto-link; semantic confirmation stays with the caller.
|
|
77
106
|
*/
|
|
78
|
-
function toCandidates(
|
|
107
|
+
function toCandidates(
|
|
108
|
+
// CR-SM-264: `readonly`, weil die Kandidaten jetzt aus `idx.elementsOfType()` kommen. Seit
|
|
109
|
+
// CR-SM-261 sortiert diese Funktion ohnehin eine eigene Liste (decorate-sort-undecorate) und
|
|
110
|
+
// fasst die Eingabe nicht mehr an — der mutable Typ war schon vorher zu weit.
|
|
111
|
+
elements, ref) {
|
|
79
112
|
if (!ref)
|
|
80
113
|
return elements.map(e => ({ id: e.id, type: e.type, name: e.name }));
|
|
81
|
-
const refTokens =
|
|
114
|
+
const refTokens = tokensOf(ref);
|
|
82
115
|
const score = (e) => {
|
|
83
116
|
let n = 0;
|
|
84
|
-
for (const tok of
|
|
117
|
+
for (const tok of tokensOf(e))
|
|
85
118
|
if (refTokens.has(tok))
|
|
86
119
|
n++;
|
|
87
120
|
return n;
|
|
88
121
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
122
|
+
// CR-SM-261: EINMAL bewerten, dann sortieren. Vorher stand `score` im Komparator und lief
|
|
123
|
+
// damit rund 2·k·log k mal statt k mal. Der Index ist der Gleichstands-Tiebreak und haelt die
|
|
124
|
+
// Ausgabe byte-identisch: `Array.prototype.sort` ist seit ES2019 stabil, die alte Fassung hat
|
|
125
|
+
// bei gleichem Score also die Eingangsreihenfolge behalten — diese tut es explizit.
|
|
126
|
+
return elements
|
|
127
|
+
.map((e, i) => ({ e, i, s: score(e) }))
|
|
128
|
+
.sort((a, b) => b.s - a.s || a.i - b.i)
|
|
129
|
+
.map(({ e }) => ({ id: e.id, type: e.type, name: e.name }));
|
|
92
130
|
}
|
|
93
131
|
// ---------------------------------------------------------------------------
|
|
94
132
|
// R-01: Every REQ must have at least one verify trace
|
|
95
133
|
// ---------------------------------------------------------------------------
|
|
96
134
|
function reqMustHaveVerification(graph) {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
135
|
+
const idx = indexOf(graph);
|
|
136
|
+
const reqs = idx.elementsOfType('REQ');
|
|
137
|
+
const tests = idx.elementsOfType('TEST');
|
|
99
138
|
return reqs
|
|
100
|
-
.filter(req =>
|
|
139
|
+
.filter(req => idx.in(req.id, 'verify').length === 0)
|
|
101
140
|
.map(req => ({
|
|
102
141
|
rule_id: 'R-01',
|
|
103
142
|
severity: 'error',
|
|
@@ -116,14 +155,15 @@ function reqMustHaveVerification(graph) {
|
|
|
116
155
|
// R-02: Every FUNC must satisfy at least one REQ
|
|
117
156
|
// ---------------------------------------------------------------------------
|
|
118
157
|
function funcMustSatisfyReq(graph) {
|
|
158
|
+
const idx = indexOf(graph);
|
|
119
159
|
// CR-SM-256: Grundgesamtheit sind die BLAETTER. Ein zerlegter FUNC erfuellt keine REQ,
|
|
120
160
|
// die nicht schon eine seiner Blatt-Funktionen erfuellt — wer ihn befriedigen will,
|
|
121
161
|
// muss eine satisfy-Kante erfinden, die dieselbe REQ ein zweites Mal beansprucht.
|
|
122
162
|
// Am graphcode-Selbstmodell (graphVersion 171) melden 12 der 13 Bloecke R-02 UND R-31,
|
|
123
163
|
// also dieselbe Ursache zweimal gezaehlt — die Klasse CR-SM-235/-242.
|
|
124
164
|
const decomposed = decomposedFuncs(graph);
|
|
125
|
-
const funcs =
|
|
126
|
-
const allReqs =
|
|
165
|
+
const funcs = idx.elementsOfType('FUNC').filter(e => !decomposed.has(e.id));
|
|
166
|
+
const allReqs = idx.elementsOfType('REQ');
|
|
127
167
|
// CR-GC-366: der Filter prueft den ZIELTYP. Vorher war er zieltyp-blind (`type === 'satisfy'`),
|
|
128
168
|
// womit ein `FUNC -satisfy-> UC` die Regel stumm schaltete, obwohl kein Requirement erfuellt war —
|
|
129
169
|
// der Regelname versprach mehr als der Code pruefte. Am graphcode-Selbstmodell verdeckte das 6 von
|
|
@@ -132,15 +172,15 @@ function funcMustSatisfyReq(graph) {
|
|
|
132
172
|
// Ziele das Meta-Modell gerade zulaesst.
|
|
133
173
|
const reqIds = new Set(allReqs.map(r => r.id));
|
|
134
174
|
return funcs
|
|
135
|
-
.filter(fn => !
|
|
175
|
+
.filter(fn => !idx.out(fn.id, 'satisfy').some(t => reqIds.has(t.target)))
|
|
136
176
|
.map(fn => {
|
|
137
177
|
const modId = findParentModule(graph, fn.id);
|
|
138
178
|
// Candidate REQs: same module's other FUNCs satisfy these, or constraints referencing this func
|
|
139
179
|
const siblingReqs = modId
|
|
140
180
|
? graph.traces
|
|
141
181
|
.filter(t => t.type === 'allocate' && t.target === modId && t.source !== fn.id)
|
|
142
|
-
.flatMap(t =>
|
|
143
|
-
.map(st =>
|
|
182
|
+
.flatMap(t => idx.out(t.source, 'satisfy'))
|
|
183
|
+
.map(st => idx.byId.get(st.target))
|
|
144
184
|
.filter((e) => !!e)
|
|
145
185
|
: [];
|
|
146
186
|
// Also include constraints whose description mentions this func's name
|
|
@@ -167,12 +207,13 @@ function funcMustSatisfyReq(graph) {
|
|
|
167
207
|
// R-03: ASIL isolation
|
|
168
208
|
// ---------------------------------------------------------------------------
|
|
169
209
|
function asilIsolation(graph) {
|
|
210
|
+
const idx = indexOf(graph);
|
|
170
211
|
const violations = [];
|
|
171
|
-
const modules =
|
|
212
|
+
const modules = idx.elementsOfType('MOD');
|
|
172
213
|
for (const mod of modules) {
|
|
173
214
|
const allocated = graph.traces
|
|
174
215
|
.filter(t => t.target === mod.id && t.type === 'allocate')
|
|
175
|
-
.map(t =>
|
|
216
|
+
.map(t => idx.byId.get(t.source))
|
|
176
217
|
.filter((e) => !!e);
|
|
177
218
|
const hasD = allocated.some(e => e.asil === 'D');
|
|
178
219
|
const hasQM = allocated.some(e => e.asil === 'QM');
|
|
@@ -206,22 +247,22 @@ function asilIsolation(graph) {
|
|
|
206
247
|
// `null` → messen, nicht urteilen: die Regel schweigt.
|
|
207
248
|
// ---------------------------------------------------------------------------
|
|
208
249
|
function maxModuleSize(graph, policy) {
|
|
250
|
+
const idx = indexOf(graph);
|
|
209
251
|
const violations = [];
|
|
210
252
|
const steps = policy.moduleSize;
|
|
211
253
|
if (steps === null)
|
|
212
254
|
return violations;
|
|
213
|
-
const modules =
|
|
255
|
+
const modules = idx.elementsOfType('MOD');
|
|
214
256
|
for (const mod of modules) {
|
|
215
|
-
const allocatedIds =
|
|
216
|
-
const allocated = allocatedIds.map(id =>
|
|
257
|
+
const allocatedIds = idx.in(mod.id, 'allocate').map(t => t.source);
|
|
258
|
+
const allocated = allocatedIds.map(id => idx.byId.get(id)).filter((e) => !!e);
|
|
217
259
|
const funcCount = allocated.length;
|
|
218
260
|
if (funcCount <= steps.coupled)
|
|
219
261
|
continue;
|
|
220
262
|
// Count crossing flows: io paths from FUNCs in this module to FUNCs in other modules
|
|
221
263
|
const funcIds = new Set(allocatedIds);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return false;
|
|
264
|
+
// CR-SM-264: nur die io-Kanten, nicht alle Traces je MOD.
|
|
265
|
+
const crossings = idx.tracesOfType('io').filter(t => {
|
|
225
266
|
const srcIn = funcIds.has(t.source);
|
|
226
267
|
const tgtIn = funcIds.has(t.target);
|
|
227
268
|
return (srcIn && !tgtIn) || (!srcIn && tgtIn);
|
|
@@ -263,10 +304,11 @@ function maxModuleSize(graph, policy) {
|
|
|
263
304
|
// R-05: Every TEST must verify at least one REQ
|
|
264
305
|
// ---------------------------------------------------------------------------
|
|
265
306
|
function testMustVerifyReq(graph) {
|
|
266
|
-
const
|
|
267
|
-
const
|
|
307
|
+
const idx = indexOf(graph);
|
|
308
|
+
const tests = idx.elementsOfType('TEST');
|
|
309
|
+
const allReqs = idx.elementsOfType('REQ');
|
|
268
310
|
return tests
|
|
269
|
-
.filter(test =>
|
|
311
|
+
.filter(test => idx.out(test.id, 'verify').length === 0)
|
|
270
312
|
.map(test => ({
|
|
271
313
|
rule_id: 'R-05',
|
|
272
314
|
severity: 'warning',
|
|
@@ -290,11 +332,13 @@ function testMustVerifyReq(graph) {
|
|
|
290
332
|
// R-08: Bidirectional trace consistency
|
|
291
333
|
// ---------------------------------------------------------------------------
|
|
292
334
|
function traceConsistency(graph) {
|
|
335
|
+
const idx = indexOf(graph);
|
|
336
|
+
// CR-SM-266 D5: kein `category !== 'audit'`-Filter mehr — das Attribut ist weg, und mit ihm
|
|
337
|
+
// die Moeglichkeit, eine Kante per Selbstauskunft aus der Referenzintegritaet zu nehmen.
|
|
293
338
|
return graph.traces
|
|
294
|
-
.filter(t => t.category !== 'audit')
|
|
295
339
|
.filter(t => {
|
|
296
|
-
const sourceExists =
|
|
297
|
-
const targetExists =
|
|
340
|
+
const sourceExists = idx.byId.has(t.source);
|
|
341
|
+
const targetExists = idx.byId.has(t.target);
|
|
298
342
|
return !sourceExists || !targetExists;
|
|
299
343
|
})
|
|
300
344
|
.map(t => ({
|
|
@@ -315,11 +359,14 @@ function traceConsistency(graph) {
|
|
|
315
359
|
// concern, stricter check — no parallel path); up to two warnings per FLOW.
|
|
316
360
|
// ---------------------------------------------------------------------------
|
|
317
361
|
function flowCompleteness(graph) {
|
|
318
|
-
const
|
|
362
|
+
const idx = indexOf(graph);
|
|
363
|
+
const flows = idx.elementsOfType('FLOW');
|
|
319
364
|
// Producers/consumers of a FLOW are FUNCs or ACTORs (io endpoints on the
|
|
320
|
-
// upstream/downstream side)
|
|
321
|
-
|
|
322
|
-
|
|
365
|
+
// upstream/downstream side). CR-SM-266 D4: UC ist KEIN Konsument mehr — das Pattern
|
|
366
|
+
// `FLOW -io-> UC` ist entfallen, ein FLOW erreicht einen UC nur noch ueber ein Kettenglied.
|
|
367
|
+
// Der fix_hint hat den UC vorher noch angeboten und haette in eine R-18-Ablehnung gefuehrt.
|
|
368
|
+
const sources = graph.elements.filter(e => e.type === 'FUNC' || e.type === 'ACTOR'); // Mehrtyp: ein Durchlauf ist hier billiger als vier Index-Listen zu mischen (Reihenfolge!)
|
|
369
|
+
const io = idx.tracesOfType('io');
|
|
323
370
|
const violations = [];
|
|
324
371
|
for (const f of flows) {
|
|
325
372
|
const ctx = { element_type: f.type, element_name: f.name, candidate_targets: toCandidates(sources) };
|
|
@@ -339,7 +386,7 @@ function flowCompleteness(graph) {
|
|
|
339
386
|
severity: 'warning',
|
|
340
387
|
element_id: f.id,
|
|
341
388
|
message: `${f.id} has no consumer (outgoing io)`,
|
|
342
|
-
fix_hint: 'Link a FUNC
|
|
389
|
+
fix_hint: 'Link a FUNC or ACTOR as the target via io trace',
|
|
343
390
|
context: ctx,
|
|
344
391
|
});
|
|
345
392
|
}
|
|
@@ -361,6 +408,7 @@ function flowCompleteness(graph) {
|
|
|
361
408
|
/** Trace types on which a direct 2-cycle is a real finding (CR-GC-315). */
|
|
362
409
|
const CIRCULAR_TRACE_TYPES = new Set(['compose', 'allocate', 'relation']);
|
|
363
410
|
function noDirectCircular(graph) {
|
|
411
|
+
const idx = indexOf(graph);
|
|
364
412
|
const violations = [];
|
|
365
413
|
// Direction-independent key: A↔B is ONE cycle, reported once. Keying on the
|
|
366
414
|
// message (CR-GC-315 predecessor) never collapsed anything — the two
|
|
@@ -369,7 +417,7 @@ function noDirectCircular(graph) {
|
|
|
369
417
|
for (const t of graph.traces) {
|
|
370
418
|
if (!CIRCULAR_TRACE_TYPES.has(t.type))
|
|
371
419
|
continue;
|
|
372
|
-
if (!
|
|
420
|
+
if (!idx.out(t.target, t.type).some(other => other.target === t.source))
|
|
373
421
|
continue;
|
|
374
422
|
const key = `${t.type}|${[t.source, t.target].sort().join('|')}`;
|
|
375
423
|
if (seen.has(key))
|
|
@@ -391,12 +439,12 @@ function noDirectCircular(graph) {
|
|
|
391
439
|
// RD-01: Unresolved requirement (leaf REQ without satisfy)
|
|
392
440
|
// ---------------------------------------------------------------------------
|
|
393
441
|
function unresolvedRequirement(graph) {
|
|
394
|
-
const
|
|
442
|
+
const idx = indexOf(graph);
|
|
443
|
+
const reqs = idx.elementsOfType('REQ');
|
|
395
444
|
// Leaf REQs: no compose→REQ children
|
|
396
|
-
const leafReqs = reqs.filter(req => !
|
|
397
|
-
graph.elements.some(e => e.id === t.target && e.type === 'REQ')));
|
|
445
|
+
const leafReqs = reqs.filter(req => !idx.out(req.id, 'compose').some(t => idx.typeOf(t.target) === 'REQ'));
|
|
398
446
|
return leafReqs
|
|
399
|
-
.filter(req =>
|
|
447
|
+
.filter(req => idx.in(req.id, 'satisfy').length === 0)
|
|
400
448
|
.map(req => ({
|
|
401
449
|
rule_id: 'RD-01',
|
|
402
450
|
severity: 'warning',
|
|
@@ -415,12 +463,11 @@ function unresolvedRequirement(graph) {
|
|
|
415
463
|
// RD-02: Decomposition consistency (parent REQ should not have FUNC satisfy)
|
|
416
464
|
// ---------------------------------------------------------------------------
|
|
417
465
|
function decompositionConsistency(graph) {
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
|
|
466
|
+
const idx = indexOf(graph);
|
|
467
|
+
const reqs = idx.elementsOfType('REQ');
|
|
468
|
+
const parentReqs = reqs.filter(req => idx.out(req.id, 'compose').some(t => idx.typeOf(t.target) === 'REQ'));
|
|
421
469
|
return parentReqs
|
|
422
|
-
.filter(req =>
|
|
423
|
-
graph.elements.some(e => e.id === t.source && e.type === 'FUNC')))
|
|
470
|
+
.filter(req => idx.in(req.id, 'satisfy').some(t => idx.typeOf(t.source) === 'FUNC'))
|
|
424
471
|
.map(req => ({
|
|
425
472
|
rule_id: 'RD-02',
|
|
426
473
|
severity: 'warning',
|
|
@@ -434,18 +481,18 @@ function decompositionConsistency(graph) {
|
|
|
434
481
|
// RD-03: No premature decomposition (all children same satisfy target)
|
|
435
482
|
// ---------------------------------------------------------------------------
|
|
436
483
|
function noPrematureDecomposition(graph) {
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
|
|
484
|
+
const idx = indexOf(graph);
|
|
485
|
+
const reqs = idx.elementsOfType('REQ');
|
|
486
|
+
const parentReqs = reqs.filter(req => idx.out(req.id, 'compose').some(t => idx.typeOf(t.target) === 'REQ'));
|
|
440
487
|
return parentReqs
|
|
441
488
|
.filter(parent => {
|
|
442
489
|
const childIds = graph.traces
|
|
443
490
|
.filter(t => t.source === parent.id && t.type === 'compose' &&
|
|
444
|
-
|
|
491
|
+
idx.typeOf(t.target) === 'REQ')
|
|
445
492
|
.map(t => t.target);
|
|
446
493
|
if (childIds.length < 2)
|
|
447
494
|
return false;
|
|
448
|
-
const satisfyTargets = childIds.map(cid =>
|
|
495
|
+
const satisfyTargets = childIds.map(cid => idx.in(cid, 'satisfy').filter(t => t.source !== cid).map(t => t.source));
|
|
449
496
|
// All children satisfied by the same single source
|
|
450
497
|
const allSame = satisfyTargets.every(targets => targets.length === 1 && targets[0] === satisfyTargets[0]?.[0]);
|
|
451
498
|
return allSame && satisfyTargets[0]?.length === 1;
|
|
@@ -473,8 +520,10 @@ function noPrematureDecomposition(graph) {
|
|
|
473
520
|
*/
|
|
474
521
|
const DECOMPOSITION_BREADTH_MAX = 11;
|
|
475
522
|
function decompositionBreadth(graph) {
|
|
476
|
-
const
|
|
477
|
-
|
|
523
|
+
const idx = indexOf(graph);
|
|
524
|
+
// CR-SM-264: `typeOf` und `byId` baute diese Regel je Aufruf selbst — der Index hat beide.
|
|
525
|
+
const typeOf = idx;
|
|
526
|
+
const byId = idx.byId;
|
|
478
527
|
const counts = new Map();
|
|
479
528
|
const bump = (parentId, kind) => {
|
|
480
529
|
const key = `${parentId}\u0000${kind}`;
|
|
@@ -485,8 +534,8 @@ function decompositionBreadth(graph) {
|
|
|
485
534
|
counts.set(key, { parentId, kind, n: 1 });
|
|
486
535
|
};
|
|
487
536
|
for (const t of graph.traces) {
|
|
488
|
-
const src = typeOf.
|
|
489
|
-
const tgt = typeOf.
|
|
537
|
+
const src = typeOf.typeOf(t.source);
|
|
538
|
+
const tgt = typeOf.typeOf(t.target);
|
|
490
539
|
if (t.type === 'compose' && src === 'FUNC' && tgt === 'FUNC') {
|
|
491
540
|
bump(t.source, 'sub-FUNC');
|
|
492
541
|
}
|
|
@@ -515,9 +564,10 @@ function decompositionBreadth(graph) {
|
|
|
515
564
|
// R-14: UC must have at least 1 compose trace (→ FCHAIN or REQ) (CR-117)
|
|
516
565
|
// ---------------------------------------------------------------------------
|
|
517
566
|
function ucMustHaveCompose(graph) {
|
|
518
|
-
const
|
|
567
|
+
const idx = indexOf(graph);
|
|
568
|
+
const ucs = idx.elementsOfType('UC');
|
|
519
569
|
return ucs
|
|
520
|
-
.filter(uc =>
|
|
570
|
+
.filter(uc => idx.out(uc.id, 'compose').length === 0)
|
|
521
571
|
.map(uc => ({
|
|
522
572
|
rule_id: 'R-14',
|
|
523
573
|
severity: 'warning',
|
|
@@ -549,14 +599,15 @@ function ucMustHaveCompose(graph) {
|
|
|
549
599
|
// einen einzigen Befund ueber beide Selbstmodelle.
|
|
550
600
|
// ---------------------------------------------------------------------------
|
|
551
601
|
function fchainCompleteness(graph) {
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
const
|
|
602
|
+
const idx = indexOf(graph);
|
|
603
|
+
const fchains = idx.elementsOfType('FCHAIN');
|
|
604
|
+
const funcs = idx.elementsOfType('FUNC');
|
|
605
|
+
const ucs = idx.elementsOfType('UC');
|
|
555
606
|
const ucIds = new Set(ucs.map(u => u.id));
|
|
556
607
|
const violations = [];
|
|
557
608
|
for (const fc of fchains) {
|
|
558
609
|
const ctx = { element_type: fc.type, element_name: fc.name };
|
|
559
|
-
if (
|
|
610
|
+
if (idx.out(fc.id, 'compose').length === 0) {
|
|
560
611
|
violations.push({
|
|
561
612
|
rule_id: 'R-15',
|
|
562
613
|
severity: 'warning',
|
|
@@ -566,7 +617,7 @@ function fchainCompleteness(graph) {
|
|
|
566
617
|
context: { ...ctx, candidate_targets: toCandidates(funcs) },
|
|
567
618
|
});
|
|
568
619
|
}
|
|
569
|
-
if (!
|
|
620
|
+
if (!idx.in(fc.id, 'compose').some(t => ucIds.has(t.source))) {
|
|
570
621
|
violations.push({
|
|
571
622
|
rule_id: 'R-15',
|
|
572
623
|
severity: 'warning',
|
|
@@ -583,9 +634,10 @@ function fchainCompleteness(graph) {
|
|
|
583
634
|
// R-16: ACTOR must have at least 1 io trace (CR-117)
|
|
584
635
|
// ---------------------------------------------------------------------------
|
|
585
636
|
function actorMustHaveTrace(graph) {
|
|
586
|
-
const
|
|
637
|
+
const idx = indexOf(graph);
|
|
638
|
+
const actors = idx.elementsOfType('ACTOR');
|
|
587
639
|
return actors
|
|
588
|
-
.filter(a =>
|
|
640
|
+
.filter(a => idx.out(a.id, 'io').length === 0 && idx.in(a.id, 'io').length === 0)
|
|
589
641
|
.map(a => ({
|
|
590
642
|
rule_id: 'R-16',
|
|
591
643
|
severity: 'warning',
|
|
@@ -602,9 +654,10 @@ function actorMustHaveTrace(graph) {
|
|
|
602
654
|
// R-17: SYS must have at least 1 compose trace (CR-117)
|
|
603
655
|
// ---------------------------------------------------------------------------
|
|
604
656
|
function sysMustHaveCompose(graph) {
|
|
605
|
-
const
|
|
657
|
+
const idx = indexOf(graph);
|
|
658
|
+
const systems = idx.elementsOfType('SYS');
|
|
606
659
|
return systems
|
|
607
|
-
.filter(sys =>
|
|
660
|
+
.filter(sys => idx.out(sys.id, 'compose').length === 0)
|
|
608
661
|
.map(sys => ({
|
|
609
662
|
rule_id: 'R-17',
|
|
610
663
|
severity: 'warning',
|
|
@@ -629,10 +682,10 @@ function sysMustHaveCompose(graph) {
|
|
|
629
682
|
// zusaetzlich compose) — genau deshalb ist jetzt der Zeitpunkt.
|
|
630
683
|
// ---------------------------------------------------------------------------
|
|
631
684
|
function msEmptyScope(graph) {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
.filter(ms => !
|
|
685
|
+
const idx = indexOf(graph);
|
|
686
|
+
const crIds = new Set(idx.elementsOfType('CR').map(e => e.id));
|
|
687
|
+
return idx.elementsOfType('MS')
|
|
688
|
+
.filter(ms => !idx.in(ms.id, 'relation').some(t => crIds.has(t.source)))
|
|
636
689
|
.map(ms => ({
|
|
637
690
|
rule_id: 'MS-01',
|
|
638
691
|
severity: 'warning',
|
|
@@ -646,7 +699,8 @@ function msEmptyScope(graph) {
|
|
|
646
699
|
// MS-02: Milestone depends-on dangling target
|
|
647
700
|
// ---------------------------------------------------------------------------
|
|
648
701
|
function msDanglingDependency(graph) {
|
|
649
|
-
const
|
|
702
|
+
const idx = indexOf(graph);
|
|
703
|
+
const msIds = new Set(idx.elementsOfType('MS').map(e => e.id));
|
|
650
704
|
return graph.traces
|
|
651
705
|
.filter(t => t.type === 'relation' && t.label === 'depends-on' && msIds.has(t.source))
|
|
652
706
|
.filter(t => !msIds.has(t.target))
|
|
@@ -667,24 +721,75 @@ function msDanglingDependency(graph) {
|
|
|
667
721
|
// here we only judge pairs whose endpoints both resolve. Audit traces are exempt.
|
|
668
722
|
// ---------------------------------------------------------------------------
|
|
669
723
|
function validTracePattern(graph) {
|
|
670
|
-
const
|
|
724
|
+
const idx = indexOf(graph);
|
|
725
|
+
// CR-SM-264: die Typ-Map kommt aus dem Index statt je Aufruf neu gebaut zu werden.
|
|
726
|
+
const typeById = { get: (id) => idx.typeOf(id) };
|
|
727
|
+
// CR-SM-266 D5: der `category !== 'audit'`-Filter ist WEG. Er war die Ausnahme fuer das
|
|
728
|
+
// einzige audit-Pattern (`SESSION -produces-> *`); mit dessen Entfernung waere er zum
|
|
729
|
+
// Schlupfloch geworden — eine Kante mit `category: 'audit'` haette die Matrix komplett
|
|
730
|
+
// umgangen. Das Attribut existiert nicht mehr (ontology.ts), hier faellt der Leser mit.
|
|
671
731
|
return graph.traces
|
|
672
|
-
.filter(t => t.category !== 'audit')
|
|
673
732
|
.filter(t => {
|
|
674
733
|
const src = typeById.get(t.source);
|
|
675
734
|
const tgt = typeById.get(t.target);
|
|
676
735
|
if (!src || !tgt)
|
|
677
736
|
return false; // dangling endpoint → R-08, not R-18
|
|
678
|
-
|
|
737
|
+
// CR-SM-266 B: die where-Patterns lesen die Kinds des betroffenen Endes mit.
|
|
738
|
+
return !isValidTrace({
|
|
739
|
+
source: src, target: tgt, type: t.type, label: t.label,
|
|
740
|
+
sourceKinds: idx.byId.get(t.source)?.kinds,
|
|
741
|
+
targetKinds: idx.byId.get(t.target)?.kinds,
|
|
742
|
+
});
|
|
679
743
|
})
|
|
680
|
-
.map(t => ({
|
|
744
|
+
.map((t) => ({
|
|
681
745
|
rule_id: 'R-18',
|
|
682
746
|
severity: 'error',
|
|
683
747
|
element_id: t.source,
|
|
684
748
|
message: `Invalid trace ${t.source} -${t.type}-> ${t.target}: ` +
|
|
685
749
|
`${typeById.get(t.source)} → ${typeById.get(t.target)} is not a valid ${t.type} pattern`,
|
|
686
750
|
fix_hint: 'Use a trace type whose TRACE_PATTERNS allows this source/target element-type pair',
|
|
687
|
-
}))
|
|
751
|
+
}))
|
|
752
|
+
.concat(cardinalityViolations(graph));
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* CR-SM-266b — das ZWEITE BEIN von R-18: die Kardinalitaets-Obergrenzen.
|
|
756
|
+
*
|
|
757
|
+
* Bewusst dieselbe `rule_id` und keine eigene, nach dem Muster von R-10 (Produzent/Konsument)
|
|
758
|
+
* und R-15 seit CR-SM-249: die Aussage ist dieselbe — "diese Kante ist nach dem Meta-Modell
|
|
759
|
+
* nicht zulaessig" —, nur ist die Bedingung hier eine ueber die MENGE statt ueber das Paar.
|
|
760
|
+
* Eine eigene ID kostete dauerhaft einen Nenner-Anteil, eine Katalogzeile, eine
|
|
761
|
+
* readiness-Zuordnung und einen Golden-File-Eintrag (Gate 6), und ein Leser muesste sie von
|
|
762
|
+
* R-18 unterscheiden, obwohl der naechste Schritt derselbe ist: Kante weg.
|
|
763
|
+
*
|
|
764
|
+
* EIN Befund je Quellknoten, nicht je ueberzaehliger Kante. Bei n Allokationen sind nicht
|
|
765
|
+
* n-1 Kanten "falsch" — es ist EINE Entscheidung offen (welches Modul), und n-1 Befunde
|
|
766
|
+
* wuerden den Zaehler ueber den Nenner-Beitrag treiben, die Fehlmessung aus CR-SM-242.
|
|
767
|
+
*/
|
|
768
|
+
function cardinalityViolations(graph) {
|
|
769
|
+
const idx = indexOf(graph);
|
|
770
|
+
const violations = [];
|
|
771
|
+
for (const p of BOUNDED_PATTERNS) {
|
|
772
|
+
const max = maxOccurs(p.cardinality);
|
|
773
|
+
for (const el of idx.elementsOfType(p.source)) {
|
|
774
|
+
const matching = idx.out(el.id, p.type).filter(t => idx.typeOf(t.target) === p.target);
|
|
775
|
+
if (matching.length <= max)
|
|
776
|
+
continue;
|
|
777
|
+
violations.push({
|
|
778
|
+
rule_id: 'R-18',
|
|
779
|
+
severity: 'error',
|
|
780
|
+
element_id: el.id,
|
|
781
|
+
message: `${el.id} has ${matching.length} ${p.type} traces to ${p.target} — ` +
|
|
782
|
+
`the meta-model allows at most ${max} (${matching.map(t => t.target).join(', ')})`,
|
|
783
|
+
fix_hint: `Keep exactly one ${p.type} trace from ${el.id} to a ${p.target} and delete the others`,
|
|
784
|
+
context: {
|
|
785
|
+
element_type: el.type,
|
|
786
|
+
element_name: el.name,
|
|
787
|
+
existing_traces: matching.map(t => ({ source: t.source, target: t.target, type: t.type })),
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return violations;
|
|
688
793
|
}
|
|
689
794
|
// ---------------------------------------------------------------------------
|
|
690
795
|
// R-19: Runnable TEST binding (CR-GC-205 Item 4) — a TEST that is not explicitly
|
|
@@ -697,8 +802,8 @@ function validTracePattern(graph) {
|
|
|
697
802
|
// only and surfaces an unbound runnable TEST in rules_evaluate / readiness.
|
|
698
803
|
// ---------------------------------------------------------------------------
|
|
699
804
|
function testMustHaveRunnableBinding(graph) {
|
|
700
|
-
|
|
701
|
-
|
|
805
|
+
const idx = indexOf(graph);
|
|
806
|
+
return idx.elementsOfType('TEST')
|
|
702
807
|
.filter(e => e.attributes?.concept !== true) // concept-only TESTs are exempt
|
|
703
808
|
// CR-SM-231: `testRefs` (Array, min 1) statt `testRef` (Objekt). Semantik unveraendert —
|
|
704
809
|
// Praesenz und Form, keine Datei-Existenz; die pruefst RC-02.
|
|
@@ -729,6 +834,7 @@ function testMustHaveRunnableBinding(graph) {
|
|
|
729
834
|
// zu sein.
|
|
730
835
|
// ---------------------------------------------------------------------------
|
|
731
836
|
function testFileExclusivity(graph) {
|
|
837
|
+
const idx = indexOf(graph);
|
|
732
838
|
const claimedBy = new Map();
|
|
733
839
|
for (const el of graph.elements) {
|
|
734
840
|
if (el.type !== 'TEST')
|
|
@@ -758,7 +864,7 @@ function testFileExclusivity(graph) {
|
|
|
758
864
|
element_id: owner,
|
|
759
865
|
message: `${owner} claims test file '${file}', which is also claimed by ${sorted.filter(o => o !== owner).join(', ')}`,
|
|
760
866
|
fix_hint: 'A test file belongs to at most one TEST. Split the file, or drop the entry from all but the one acceptance it really evidences',
|
|
761
|
-
context: { element_type: 'TEST', element_name:
|
|
867
|
+
context: { element_type: 'TEST', element_name: idx.byId.get(owner)?.name },
|
|
762
868
|
});
|
|
763
869
|
}
|
|
764
870
|
}
|
|
@@ -778,7 +884,8 @@ function testFileExclusivity(graph) {
|
|
|
778
884
|
// RC-01's job); this guards presence/shape only. Cycle-safe.
|
|
779
885
|
// ---------------------------------------------------------------------------
|
|
780
886
|
function funcMustHaveCodeBinding(graph) {
|
|
781
|
-
const
|
|
887
|
+
const idx = indexOf(graph);
|
|
888
|
+
const funcById = new Map(idx.elementsOfType('FUNC').map(e => [e.id, e]));
|
|
782
889
|
const composeFuncChildren = (id) => graph.traces
|
|
783
890
|
.filter(t => t.source === id && t.type === 'compose' && funcById.has(t.target))
|
|
784
891
|
.map(t => t.target);
|
|
@@ -869,9 +976,10 @@ function funcMustHaveCodeBinding(graph) {
|
|
|
869
976
|
// choice. The FCHAIN is the modelled claim; the test is owed on the claim.
|
|
870
977
|
// ---------------------------------------------------------------------------
|
|
871
978
|
function fchainMustHaveIntegrationTest(graph) {
|
|
979
|
+
const idx = indexOf(graph);
|
|
872
980
|
const typeOf = new Map(graph.elements.map(e => [e.id, e.type]));
|
|
873
981
|
const isFunc = (id) => typeOf.get(id) === 'FUNC';
|
|
874
|
-
const io =
|
|
982
|
+
const io = idx.tracesOfType('io');
|
|
875
983
|
// FUNC↔FUNC connections via the FLOW hop: (producer FUNC) → FLOW → (consumer FUNC).
|
|
876
984
|
const connections = [];
|
|
877
985
|
for (const e of graph.elements) {
|
|
@@ -894,7 +1002,7 @@ function fchainMustHaveIntegrationTest(graph) {
|
|
|
894
1002
|
}
|
|
895
1003
|
}
|
|
896
1004
|
// FCHAINs whose satisfy-REQ is verified by a TEST = chains with an integration test.
|
|
897
|
-
const verifiedReqs = new Set(
|
|
1005
|
+
const verifiedReqs = new Set(idx.tracesOfType('verify').map(t => t.target));
|
|
898
1006
|
const testedChains = new Set();
|
|
899
1007
|
for (const t of graph.traces) {
|
|
900
1008
|
if (t.type === 'satisfy' && typeOf.get(t.source) === 'FCHAIN' && verifiedReqs.has(t.target)) {
|
|
@@ -918,7 +1026,7 @@ function fchainMustHaveIntegrationTest(graph) {
|
|
|
918
1026
|
if (seen.has(key))
|
|
919
1027
|
continue;
|
|
920
1028
|
seen.add(key);
|
|
921
|
-
const anchorEl =
|
|
1029
|
+
const anchorEl = idx.byId.get(anchor);
|
|
922
1030
|
violations.push({
|
|
923
1031
|
rule_id: 'R-21',
|
|
924
1032
|
severity: 'warning',
|
|
@@ -940,11 +1048,12 @@ function fchainMustHaveIntegrationTest(graph) {
|
|
|
940
1048
|
// deliberately not checked (CR-GVE-173/174).
|
|
941
1049
|
// ---------------------------------------------------------------------------
|
|
942
1050
|
function funcMustBeAllocated(graph) {
|
|
943
|
-
const
|
|
944
|
-
const
|
|
1051
|
+
const idx = indexOf(graph);
|
|
1052
|
+
const funcs = idx.elementsOfType('FUNC');
|
|
1053
|
+
const mods = idx.elementsOfType('MOD');
|
|
945
1054
|
const modIds = new Set(mods.map(m => m.id));
|
|
946
1055
|
return funcs
|
|
947
|
-
.filter(fn => !
|
|
1056
|
+
.filter(fn => !idx.out(fn.id, 'allocate').some(t => modIds.has(t.target)))
|
|
948
1057
|
.map(fn => ({
|
|
949
1058
|
rule_id: 'R-22',
|
|
950
1059
|
severity: 'warning',
|
|
@@ -967,13 +1076,13 @@ function funcMustBeAllocated(graph) {
|
|
|
967
1076
|
// R-22: one rule per element perspective (like R-01 REQ-side beside R-02/R-05).
|
|
968
1077
|
// ---------------------------------------------------------------------------
|
|
969
1078
|
function modMustHaveAllocatedFunc(graph) {
|
|
970
|
-
const
|
|
971
|
-
const
|
|
1079
|
+
const idx = indexOf(graph);
|
|
1080
|
+
const mods = idx.elementsOfType('MOD');
|
|
1081
|
+
const funcs = idx.elementsOfType('FUNC');
|
|
972
1082
|
const modIds = new Set(mods.map(m => m.id));
|
|
973
|
-
const unallocatedFuncs = funcs.filter(fn => !
|
|
1083
|
+
const unallocatedFuncs = funcs.filter(fn => !idx.out(fn.id, 'allocate').some(t => modIds.has(t.target)));
|
|
974
1084
|
return mods
|
|
975
|
-
.filter(mod => !
|
|
976
|
-
graph.elements.some(e => e.id === t.source && e.type === 'FUNC')))
|
|
1085
|
+
.filter(mod => !idx.in(mod.id, 'allocate').some(t => idx.typeOf(t.source) === 'FUNC'))
|
|
977
1086
|
.map(mod => ({
|
|
978
1087
|
rule_id: 'R-23',
|
|
979
1088
|
severity: 'warning',
|
|
@@ -1001,8 +1110,8 @@ function modMustHaveAllocatedFunc(graph) {
|
|
|
1001
1110
|
// out of scope here (pure, no I/O — that is RC-03's job).
|
|
1002
1111
|
// ---------------------------------------------------------------------------
|
|
1003
1112
|
function schemaMustHaveSchemaRef(graph) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1113
|
+
const idx = indexOf(graph);
|
|
1114
|
+
return idx.elementsOfType('SCHEMA')
|
|
1006
1115
|
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
1007
1116
|
.filter(e => !RealRefSchema.safeParse(e.attributes?.realRef).success)
|
|
1008
1117
|
.map(sc => ({
|
|
@@ -1025,8 +1134,9 @@ function schemaMustHaveSchemaRef(graph) {
|
|
|
1025
1134
|
// realRef with a file. RESOLUTION (file on disk) is a consumer/RC concern.
|
|
1026
1135
|
// ---------------------------------------------------------------------------
|
|
1027
1136
|
function physicalModMustHaveRealRef(graph) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1137
|
+
const idx = indexOf(graph);
|
|
1138
|
+
return idx.elementsOfType('MOD')
|
|
1139
|
+
.filter(e => e.attributes?.kind === 'physical')
|
|
1030
1140
|
.filter(e => e.attributes?.concept !== true && e.attributes?.external !== true)
|
|
1031
1141
|
.filter(e => !RealRefSchema.safeParse(e.attributes?.realRef).success)
|
|
1032
1142
|
.map(mod => ({
|
|
@@ -1077,8 +1187,9 @@ function physicalModMustHaveRealRef(graph) {
|
|
|
1077
1187
|
// waere Neubau-Zwang statt Steuerung.
|
|
1078
1188
|
// ---------------------------------------------------------------------------
|
|
1079
1189
|
function funcMustBeInEffectChain(graph) {
|
|
1080
|
-
const
|
|
1081
|
-
const
|
|
1190
|
+
const idx = indexOf(graph);
|
|
1191
|
+
const funcs = idx.elementsOfType('FUNC');
|
|
1192
|
+
const fchains = idx.elementsOfType('FCHAIN');
|
|
1082
1193
|
const funcIds = new Set(funcs.map(f => f.id));
|
|
1083
1194
|
const fchainIds = new Set(fchains.map(fc => fc.id));
|
|
1084
1195
|
// CR-SM-256: dieselbe Definition wie R-02 und R-31, nicht mehr die lokale Kopie.
|
|
@@ -1112,15 +1223,16 @@ function funcMustBeInEffectChain(graph) {
|
|
|
1112
1223
|
// eigenen Nenner-Beitrag, was CR-SM-242 fuer IO-01 als Fehlmessung nachgewiesen hat.
|
|
1113
1224
|
// ---------------------------------------------------------------------------
|
|
1114
1225
|
function funcMustBeWired(graph) {
|
|
1226
|
+
const idx = indexOf(graph);
|
|
1115
1227
|
// CR-SM-256: Grundgesamtheit sind die BLAETTER, wie bei R-02 und R-30. Durch einen
|
|
1116
1228
|
// Blackbox-Block fliessen keine Daten — die io-Kanten haengen an seinen Kindern.
|
|
1117
1229
|
// CR-GC-375 hat das fuer R-31 ausdruecklich offengelassen ("eine eigene Frage, hier
|
|
1118
1230
|
// bewusst nicht"); die Messung liegt jetzt vor: 12 von 13 Bloecken am
|
|
1119
1231
|
// graphcode-Selbstmodell melden nur deshalb, und keiner aus einem anderen Grund.
|
|
1120
1232
|
const decomposed = decomposedFuncs(graph);
|
|
1121
|
-
const funcs =
|
|
1122
|
-
const flows =
|
|
1123
|
-
const io =
|
|
1233
|
+
const funcs = idx.elementsOfType('FUNC').filter(e => !decomposed.has(e.id));
|
|
1234
|
+
const flows = idx.elementsOfType('FLOW');
|
|
1235
|
+
const io = idx.tracesOfType('io');
|
|
1124
1236
|
const hasInput = new Set(io.map(t => t.target)); // FLOW -io-> FUNC
|
|
1125
1237
|
const hasOutput = new Set(io.map(t => t.source)); // FUNC -io-> FLOW
|
|
1126
1238
|
return funcs
|