@sigloch/contracts 6.3.0 → 10.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.
- package/dist/se/ao-rules.d.ts +12 -12
- package/dist/se/ao-rules.js +88 -225
- package/dist/se/conformance-rules.d.ts +28 -0
- package/dist/se/conformance-rules.js +40 -17
- package/dist/se/cr-quality-rules.js +75 -58
- package/dist/se/evaluate-all.d.ts +4 -2
- package/dist/se/evaluate-all.js +40 -24
- package/dist/se/fchain-quality-rules.d.ts +0 -1
- package/dist/se/fchain-quality-rules.js +0 -44
- package/dist/se/flat-graph.d.ts +9 -1
- package/dist/se/flat-graph.js +15 -6
- 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 +10 -7
- package/dist/se/grammar-snapshot.js +172 -27
- package/dist/se/index.d.ts +4 -3
- package/dist/se/index.js +4 -3
- package/dist/se/meta-model.d.ts +99 -7
- package/dist/se/meta-model.js +155 -14
- package/dist/se/metric-rules.d.ts +20 -1
- package/dist/se/metric-rules.js +97 -58
- package/dist/se/module-crossings.d.ts +102 -0
- package/dist/se/module-crossings.js +196 -0
- package/dist/se/near-duplicate-rules.d.ts +16 -24
- package/dist/se/near-duplicate-rules.js +21 -92
- package/dist/se/ontology.d.ts +32 -47
- package/dist/se/ontology.js +56 -13
- package/dist/se/policy.d.ts +6 -0
- package/dist/se/policy.js +53 -2
- package/dist/se/quality-rules.d.ts +18 -0
- package/dist/se/quality-rules.js +68 -12
- package/dist/se/readiness.d.ts +7 -5
- package/dist/se/readiness.js +40 -22
- package/dist/se/rules.d.ts +6 -7
- package/dist/se/rules.js +358 -128
- package/dist/se/schema-quality-rules.d.ts +0 -1
- package/dist/se/schema-quality-rules.js +7 -28
- package/dist/se/similarity.d.ts +61 -0
- package/dist/se/similarity.js +116 -0
- package/dist/se/uc-quality-rules.js +31 -12
- package/package.json +3 -2
package/dist/se/meta-model.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { normalizeReqKinds } from './ontology.js';
|
|
2
|
+
/** Die Kinds, die eine Wirkkette bzw. ein Verhalten traegt. */
|
|
3
|
+
const BEHAVIOURAL_KINDS = ['functional', 'precondition', 'postcondition'];
|
|
4
|
+
/** Die Kinds, die eine Struktur (Modul/System) traegt. */
|
|
5
|
+
const STRUCTURAL_KINDS = ['non-functional', 'risk', 'mitigation'];
|
|
1
6
|
export const TRACE_PATTERNS = [
|
|
2
7
|
// ── compose (parent → child) ──
|
|
3
8
|
{ source: 'SYS', target: 'SYS', type: 'compose', cardinality: '0..*', description: 'System decomposes into subsystems' },
|
|
@@ -15,11 +20,25 @@ export const TRACE_PATTERNS = [
|
|
|
15
20
|
{ source: 'FLOW', target: 'ACTOR', type: 'io', description: 'Flow delivers to actor' },
|
|
16
21
|
{ source: 'FUNC', target: 'FLOW', type: 'io', description: 'Function outputs to flow' },
|
|
17
22
|
{ source: 'FLOW', target: 'FUNC', type: 'io', description: 'Flow feeds into function' },
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
// CR-SM-266 D1: `ACTOR -io-> UC` ENTFAELLT (BREAKING). Der tragende Pfad existiert und ist
|
|
24
|
+
// typisiert: `ACTOR -io-> FLOW -io-> FUNC`, FUNC in der FCHAIN des UC. Die Direktkante war
|
|
25
|
+
// die TYPLOSE Abkuerzung daneben — ein Actor-Beitrag ohne FLOW traegt kein SCHEMA und
|
|
26
|
+
// entzieht sich damit derselben Typgarantie, die fuer jeden anderen Fluss gilt. Migration
|
|
27
|
+
// je Bestandskante: FLOW einfuehren oder Kante loeschen (70 Kanten ueber 6 Graphen).
|
|
28
|
+
// CR-SM-266 D2: `MOD -io-> MOD` ENTFAELLT (BREAKING). Modul-Kopplung ist vollstaendig
|
|
29
|
+
// ABLEITBAR: `FUNC -allocate-> MOD` x `FUNC -io-> FLOW -io-> FUNC` ergibt die
|
|
30
|
+
// Modul-Ebenen-Projektion. Eine BEHAUPTETE Kopplungskante kann von der tatsaechlichen
|
|
31
|
+
// driften, eine berechnete nicht — und die behauptete gewann, weil sie billiger war.
|
|
32
|
+
// Soll-Architektur wird als REQ (non-functional) modelliert, nicht als Kante.
|
|
33
|
+
// CR-SM-266 D4: `FLOW -io-> UC` ENTFAELLT (BREAKING). Zweitweg neben `FLOW -io-> FUNC` mit
|
|
34
|
+
// FUNC in der FCHAIN des UC. Die UC-Grenze ERGIBT SICH aus den FLOWs der Kettenglieder; eine
|
|
35
|
+
// direkte FLOW-UC-Kante umgeht die Kettenzuordnung und damit IO-01 und R-21.
|
|
21
36
|
// ── satisfy (implementation) ──
|
|
22
|
-
{
|
|
37
|
+
{
|
|
38
|
+
source: 'FUNC', target: 'REQ', type: 'satisfy',
|
|
39
|
+
where: { on: 'target', field: 'kinds', allowed: BEHAVIOURAL_KINDS },
|
|
40
|
+
description: 'Function implements requirement — behavioural kinds only',
|
|
41
|
+
},
|
|
23
42
|
// CR-GC-366: `FUNC -satisfy-> UC` ENTFAELLT (BREAKING). Es war ein zweiter, redundanter Weg
|
|
24
43
|
// vom Verhalten zum Use Case neben dem einzig tragenden `UC -compose-> FCHAIN -compose-> FUNC`.
|
|
25
44
|
// Zwei Wege = zwei Wahrheiten: eine FUNC konnte einen UC bedienen, ohne in dessen Wirkkette zu
|
|
@@ -29,14 +48,42 @@ export const TRACE_PATTERNS = [
|
|
|
29
48
|
// stand ohnehin in einer FCHAIN); die restlichen 13 sind der Migrationsanlass, nicht ein
|
|
30
49
|
// Argument fuer das Pattern. Ersatz: die FUNC in die FCHAIN des UC aufnehmen (R-30).
|
|
31
50
|
// CR-154: NFR satisfy — non-functional REQs can be satisfied by chains, modules, or the system
|
|
51
|
+
//
|
|
52
|
+
// CR-SM-266 B: die drei strukturellen satisfy-Patterns tragen jetzt ein `where`. Ohne es
|
|
53
|
+
// konnte ein MOD oder SYS ein FUNKTIONALES REQ erfuellen, ohne dass eine Wirkkette es traegt
|
|
54
|
+
// — der Zweitweg aus CR-GC-366, eine Ebene hoeher. Die FCHAIN behaelt bewusst ALLE Kinds:
|
|
55
|
+
// sie IST die Wirkkette, an ihr haengen IO-01 und R-21, sie ist also nie die Abkuerzung.
|
|
32
56
|
{ source: 'FCHAIN', target: 'REQ', type: 'satisfy', description: 'Function chain satisfies end-to-end NFR (e.g. latency)' },
|
|
33
|
-
{
|
|
34
|
-
|
|
57
|
+
{
|
|
58
|
+
source: 'MOD', target: 'REQ', type: 'satisfy',
|
|
59
|
+
where: { on: 'target', field: 'kinds', allowed: STRUCTURAL_KINDS },
|
|
60
|
+
description: 'Module satisfies budget NFR (e.g. uptime, memory) — structural kinds only',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
source: 'SYS', target: 'REQ', type: 'satisfy',
|
|
64
|
+
where: { on: 'target', field: 'kinds', allowed: STRUCTURAL_KINDS },
|
|
65
|
+
description: 'System satisfies system-level NFR (e.g. availability) — structural kinds only',
|
|
66
|
+
},
|
|
35
67
|
// ── verify (INCOSE: test verifies requirement) ──
|
|
36
68
|
{ source: 'TEST', target: 'REQ', type: 'verify', description: 'Test verifies requirement' },
|
|
37
69
|
// ── allocate (deployment/assignment) ──
|
|
38
|
-
|
|
39
|
-
|
|
70
|
+
// CR-SM-266b A/C: OBERGRENZEN als Grammatik. Beide Kanten sind Besitz-Aussagen, und Besitz
|
|
71
|
+
// ist nicht mehrfach vergebbar — eine FUNC wohnt in EINEM Modul, ein FLOW traegt EINEN
|
|
72
|
+
// Datenvertrag. Zwei Allokationen sind kein "staerkeres" Modell, sondern eine Frage ohne
|
|
73
|
+
// Antwort ("in welchem Modul liegt der Code?"), und zwei SCHEMA an einem FLOW heben genau
|
|
74
|
+
// die Typgarantie auf, fuer die der geteilte SCHEMA-Knoten existiert (CR-SM-266 C): zwei
|
|
75
|
+
// Vertraege koennen divergieren, einer kann es nicht.
|
|
76
|
+
//
|
|
77
|
+
// `FUNC -allocate-> MOD` traegt nur die OBERE Grenze (`0..1`): seine Untergrenze bleibt
|
|
78
|
+
// R-22 (warning) — eine frisch angelegte FUNC ist legitim noch unalloziert, und ein
|
|
79
|
+
// error-Bein wuerde jedes schrittweise Authoring per Delta-Semantik blockieren.
|
|
80
|
+
//
|
|
81
|
+
// CR-SM-271 Teil 2: `FLOW -relation-> SCHEMA` ist `1..1` — beide Grenzen Grammatik. Ein
|
|
82
|
+
// FLOW ohne SCHEMA ist kein schwaecherer Typ, sondern untypisiert; die fruehere Untergrenze
|
|
83
|
+
// SC-04 (warning) ist damit ersatzlos entfallen (Grammatik ersetzt Regel, das urspruengliche
|
|
84
|
+
// Ziel von CR-SM-266 C). Durchgesetzt als drittes Bein von R-18 (s. REQUIRED_PATTERNS).
|
|
85
|
+
{ source: 'FUNC', target: 'MOD', type: 'allocate', cardinality: '0..1', description: 'Function deployed in exactly one module' },
|
|
86
|
+
{ source: 'FLOW', target: 'SCHEMA', type: 'relation', cardinality: '1', description: 'Flow data format defined by exactly one schema' },
|
|
40
87
|
// CR-228: REQ→MOD allocate removed — a MOD does not own a REQ. Physical NFRs use
|
|
41
88
|
// MOD→satisfy→REQ, behavioral NFRs FCHAIN→satisfy→REQ; structural constraints are
|
|
42
89
|
// rules, not REQs. R-18 now flags any REQ→MOD allocate edge as invalid.
|
|
@@ -44,24 +91,118 @@ export const TRACE_PATTERNS = [
|
|
|
44
91
|
{ source: 'MS', target: 'FUNC', type: 'compose', description: 'Milestone includes function' },
|
|
45
92
|
{ source: 'MS', target: 'REQ', type: 'compose', description: 'Milestone includes requirement' },
|
|
46
93
|
{ source: 'MS', target: 'UC', type: 'compose', description: 'Milestone includes use case' },
|
|
47
|
-
|
|
48
|
-
|
|
94
|
+
// CR-SM-266 D3: `MS -compose-> MS` ENTFAELLT (BREAKING). Zwei Kantenarten zwischen denselben
|
|
95
|
+
// zwei Meilensteinen sagten dasselbe auf zwei Arten — Schachtelung und Abfolge sind an einem
|
|
96
|
+
// Meilenstein nicht unterscheidbar, MS-01 zaehlte den Scope einmal ueber `CR -relation-> MS`
|
|
97
|
+
// (seit CR-SM-245 der einzige Weg) und die compose-Kante trug nichts mehr bei. `relation`
|
|
98
|
+
// bleibt als EINZIGE MS-MS-Kante, Semantik: Ordnung/Abfolge (Vorgaenger → Nachfolger).
|
|
99
|
+
// Migration: keine — 0 solche Kanten in allen 9 aktiven Familie-Graphen (gemessen 2026-08-25).
|
|
100
|
+
{ source: 'MS', target: 'MS', type: 'relation', label: 'depends-on', description: 'Milestone order: predecessor → successor' },
|
|
49
101
|
{ source: 'CR', target: 'MS', type: 'relation', description: 'CR assigned to milestone' },
|
|
50
102
|
// ── CR traceability (CR-155: CR tracks mutated elements) ──
|
|
51
103
|
{ source: 'CR', target: 'UC', type: 'relation', description: 'CR affects use case' },
|
|
52
104
|
{ source: 'CR', target: 'REQ', type: 'relation', description: 'CR affects requirement' },
|
|
53
105
|
{ source: 'CR', target: 'FUNC', type: 'relation', description: 'CR affects function' },
|
|
54
106
|
{ source: 'CR', target: 'MOD', type: 'relation', description: 'CR affects module' },
|
|
55
|
-
//
|
|
56
|
-
|
|
107
|
+
// CR-SM-266 D5: `SESSION -produces-> *` ENTFAELLT ersatzlos, mit ihm der TraceType `produces`
|
|
108
|
+
// und der ElementType `SESSION`. Der urspruengliche Entwurf hielt das Pattern fuer die
|
|
109
|
+
// Provenance-Kante und wollte es behalten; die MESSUNG hat das widerlegt: 0 produces-Kanten
|
|
110
|
+
// und 0 SESSION-Knoten in ALLEN 9 aktiven Familie-Graphen, auch im graphcode-Selbstmodell
|
|
111
|
+
// nach 195 gegateten Versionen. Die reale Provenance lebt seit CR-GC-347 in
|
|
112
|
+
// `.graphcode/audit.jsonl` — audit_trail/audit_stats lesen die DATEI, nie den Graphen.
|
|
113
|
+
// Einziger Schreiber war ein toter Pfad (CR-195d in graph-api-core), Leser mit Wirkung: keiner.
|
|
114
|
+
// Nicht auf `relation` umgebogen: eine Kante ohne Schreiber UND ohne Leser wird gestrichen,
|
|
115
|
+
// nicht umbenannt.
|
|
57
116
|
];
|
|
117
|
+
/**
|
|
118
|
+
* Erfuellt das betroffene Kantenende das Praedikat?
|
|
119
|
+
*
|
|
120
|
+
* TEILMENGEN-Semantik, nicht Schnittmenge: JEDER deklarierte Kind muss in der erlaubten Liste
|
|
121
|
+
* liegen. Die schwaechere Lesart ("mindestens einer passt") waere das Loch, das der ganze
|
|
122
|
+
* Abschnitt schliessen will — ein funktionales REQ zusaetzlich als `non-functional` zu
|
|
123
|
+
* deklarieren haette gereicht, damit ein MOD es wieder erfuellen darf, und der billige
|
|
124
|
+
* Zweitweg waere ueber ein Attribut zurueck.
|
|
125
|
+
*
|
|
126
|
+
* Der leere Fall LEHNT AB (`kinds.length === 0`): ohne Deklaration ist nicht entscheidbar, ob
|
|
127
|
+
* die Kante zulaessig ist, und "unentscheidbar" darf nicht "erlaubt" heissen — sonst wird das
|
|
128
|
+
* WEGLASSEN des Attributs zum Umgehungsweg. Das ist die durchsetzende Haelfte von REQ-X06
|
|
129
|
+
* (`kinds` ist Pflicht am REQ); die meldende Haelfte haengt an BQ-07.
|
|
130
|
+
*/
|
|
131
|
+
function satisfiesPredicate(where, trace) {
|
|
132
|
+
// `normalizeReqKinds` statt direktem Zugriff: drei Familie-Graphen tragen `kinds` als String
|
|
133
|
+
// statt als Liste, und ein `.every()` darauf WIRFT — mitten im Regellauf, statt einen Befund
|
|
134
|
+
// zu melden. Die Normalisierung heilt die Drift nicht, sie macht sie nur urteilsfaehig.
|
|
135
|
+
const kinds = normalizeReqKinds(where.on === 'source' ? trace.sourceKinds : trace.targetKinds);
|
|
136
|
+
if (kinds.length === 0)
|
|
137
|
+
return false;
|
|
138
|
+
return kinds.every(k => where.allowed.includes(k));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Die Obergrenze eines Kardinalitaets-Ausdrucks — `Infinity`, wo keine gilt.
|
|
142
|
+
*
|
|
143
|
+
* Getrennt von `isValidTrace`, weil es eine andere ART von Bedingung ist: `isValidTrace`
|
|
144
|
+
* urteilt ueber EINE Kante und braucht nur deren Enden, eine Kardinalitaet urteilt ueber die
|
|
145
|
+
* MENGE der Kanten eines Knotens. Der urspruengliche CR-Entwurf wollte beides in
|
|
146
|
+
* `isValidTrace` erledigen; das geht nicht, und die Korrektur steht im Impact-Audit.
|
|
147
|
+
*/
|
|
148
|
+
export function maxOccurs(cardinality) {
|
|
149
|
+
if (cardinality === undefined)
|
|
150
|
+
return Infinity;
|
|
151
|
+
return cardinality === '1' || cardinality === '0..1' ? 1 : Infinity;
|
|
152
|
+
}
|
|
153
|
+
/** Die Patterns mit einer echten Obergrenze — die Arbeitsliste des Kardinalitaets-Beins. */
|
|
154
|
+
export const BOUNDED_PATTERNS = TRACE_PATTERNS.filter(p => maxOccurs(p.cardinality) < Infinity);
|
|
155
|
+
/**
|
|
156
|
+
* Die Untergrenze eines Kardinalitaets-Ausdrucks — `0`, wo keine gilt.
|
|
157
|
+
* Gegenstueck zu `maxOccurs` (CR-SM-271 Teil 2).
|
|
158
|
+
*/
|
|
159
|
+
export function minOccurs(cardinality) {
|
|
160
|
+
if (cardinality === undefined)
|
|
161
|
+
return 0;
|
|
162
|
+
return cardinality === '1' || cardinality === '1..*' ? 1 : 0;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Die Patterns, deren Untergrenze DURCHGESETZT wird — nur exakt-`1` (CR-SM-271 Teil 2).
|
|
166
|
+
*
|
|
167
|
+
* Bewusst NICHT `minOccurs(p.cardinality) >= 1`: die `1..*`-compose-Patterns (SYS→UC,
|
|
168
|
+
* UC→FCHAIN, UC→REQ, FCHAIN→FUNC) tragen ihre Untergrenze seit jeher als
|
|
169
|
+
* Vollstaendigkeits-WARNUNG (UC-01, R-15, …) — ein SYS existiert legitim, BEVOR sein erster
|
|
170
|
+
* UC modelliert ist, und ein error-Bein wuerde jeden Kaltstart per Delta-Semantik blockieren.
|
|
171
|
+
* Sie hier mitzunehmen waere zudem die Doppelzaehlung aus Gate 4: eine Ursache, zwei Befunde.
|
|
172
|
+
* Exakt-`1` dagegen ist eine Grammatik-Aussage ohne legitimen Zwischenzustand: ein FLOW ohne
|
|
173
|
+
* SCHEMA ist untypisiert, nicht unfertig — sein SCHEMA gehoert in DENSELBEN Batch.
|
|
174
|
+
*/
|
|
175
|
+
export const REQUIRED_PATTERNS = TRACE_PATTERNS.filter(p => p.cardinality === '1');
|
|
58
176
|
/**
|
|
59
177
|
* Check if a trace matches a valid pattern in the meta-model.
|
|
60
|
-
* Uses element types (not IDs)
|
|
178
|
+
* Uses element types (not IDs) plus — where a pattern declares one — an attribute predicate.
|
|
61
179
|
*/
|
|
62
180
|
export function isValidTrace(trace, patterns = TRACE_PATTERNS) {
|
|
63
181
|
return patterns.some(p => (p.source === '*' || p.source === trace.source) &&
|
|
64
182
|
(p.target === '*' || p.target === trace.target) &&
|
|
65
183
|
p.type === trace.type &&
|
|
66
|
-
(!p.label || p.label === trace.label)
|
|
184
|
+
(!p.label || p.label === trace.label) &&
|
|
185
|
+
(!p.where || satisfiesPredicate(p.where, trace)));
|
|
186
|
+
}
|
|
187
|
+
/** Passt das Pattern auf das Typ-Paar (Kantentyp, Label) — OHNE das `where` zu befragen? */
|
|
188
|
+
function matchesShape(p, trace) {
|
|
189
|
+
return (p.source === '*' || p.source === trace.source) &&
|
|
190
|
+
(p.target === '*' || p.target === trace.target) &&
|
|
191
|
+
p.type === trace.type &&
|
|
192
|
+
(!p.label || p.label === trace.label);
|
|
193
|
+
}
|
|
194
|
+
/** `undefined`, wenn die Kante gueltig ist — sonst der Grund. */
|
|
195
|
+
export function traceRejection(trace, patterns = TRACE_PATTERNS) {
|
|
196
|
+
if (isValidTrace(trace, patterns))
|
|
197
|
+
return undefined;
|
|
198
|
+
// Nur die Patterns, die am Typ-Paar ueberhaupt greifen. Gibt es keins, war es die Matrix.
|
|
199
|
+
// Gibt es mehrere (kaeme durch ein zweites `where` am selben Paar), entscheidet das erste —
|
|
200
|
+
// sie haben es alle abgelehnt, und der erste Grund ist so wahr wie jeder andere.
|
|
201
|
+
const candidate = patterns.find(p => matchesShape(p, trace) && p.where);
|
|
202
|
+
if (!candidate?.where)
|
|
203
|
+
return { reason: 'no-pattern' };
|
|
204
|
+
const declared = normalizeReqKinds(candidate.where.on === 'source' ? trace.sourceKinds : trace.targetKinds);
|
|
205
|
+
return declared.length === 0
|
|
206
|
+
? { reason: 'kinds-undeclared', pattern: candidate }
|
|
207
|
+
: { reason: 'kinds-mismatch', pattern: candidate, declared };
|
|
67
208
|
}
|
|
@@ -18,7 +18,7 @@ import type { MetricPolicy } from './policy.js';
|
|
|
18
18
|
export declare function mt01Instability(graph: OntologyGraph, policy: MetricPolicy): RuleViolation[];
|
|
19
19
|
/**
|
|
20
20
|
* MT-02: LCOM4 (Lack of Cohesion — component count).
|
|
21
|
-
* MOD with allocated FUNCs that share no common io
|
|
21
|
+
* MOD with allocated FUNCs that share no common io targets → cohesion problem.
|
|
22
22
|
* `policy.lcom4.info` ≤ LCOM4 < `policy.lcom4.warning` → info, darüber warning.
|
|
23
23
|
*
|
|
24
24
|
* CR-SM-233: die Stufen sind Eingabe, `null` heißt messen statt urteilen.
|
|
@@ -85,6 +85,25 @@ export interface ModuleMetrics {
|
|
|
85
85
|
external: number;
|
|
86
86
|
ratio: number;
|
|
87
87
|
} | null;
|
|
88
|
+
/**
|
|
89
|
+
* CR-SM-301 — Martins *Stable Dependencies Principle*, als MESSUNG und ausdruecklich als
|
|
90
|
+
* KEINE Regel: die Zahl der Vertraege, die dieses Modul von einem WENIGER stabilen Lieferanten
|
|
91
|
+
* bezieht (`I_Lieferant > I_Bezieher`). Abhaengigkeiten „bergauf".
|
|
92
|
+
*
|
|
93
|
+
* Warum keine Regel: CR-SM-298 hat genau das versucht und ist an Gate 7 gescheitert. Ueber
|
|
94
|
+
* 19 Familiengraphen laufen 25 von 250 Abhaengigkeiten bergauf, aber der MEDIAN der
|
|
95
|
+
* Stabilitaetsluecke ist **0.10** — bei einer Vertragsmasse von im Mittel 5 ist das ein
|
|
96
|
+
* Vertrag Unterschied, also Rauschen. Filtert man auf Luecke > 0.2 und beide Enden >= 3
|
|
97
|
+
* Vertraege, bleiben 3 von 25, davon zwei in einem Wegwerf-Trial und einer Demo. Eine Regel
|
|
98
|
+
* haette zwei erfundene Schwellen gebraucht, um EINEN Befund zu produzieren.
|
|
99
|
+
*
|
|
100
|
+
* Was bleibt, ist die Zahl. `graph_metrics` traegt sie in jeder Modulzeile, ein Mensch liest
|
|
101
|
+
* sie neben `instability` und deutet sie — Praezedenz MT-03 (CR-SM-223) und `cohesion` eine
|
|
102
|
+
* Zeile weiter oben: messen, nicht urteilen, ohne Katalogeintrag und ohne Nenner-Anteil.
|
|
103
|
+
*
|
|
104
|
+
* `null`, wo `instability` es auch ist — ohne Kopplung gibt es keine Richtung.
|
|
105
|
+
*/
|
|
106
|
+
uphillDependencies: number | null;
|
|
88
107
|
}
|
|
89
108
|
/**
|
|
90
109
|
* Per-module architecture metrics as NUMBERS — one row per MOD, threshold or not
|
package/dist/se/metric-rules.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { decomposedFuncs } from './rules.js';
|
|
2
2
|
import { indexOf } from './graph-index.js';
|
|
3
|
+
import { moduleCrossings } from './module-crossings.js';
|
|
4
|
+
/** Geteilte leere Menge — spart je Modul zwei Allokationen in `measureModulesUncached`. */
|
|
5
|
+
const EMPTY_SET = new Set();
|
|
3
6
|
/**
|
|
4
7
|
* MT-01: Module Instability (CR-165: indirect via allocate-path).
|
|
5
8
|
* I = fan_out / (fan_in + fan_out) > `policy.instability` → warning.
|
|
@@ -27,6 +30,10 @@ export function mt01Instability(graph, policy) {
|
|
|
27
30
|
severity: 'warning',
|
|
28
31
|
element_id: m.moduleId,
|
|
29
32
|
message: `${m.moduleName} has instability ${Math.round(m.instability * 100)}% (>${threshold * 100}%). fan_in=${m.fanIn}, fan_out=${m.fanOut}`,
|
|
33
|
+
// CR-SM-288: `> threshold` ist exklusiv, also liegt `threshold` selbst noch im Budget.
|
|
34
|
+
// Die Masse ist hier ein Bruch, kein Zaehler — sie wird nie mit der einer anderen Stufe
|
|
35
|
+
// verrechnet (CR-SM-287 vergleicht lexikographisch, nie summierend).
|
|
36
|
+
context: { value: m.instability, threshold },
|
|
30
37
|
});
|
|
31
38
|
}
|
|
32
39
|
}
|
|
@@ -34,7 +41,7 @@ export function mt01Instability(graph, policy) {
|
|
|
34
41
|
}
|
|
35
42
|
/**
|
|
36
43
|
* MT-02: LCOM4 (Lack of Cohesion — component count).
|
|
37
|
-
* MOD with allocated FUNCs that share no common io
|
|
44
|
+
* MOD with allocated FUNCs that share no common io targets → cohesion problem.
|
|
38
45
|
* `policy.lcom4.info` ≤ LCOM4 < `policy.lcom4.warning` → info, darüber warning.
|
|
39
46
|
*
|
|
40
47
|
* CR-SM-233: die Stufen sind Eingabe, `null` heißt messen statt urteilen.
|
|
@@ -49,18 +56,22 @@ export function mt02Lcom4(graph, policy) {
|
|
|
49
56
|
if (m.lcom4 === null)
|
|
50
57
|
continue;
|
|
51
58
|
// CR-SM-263: ein MOD aus lauter zerlegten Bloecken ist per Konstruktion zerfallen —
|
|
52
|
-
// LCOM4 verbindet ueber geteilte io
|
|
59
|
+
// LCOM4 verbindet ueber geteilte io-ZIELE, und CR-SM-256 hat festgehalten, dass
|
|
53
60
|
// ein Rollup genau die nicht traegt. Die Regel war dort nicht streng, sondern
|
|
54
61
|
// unerfuellbar: gruen wird sie erst mit der Kante, die CR-SM-256 fuer falsch erklaert.
|
|
55
62
|
// Die MESSUNG bleibt (`moduleMetrics` liefert `lcom4` unveraendert) — nur das Urteil faellt.
|
|
56
63
|
if (m.rollupContainer)
|
|
57
64
|
continue;
|
|
58
65
|
const message = `${m.moduleName} has LCOM4=${m.lcom4} (${m.allocatedFuncs} FUNCs in ${m.lcom4} disconnected groups)`;
|
|
66
|
+
// CR-SM-288: EIN Budget fuer beide Stufen — `steps.warning - 1` ist der groesste LCOM4,
|
|
67
|
+
// der noch drin liegt. Die `info`-Stufe meldet frueher, aber sie meldet keine Ueberschreitung;
|
|
68
|
+
// ihre Masse ist 0.
|
|
69
|
+
const context = { value: m.lcom4, threshold: steps.warning - 1 };
|
|
59
70
|
if (m.lcom4 >= steps.info && m.lcom4 < steps.warning) {
|
|
60
|
-
violations.push({ rule_id: 'MT-02', severity: 'info', element_id: m.moduleId, message });
|
|
71
|
+
violations.push({ rule_id: 'MT-02', severity: 'info', element_id: m.moduleId, message, context });
|
|
61
72
|
}
|
|
62
73
|
else if (m.lcom4 >= steps.warning) {
|
|
63
|
-
violations.push({ rule_id: 'MT-02', severity: 'warning', element_id: m.moduleId, message });
|
|
74
|
+
violations.push({ rule_id: 'MT-02', severity: 'warning', element_id: m.moduleId, message, context });
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
return violations;
|
|
@@ -157,58 +168,37 @@ function measureModulesUncached(graph) {
|
|
|
157
168
|
// Eine zweite Definition hier waere ein zweiter Weg zu derselben Aussage (Gate 2).
|
|
158
169
|
const decomposed = decomposedFuncs(graph);
|
|
159
170
|
const rows = [];
|
|
160
|
-
// --- CR-SM-
|
|
171
|
+
// --- CR-SM-293: fan_in/fan_out sind KOPPLUNG, und Kopplung ist der Vertrag am Rand ---
|
|
161
172
|
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// lesen. Der Index loest sie nicht auf: die Frage ist nicht "welche Kanten haengen an diesem
|
|
165
|
-
// Knoten", sondern "welche Kanten kreuzen diese Modulgrenze".
|
|
173
|
+
// Bis hierher zaehlte die Rechnung JEDE Kante, deren eines Ende einem Modul gehoerte. Ueber
|
|
174
|
+
// 19 Familiengraphen waren das 3281 Kanten, von denen nur 1117 (34 %) Kopplung sind:
|
|
166
175
|
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
const indirectOutOf = new Map();
|
|
189
|
-
const indirectInOf = new Map();
|
|
190
|
-
const bump = (m, key) => { m.set(key, (m.get(key) ?? 0) + 1); };
|
|
191
|
-
for (const t of graph.traces) {
|
|
192
|
-
if (t.type === 'allocate')
|
|
193
|
-
continue; // allocate itself doesn't count as coupling
|
|
194
|
-
for (const owner of ownersOf.get(t.source) ?? []) {
|
|
195
|
-
if (!membersOf.get(owner).has(t.target) && t.target !== owner)
|
|
196
|
-
bump(indirectOutOf, owner);
|
|
197
|
-
}
|
|
198
|
-
for (const owner of ownersOf.get(t.target) ?? []) {
|
|
199
|
-
if (!membersOf.get(owner).has(t.source) && t.source !== owner)
|
|
200
|
-
bump(indirectInOf, owner);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
176
|
+
// fan_in 2164 = 674 `allocate` (das ist die MODULGROESSE) + 610 `FLOW -io-> FUNC`
|
|
177
|
+
// + 348 `FCHAIN -compose-> FUNC` + 255 `CR -relation-> FUNC` + 277 Rest
|
|
178
|
+
// fan_out 1117 = 507 `FUNC -io-> FLOW` + 501 `FUNC -satisfy-> REQ` + 109 Rest
|
|
179
|
+
//
|
|
180
|
+
// Drei Befunde, jeder fuer sich hinreichend: ein Modul wurde STABILER, indem es Funktionen
|
|
181
|
+
// bekam; fast die halbe fan_out war `satisfy`, also Spezifikation und keine Laufzeitkopplung
|
|
182
|
+
// (dieselbe Begruendung, mit der CR-SM-297 sie aus LCOM4 genommen hat); und 70 Kanten liefen
|
|
183
|
+
// ueber `MOD -io-> MOD`, ein Muster, das CR-SM-266 D2 aus TRACE_PATTERNS geloescht hat.
|
|
184
|
+
//
|
|
185
|
+
// Jetzt liest MT-01 `moduleCrossings` — DIESELBE Definition von Rand und dieselbe Zaehlbasis
|
|
186
|
+
// (verschiedene Vertraege statt Einzelquerungen), die CR-01, R-04 und BW-02 seit CR-SM-274/276
|
|
187
|
+
// benutzen. Eine zweite Vorstellung davon, was eine Kreuzung ist, war genau der Defekt, den
|
|
188
|
+
// jener CR beseitigt hat; MT-01 hatte bis hierher noch eine dritte.
|
|
189
|
+
//
|
|
190
|
+
// RICHTUNG (CR-SM-293, Entscheidung des Auftraggebers): der KONSUMENT haengt ab. Wer nach
|
|
191
|
+
// draussen liefert, wird gebraucht -> `afferentContracts` -> fan_in. Wer von draussen bezieht,
|
|
192
|
+
// haengt ab -> `efferentContracts` -> fan_out. Das ist Martins Ca/Ce und kehrt das bisherige
|
|
193
|
+
// Urteil an reinen Verbrauchermodulen um: sie lasen `I = 0` (maximal stabil) und muessen `1`
|
|
194
|
+
// lesen. Die Konvention stand vorher NIRGENDS im Repo — sie war nie entschieden, nur codiert.
|
|
195
|
+
const crossings = moduleCrossings(graph);
|
|
203
196
|
for (const mod of mods) {
|
|
204
197
|
const allocated = idx.in(mod.id, 'allocate').map(t => t.source);
|
|
205
198
|
const modFuncIds = new Set(allocated);
|
|
206
|
-
// --- MT-01: fan-in / fan-out
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
const directIn = idx.in(mod.id, 'io').length + idx.in(mod.id, 'compose').length + idx.in(mod.id, 'allocate').length;
|
|
210
|
-
const fanOut = directOut + (indirectOutOf.get(mod.id) ?? 0);
|
|
211
|
-
const fanIn = directIn + (indirectInOf.get(mod.id) ?? 0);
|
|
199
|
+
// --- MT-01: fan-in / fan-out als querende VERTRAEGE je Richtung (CR-SM-293) ---
|
|
200
|
+
const fanIn = (crossings.afferentContracts.get(mod.id) ?? EMPTY_SET).size;
|
|
201
|
+
const fanOut = (crossings.efferentContracts.get(mod.id) ?? EMPTY_SET).size;
|
|
212
202
|
const instability = fanIn + fanOut === 0 ? null : fanOut / (fanIn + fanOut);
|
|
213
203
|
rows.push({
|
|
214
204
|
moduleId: mod.id,
|
|
@@ -220,28 +210,77 @@ function measureModulesUncached(graph) {
|
|
|
220
210
|
lcom4: lcom4Of(graph, allocated),
|
|
221
211
|
rollupContainer: allocated.length >= 2 && allocated.every(f => decomposed.has(f)),
|
|
222
212
|
cohesion: cohesionOf(pairs, modFuncIds),
|
|
213
|
+
// CR-SM-301: braucht die Instabilitaet ALLER Module, also zweiter Durchgang unten.
|
|
214
|
+
uphillDependencies: null,
|
|
223
215
|
});
|
|
224
216
|
}
|
|
217
|
+
// --- CR-SM-301: Abhaengigkeiten „bergauf" (Martin, Stable Dependencies Principle) ---
|
|
218
|
+
//
|
|
219
|
+
// Ein Vertrag, den `m` von draussen bezieht, laeuft bergauf, wenn sein Lieferant WENIGER
|
|
220
|
+
// stabil ist als `m` selbst. Lieferant eines Vertrags ist jedes Modul, das ihn ueber seinen
|
|
221
|
+
// Rand LIEFERT (`afferentContracts`) — dieselbe Menge, aus der oben `fanIn` kommt, also
|
|
222
|
+
// dieselbe Zaehlbasis und dieselbe Besitzkette. Kein zweiter Kopplungsbegriff.
|
|
223
|
+
//
|
|
224
|
+
// Gezaehlt wird JE VERTRAG, nicht je Lieferant-Vertrag-Paar: ein Vertrag mit drei Lieferanten
|
|
225
|
+
// ist EINE Abhaengigkeit, von denen einer bergauf liegt. Andernfalls waere die Zahl
|
|
226
|
+
// hub-empfindlich — dieselbe Begruendung, mit der CR-SM-274 die Vertraege statt der
|
|
227
|
+
// Einzelquerungen zaehlt.
|
|
228
|
+
const suppliersOf = new Map();
|
|
229
|
+
for (const [mod, set] of crossings.afferentContracts) {
|
|
230
|
+
for (const c of set) {
|
|
231
|
+
const list = suppliersOf.get(c);
|
|
232
|
+
if (list)
|
|
233
|
+
list.push(mod);
|
|
234
|
+
else
|
|
235
|
+
suppliersOf.set(c, [mod]);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const instabilityOf = new Map(rows.map(r => [r.moduleId, r.instability]));
|
|
239
|
+
for (const row of rows) {
|
|
240
|
+
const mine = row.instability;
|
|
241
|
+
if (mine === null)
|
|
242
|
+
continue; // ohne Kopplung keine Richtung — `null` bleibt stehen
|
|
243
|
+
let uphill = 0;
|
|
244
|
+
for (const contract of crossings.efferentContracts.get(row.moduleId) ?? EMPTY_SET) {
|
|
245
|
+
for (const supplier of suppliersOf.get(contract) ?? []) {
|
|
246
|
+
if (supplier === row.moduleId)
|
|
247
|
+
continue;
|
|
248
|
+
const theirs = instabilityOf.get(supplier);
|
|
249
|
+
if (theirs !== null && theirs !== undefined && theirs > mine) {
|
|
250
|
+
uphill += 1;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
row.uphillDependencies = uphill;
|
|
256
|
+
}
|
|
225
257
|
return rows;
|
|
226
258
|
}
|
|
227
259
|
/** MT-02 core: connected components over the allocated FUNCs. null below 2 FUNCs. */
|
|
228
260
|
function lcom4Of(graph, funcIds) {
|
|
229
261
|
if (funcIds.length < 2)
|
|
230
262
|
return null;
|
|
231
|
-
//
|
|
263
|
+
// Zwei FUNCs sind verbunden, wenn sie ein gemeinsames `io`-Ziel haben — und NUR dann.
|
|
264
|
+
//
|
|
265
|
+
// CR-SM-297: `satisfy` ist aus der Verbindung gefallen. LCOM4 misst Datenkopplung; `satisfy`
|
|
266
|
+
// ist eine SPEZIFIKATIONS-Beziehung, und zwei FUNCs, die dasselbe REQ erfuellen, koennen zur
|
|
267
|
+
// Laufzeit vollstaendig entkoppelt sein. Die Wirkung ging dabei nur in eine Richtung:
|
|
268
|
+
// `satisfy` VERBINDET Gruppen, senkte also LCOM4 und liess Module kohaesiver aussehen, als
|
|
269
|
+
// ihr Datenfluss hergibt.
|
|
270
|
+
//
|
|
271
|
+
// Der Modellierungsgrund wiegt schwerer als der Messgrund: teilen sich zwei Funktionen ein
|
|
272
|
+
// Requirement, ist das REQUIREMENT zu zerlegen — nicht die Kohaesionsmessung zu beschoenigen.
|
|
273
|
+
// RD-02 sagt bereits das Verwandte (ein Parent-REQ mit direktem FUNC-satisfy; der satisfy
|
|
274
|
+
// gehoert an die Kinder).
|
|
275
|
+
//
|
|
232
276
|
// CR-SM-264: die ausgehenden Kanten kommen aus dem Index. Vorher lief je alloziertem FUNC
|
|
233
|
-
// ein Vollscan ueber alle Traces — MOD x FUNC x T.
|
|
234
|
-
// bleibt identisch (io vor satisfy war sie nicht; der Index liefert beide Listen in
|
|
235
|
-
// Graph-Reihenfolge, und `targets` ist ein Set, dessen Inhalt hier nur auf Mitgliedschaft
|
|
236
|
-
// geprueft wird).
|
|
277
|
+
// ein Vollscan ueber alle Traces — MOD x FUNC x T.
|
|
237
278
|
const idx = indexOf(graph);
|
|
238
279
|
const funcTargets = new Map();
|
|
239
280
|
for (const fid of funcIds) {
|
|
240
281
|
const targets = new Set();
|
|
241
282
|
for (const t of idx.out(fid, 'io'))
|
|
242
283
|
targets.add(t.target);
|
|
243
|
-
for (const t of idx.out(fid, 'satisfy'))
|
|
244
|
-
targets.add(t.target);
|
|
245
284
|
funcTargets.set(fid, targets);
|
|
246
285
|
}
|
|
247
286
|
const parent = new Map();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-SM-276 — EIN Begriff „kreuzender Fluss", EINE Berechnung.
|
|
3
|
+
*
|
|
4
|
+
* Zwei Katalogeintraege messen Modulrand-Kopplung: **CR-01** je Modul*paar* und **R-04** je
|
|
5
|
+
* Modul (Groesse GEGEN Kreuzungen). Bis CR-SM-274/276 hatte jeder seine eigene, jeweils
|
|
6
|
+
* falsche Vorstellung davon, was eine Kreuzung ist:
|
|
7
|
+
*
|
|
8
|
+
* - CR-01 suchte `FUNC -io-> FUNC` — ein Paar, das `TRACE_PATTERNS` nicht kennt (R-18 lehnt es
|
|
9
|
+
* ab). Die Regel konnte strukturell nicht feuern (CR-SM-274).
|
|
10
|
+
* - R-04 zaehlte „jede io-Kante einer Modul-FUNC mit einem Endpunkt ausserhalb der FUNC-Menge".
|
|
11
|
+
* FLOWs liegen NIE in der FUNC-Menge — also zaehlte jede io-Kante zu jedem FLOW, auch dem
|
|
12
|
+
* modul*internen*. Das war der io-Grad des Moduls, nicht seine Randkopplung: fuer jedes Modul
|
|
13
|
+
* mit irgendeinem Datenfluss trivial > 2, womit R-04 faktisch wieder „max module size" war —
|
|
14
|
+
* genau der Name, den CR-SM-236 verworfen hat.
|
|
15
|
+
*
|
|
16
|
+
* Hier steht der Pfad, den beide Regelkoepfe meinen: **`FUNC -io-> FLOW -io-> FUNC` ueber eine
|
|
17
|
+
* Modulgrenze**. MOD-Zugehoerigkeit einer FUNC = **direkte** `allocate`-Kante (`FUNC -allocate->
|
|
18
|
+
* MOD` ist `0..1`) — dieselbe Aufloesung wie `moduleMetrics` und die RC-05-Adjazenz.
|
|
19
|
+
*
|
|
20
|
+
* ## CR-SM-282: `byModule` rollt auf, `pairs` nicht
|
|
21
|
+
*
|
|
22
|
+
* Bis hierher las AUCH `byModule` nur die direkte Zuordnung. Ein Eltern-MOD (`MOD -compose-> MOD`)
|
|
23
|
+
* hat keine direkt allozierten FUNCs — es kam in `byModule` also gar nicht vor, und R-04 sah an ihm
|
|
24
|
+
* **null Kreuzungen**. Genau die Whitebox, in die der Leser hineinklickt, war unsichtbar. Jetzt ist
|
|
25
|
+
* die Zugehoerigkeit die **Besitzkette**: das direkt allozierte MOD plus seine compose-Vorfahren.
|
|
26
|
+
* Ein Vertrag quert den Rand von `m`, wenn ein Endpunkt in `m`s Teilbaum liegt und einer nicht.
|
|
27
|
+
*
|
|
28
|
+
* `pairs` (CR-01) bleibt bewusst auf der direkten Zuordnung: „wie stark haengen DIESE zwei Module
|
|
29
|
+
* aneinander" ist eine Frage der Blattebene; ein Elternpaar meldete dieselbe Kopplung ein zweites
|
|
30
|
+
* Mal. Ohne `MOD -compose-> MOD` im Graphen ist das Ergebnis Zeichen fuer Zeichen das bisherige.
|
|
31
|
+
*
|
|
32
|
+
* ## Zaehlbasis: verschiedene SCHEMA, nicht Einzelquerungen
|
|
33
|
+
*
|
|
34
|
+
* Begruendung in CR-SM-274 (dort familienweit gemessen): Kopplung ist die Zahl der **Vertraege**,
|
|
35
|
+
* auf die sich zwei Module einigen muessen — ein FLOW mit 3 Produzenten und 3 Konsumenten ist
|
|
36
|
+
* EIN Vertrag, nicht 9. Roh ist hub-empfindlich und durch FLOW-Splitting manipulierbar;
|
|
37
|
+
* `FLOW -relation-> SCHEMA` ist seit CR-SM-271 Teil 2 `1..1` und per R-18 erzwungen, die
|
|
38
|
+
* Zaehlbasis also garantiert vorhanden. Ein FLOW ohne SCHEMA zaehlt als eigener, untypisierter
|
|
39
|
+
* Vertrag (`UNBOUND:<flow>`) — er darf nicht stillschweigend mit anderen verschmelzen.
|
|
40
|
+
*
|
|
41
|
+
* ## Warum `byModule` die VEREINIGUNG ist und nicht die Summe ueber die Paare
|
|
42
|
+
*
|
|
43
|
+
* R-04 meldet an EINEM MOD. Quert derselbe Vertrag zu drei Nachbarn, besitzt das Modul trotzdem
|
|
44
|
+
* einen Vertrag — die Summe ueber die Paare brächte die Hub-Empfindlichkeit zurueck, die die
|
|
45
|
+
* distinct-Zaehlung gerade beseitigt, und zaehlte doppelt, was CR-01 je Paar ohnehin meldet.
|
|
46
|
+
* `byModule` ist damit die **Vertragsflaeche** des Modulrandes.
|
|
47
|
+
*/
|
|
48
|
+
import type { OntologyGraph } from './ontology.js';
|
|
49
|
+
export interface ModulePairCrossing {
|
|
50
|
+
readonly modA: string;
|
|
51
|
+
readonly modB: string;
|
|
52
|
+
/** SCHEMA-ids bzw. `UNBOUND:<flow>` — die verschiedenen Vertraege an dieser Grenze. */
|
|
53
|
+
readonly contracts: ReadonlySet<string>;
|
|
54
|
+
}
|
|
55
|
+
export interface ModuleCrossings {
|
|
56
|
+
/** Je sortiertem Modulpaar (Schluessel `modA::modB`) die querenden Vertraege. */
|
|
57
|
+
readonly pairs: ReadonlyMap<string, ModulePairCrossing>;
|
|
58
|
+
/** Je MOD die Vereinigung aller Vertraege, die seine Grenze queren. */
|
|
59
|
+
readonly byModule: ReadonlyMap<string, ReadonlySet<string>>;
|
|
60
|
+
/**
|
|
61
|
+
* CR-SM-283: je ZERLEGTER FUNC die Vertraege, die den Rand seiner Whitebox queren —
|
|
62
|
+
* dieselbe Definition und derselbe Durchlauf wie `byModule`, nur ist die Innen-Menge der
|
|
63
|
+
* `compose`-Teilbaum statt des Modul-Teilbaums. Ein Blatt-FUNC steht NICHT drin: es ist
|
|
64
|
+
* keine Whitebox, seine io-Kanten sind kein Rand.
|
|
65
|
+
*
|
|
66
|
+
* Der Rollup ist hier nicht Genauigkeit, sondern Existenz: ein zerlegter FUNC traegt selbst
|
|
67
|
+
* keine io-Kanten (die liegen an seinen Blaettern), ohne Teilbaum-Aufloesung waere die Zahl
|
|
68
|
+
* strukturell immer 0. Gemessen: `FUNC-block-grounding` 0 -> 19 (Spike CR-SM-282).
|
|
69
|
+
*/
|
|
70
|
+
readonly byFunc: ReadonlyMap<string, ReadonlySet<string>>;
|
|
71
|
+
/**
|
|
72
|
+
* CR-SM-282: je MOD die FUNCs seines TEILBAUMS — direkt alloziert plus die seiner
|
|
73
|
+
* compose-Sub-MODs. Fuer ein Blatt-MOD identisch zur direkten Allokation; fuer ein
|
|
74
|
+
* Eltern-MOD ist es die Groesse, die R-04 gegen die Kreuzungen abwaegt. Ohne diese
|
|
75
|
+
* Zahl war ein Eltern-MOD fuer R-04 leer und die Regel schwieg per `funcCount <= coupled`.
|
|
76
|
+
*/
|
|
77
|
+
readonly funcsByModule: ReadonlyMap<string, ReadonlySet<string>>;
|
|
78
|
+
/**
|
|
79
|
+
* CR-SM-293: dieselbe Grenze, jetzt mit RICHTUNG — Martins Ca/Ce, gerechnet im selben
|
|
80
|
+
* Durchlauf und auf derselben Zaehlbasis (verschiedene Vertraege) wie `pairs`/`byModule`.
|
|
81
|
+
*
|
|
82
|
+
* `afferentContracts` = Vertraege, die dieses Modul ueber seinen Rand LIEFERT: andere
|
|
83
|
+
* haengen von ihm ab. `efferentContracts` = Vertraege, die es von draussen BEZIEHT: es
|
|
84
|
+
* haengt von anderen ab. Ein Vertrag, dessen Produzent und Konsument beide im Teilbaum
|
|
85
|
+
* liegen, steht in keiner der beiden Mengen — er quert den Rand nicht.
|
|
86
|
+
*
|
|
87
|
+
* Die Richtung ist die Entscheidung, die bis hierher nirgends im Repo stand: der
|
|
88
|
+
* KONSUMENT haengt vom Vertrag des Produzenten ab, nicht umgekehrt. `moduleMetrics`
|
|
89
|
+
* las vorher „Kante zeigt weg = Abhaengigkeit" und drehte damit jedes reine
|
|
90
|
+
* Verbrauchermodul auf `I = 0` (maximal stabil), wo es `1` sein muss.
|
|
91
|
+
*/
|
|
92
|
+
readonly afferentContracts: ReadonlyMap<string, ReadonlySet<string>>;
|
|
93
|
+
readonly efferentContracts: ReadonlyMap<string, ReadonlySet<string>>;
|
|
94
|
+
}
|
|
95
|
+
/** Die querenden Vertraege dieses Graphen — einmal je Graph-Objekt berechnet. */
|
|
96
|
+
export declare function moduleCrossings(graph: OntologyGraph): ModuleCrossings;
|
|
97
|
+
/** Zahl der verschiedenen Vertraege, die den Rand dieses Moduls queren. */
|
|
98
|
+
export declare function crossingContractCount(graph: OntologyGraph, modId: string): number;
|
|
99
|
+
/** Zahl der verschiedenen Vertraege, die den Rand dieser FUNC-Whitebox queren — CR-SM-283. */
|
|
100
|
+
export declare function whiteboxContractCount(graph: OntologyGraph, funcId: string): number;
|
|
101
|
+
/** Die FUNCs im Teilbaum dieses Moduls (direkt alloziert + die seiner Sub-MODs) — CR-SM-282. */
|
|
102
|
+
export declare function subtreeFuncs(graph: OntologyGraph, modId: string): ReadonlySet<string>;
|