@sigloch/contracts 11.0.0 → 12.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/analysis-freshness-rules.d.ts +0 -27
- package/dist/se/analysis-freshness-rules.js +1 -77
- package/dist/se/chain-metrics.d.ts +116 -0
- package/dist/se/chain-metrics.js +311 -0
- package/dist/se/evaluate-all.d.ts +2 -3
- package/dist/se/evaluate-all.js +1 -3
- package/dist/se/grammar-snapshot.d.ts +4 -4
- package/dist/se/grammar-snapshot.js +4 -17
- package/dist/se/index.d.ts +2 -1
- package/dist/se/index.js +3 -1
- package/dist/se/metric-rules.d.ts +6 -5
- package/dist/se/metric-rules.js +7 -6
- package/dist/se/module-crossings.d.ts +10 -0
- package/dist/se/module-crossings.js +3 -1
- package/dist/se/ontology.d.ts +3 -6
- package/dist/se/ontology.js +4 -23
- package/dist/se/readiness.d.ts +4 -110
- package/dist/se/readiness.js +4 -171
- package/dist/se/rule-due.d.ts +5 -14
- package/dist/se/rule-due.js +7 -18
- package/dist/se/rule-help.d.ts +1 -1
- package/dist/se/rule-help.js +2 -16
- package/dist/se/rules.d.ts +5 -13
- package/dist/se/similarity.js +4 -10
- package/package.json +1 -1
|
@@ -66,33 +66,6 @@ export declare const AF_RULES: readonly [{
|
|
|
66
66
|
readonly evaluate: typeof buildPlanDue;
|
|
67
67
|
readonly domain: readonly ["graph"];
|
|
68
68
|
}];
|
|
69
|
-
/** TR-01: a stamped trade study names >=1 existing CR, each carrying a relation(label: decides) edge. */
|
|
70
|
-
export declare function tradeDecisionRecorded(graph: OntologyGraph): RuleViolation[];
|
|
71
|
-
/**
|
|
72
|
-
* IR-01 (CR-SM-397): every REQ named by a stamped assumption review exists; an empty list is a
|
|
73
|
-
* legitimate outcome. That the REQ carries a verifying TEST is R-01's finding, not repeated here.
|
|
74
|
-
*/
|
|
75
|
-
export declare function assumptionReviewRequirementsExist(graph: OntologyGraph): RuleViolation[];
|
|
76
|
-
/** CR-SM-355: the outcome rules of trade / irr — silent until the analysis is stamped. */
|
|
77
|
-
export declare const TASK_OUTCOME_RULES: readonly [{
|
|
78
|
-
readonly id: "TR-01";
|
|
79
|
-
readonly name: "Trade decision recorded as CR";
|
|
80
|
-
readonly severity: "warning";
|
|
81
|
-
readonly stage: 7;
|
|
82
|
-
readonly role: "analysis";
|
|
83
|
-
readonly evaluate: typeof tradeDecisionRecorded;
|
|
84
|
-
readonly domain: readonly ["graph"];
|
|
85
|
-
}, {
|
|
86
|
-
readonly id: "IR-01";
|
|
87
|
-
readonly name: "Assumption review requirements exist";
|
|
88
|
-
readonly severity: "warning";
|
|
89
|
-
readonly stage: 7;
|
|
90
|
-
readonly role: "analysis";
|
|
91
|
-
readonly evaluate: typeof assumptionReviewRequirementsExist;
|
|
92
|
-
readonly domain: readonly ["graph"];
|
|
93
|
-
}];
|
|
94
|
-
/** Evaluate the task-outcome rules (CR-SM-355). */
|
|
95
|
-
export declare function evaluateTaskOutcomeRules(graph: OntologyGraph): RuleViolation[];
|
|
96
69
|
/** Evaluate all analysis-freshness-presence rules. */
|
|
97
70
|
export declare function evaluateAFRules(graph: OntologyGraph): RuleViolation[];
|
|
98
71
|
export {};
|
|
@@ -30,7 +30,7 @@ const ANALYSIS_WORK = {
|
|
|
30
30
|
},
|
|
31
31
|
trade: {
|
|
32
32
|
skill: 'se-trade',
|
|
33
|
-
firstStep: 'name the open decision with its options and criteria, then
|
|
33
|
+
firstStep: 'name the open decision with its options and criteria, then apply the chosen option to the model and record the decision as a done CR with relation edges to what was decided',
|
|
34
34
|
},
|
|
35
35
|
'assumption-review': {
|
|
36
36
|
skill: 'se-irr',
|
|
@@ -108,82 +108,6 @@ export const AF_RULES = [
|
|
|
108
108
|
{ id: 'AF-04', name: 'FMEA freshness stamp present', severity: 'warning', stage: 8, role: 'analysis', evaluate: fmeaFreshnessPresent, domain: ['graph'] },
|
|
109
109
|
{ id: 'AF-05', name: 'Unbuilt elements have an open CR', severity: 'warning', stage: 10, role: 'existence', evaluate: buildPlanDue, domain: ['graph'] },
|
|
110
110
|
];
|
|
111
|
-
/**
|
|
112
|
-
* Task-Ausgangsregeln fuer trade und irr (CR-SM-355, CR-SM-397): was der Task hinterlassen hat, ist
|
|
113
|
-
* im Frischestempel genannt und existiert. Ein Variantenvergleich hinterlaesst Entscheidungs-CRs
|
|
114
|
-
* (`crRefs`, TR-01), ein Annahmen-Review Anforderungen (`reqRefs`, IR-01) — eine Analyse legt keinen
|
|
115
|
-
* Bauauftrag an. Ohne Stempel feuern die Regeln nicht (dann feuert der Eintritt AF-02/AF-03).
|
|
116
|
-
* `label` wird als `Trace.label` gelesen wie bei MS-02 — `decides` ist kein TraceType, sondern das
|
|
117
|
-
* SP-1-Label einer relation-Kante.
|
|
118
|
-
*/
|
|
119
|
-
function stampedOutcome(graph, artifactId) {
|
|
120
|
-
const sys = graph.elements.find(e => e.type === 'SYS');
|
|
121
|
-
if (!sys)
|
|
122
|
-
return null;
|
|
123
|
-
const stamp = readAnalysisStamp(graph, artifactId);
|
|
124
|
-
if (stamp.state !== 'bound')
|
|
125
|
-
return null;
|
|
126
|
-
return { sys, stamp: stamp.value };
|
|
127
|
-
}
|
|
128
|
-
/** TR-01: a stamped trade study names >=1 existing CR, each carrying a relation(label: decides) edge. */
|
|
129
|
-
export function tradeDecisionRecorded(graph) {
|
|
130
|
-
const stamped = stampedOutcome(graph, 'trade');
|
|
131
|
-
if (!stamped)
|
|
132
|
-
return [];
|
|
133
|
-
const { sys } = stamped;
|
|
134
|
-
const crRefs = stamped.stamp.crRefs ?? [];
|
|
135
|
-
const base = { rule_id: 'TR-01', severity: 'warning', context: { element_type: sys.type, element_name: sys.name } };
|
|
136
|
-
if (crRefs.length === 0) {
|
|
137
|
-
return [{
|
|
138
|
-
...base,
|
|
139
|
-
element_id: sys.id,
|
|
140
|
-
message: 'Trade Study is stamped but names no decision CR (analysisFreshness.trade.crRefs is empty) — a trade without a recorded decision is a list, not a trade',
|
|
141
|
-
fix_hint: 'Record the decision as a CR with relation(label: decides) edges and list its id in analysisFreshness.trade.crRefs (se-trade)',
|
|
142
|
-
}];
|
|
143
|
-
}
|
|
144
|
-
const byId = new Map(graph.elements.map(e => [e.id, e]));
|
|
145
|
-
const decidesFrom = new Set(graph.traces.filter(t => t.type === 'relation' && t.label === 'decides').map(t => t.source));
|
|
146
|
-
return crRefs.flatMap(id => {
|
|
147
|
-
const cr = byId.get(id);
|
|
148
|
-
if (!cr || cr.type !== 'CR') {
|
|
149
|
-
return [{ ...base, element_id: sys.id, message: `Trade Study names ${id} as its decision, but no CR with that id exists`, fix_hint: 'Point analysisFreshness.trade.crRefs at an existing CR, or add the CR through graph_mutate (se-trade)' }];
|
|
150
|
-
}
|
|
151
|
-
if (!decidesFrom.has(id)) {
|
|
152
|
-
return [{ ...base, element_id: id, message: `${id} is named as a trade decision but carries no relation(label: decides) edge`, fix_hint: 'Add a relation edge from the CR to the element it decides, with label "decides" (se-trade)' }];
|
|
153
|
-
}
|
|
154
|
-
return [];
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* IR-01 (CR-SM-397): every REQ named by a stamped assumption review exists; an empty list is a
|
|
159
|
-
* legitimate outcome. That the REQ carries a verifying TEST is R-01's finding, not repeated here.
|
|
160
|
-
*/
|
|
161
|
-
export function assumptionReviewRequirementsExist(graph) {
|
|
162
|
-
const stamped = stampedOutcome(graph, 'assumption-review');
|
|
163
|
-
if (!stamped)
|
|
164
|
-
return [];
|
|
165
|
-
const { sys } = stamped;
|
|
166
|
-
const byId = new Map(graph.elements.map(e => [e.id, e]));
|
|
167
|
-
return (stamped.stamp.reqRefs ?? [])
|
|
168
|
-
.filter(id => byId.get(id)?.type !== 'REQ')
|
|
169
|
-
.map(id => ({
|
|
170
|
-
rule_id: 'IR-01',
|
|
171
|
-
severity: 'warning',
|
|
172
|
-
element_id: sys.id,
|
|
173
|
-
message: `Assumption Review names ${id} as the requirement of a load-bearing assumption, but no REQ with that id exists`,
|
|
174
|
-
fix_hint: 'Point analysisFreshness.assumption-review.reqRefs at an existing REQ, or add the REQ with its verifying TEST through graph_mutate (se-irr)',
|
|
175
|
-
context: { element_type: sys.type, element_name: sys.name },
|
|
176
|
-
}));
|
|
177
|
-
}
|
|
178
|
-
/** CR-SM-355: the outcome rules of trade / irr — silent until the analysis is stamped. */
|
|
179
|
-
export const TASK_OUTCOME_RULES = [
|
|
180
|
-
{ id: 'TR-01', name: 'Trade decision recorded as CR', severity: 'warning', stage: 7, role: 'analysis', evaluate: tradeDecisionRecorded, domain: ['graph'] },
|
|
181
|
-
{ id: 'IR-01', name: 'Assumption review requirements exist', severity: 'warning', stage: 7, role: 'analysis', evaluate: assumptionReviewRequirementsExist, domain: ['graph'] },
|
|
182
|
-
];
|
|
183
|
-
/** Evaluate the task-outcome rules (CR-SM-355). */
|
|
184
|
-
export function evaluateTaskOutcomeRules(graph) {
|
|
185
|
-
return TASK_OUTCOME_RULES.flatMap(r => r.evaluate(graph));
|
|
186
|
-
}
|
|
187
111
|
/** Evaluate all analysis-freshness-presence rules. */
|
|
188
112
|
export function evaluateAFRules(graph) {
|
|
189
113
|
return AF_RULES.flatMap(r => r.evaluate(graph));
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-SM-404 — Kennzahlen der Wirkkette: je FCHAIN eine Zahl, oder der Grund, warum es keine gibt.
|
|
3
|
+
*
|
|
4
|
+
* Die Leitlinie verlangt, die Realisierungsarchitektur AUF DER WIRKKETTE zu bewerten (graphcode
|
|
5
|
+
* `docs/graphcode_architektur_konzept.md`, „Kennzahlen der Wirkkette"). Bis hierher rechnete das
|
|
6
|
+
* nur ein Spike in graphcode (`scripts/spike-kettenkennzahlen.mjs`); von dort ist die Rechnung
|
|
7
|
+
* portiert, nicht neu erfunden. Gemessen 2026-10-09 ueber 10 Familie-Graphen: 76 Ketten, 31
|
|
8
|
+
* bewertbar.
|
|
9
|
+
*
|
|
10
|
+
* ## Diese Datei URTEILT NICHT
|
|
11
|
+
*
|
|
12
|
+
* Keine Regel, keine Schwelle, kein Katalogeintrag — dieselbe Trennung wie `moduleMetrics` und
|
|
13
|
+
* `functionCriticality`. Ob eine Laenge von 6 gut ist, sagt erst ein Profil.
|
|
14
|
+
*
|
|
15
|
+
* ## Nicht bewertbar heisst: KEINE Zahl
|
|
16
|
+
*
|
|
17
|
+
* Eine FCHAIN ist grammatisch eine Menge; die Reihenfolge entsteht erst aus Erzeuger ->
|
|
18
|
+
* Verbraucher (`FUNC -io-> FLOW -io-> FUNC`, beide Enden Glied). Zerfaellt die Kette, beschriebe
|
|
19
|
+
* jede Kennzahl etwas, das es nicht gibt. Eine solche Kette traegt deshalb ihre Gruende und kein
|
|
20
|
+
* einziges Zahlenfeld — das Schema laesst es nicht zu. Sie zaehlt im NENNER der Quote
|
|
21
|
+
* (`measurability`), die neben jedem Wert stehen muss wie die Bindungsquote neben RC-*.
|
|
22
|
+
*
|
|
23
|
+
* ## Wer urteilt, ob eine Kette eine Kette ist
|
|
24
|
+
*
|
|
25
|
+
* - `FC-05`: die Regel FC-05, aufgerufen, nicht nachgebaut. Ursache (fehlendes Glied, Sack) und
|
|
26
|
+
* die Nebenkomponenten stehen in ihrem Befund (`context.side_components`), nicht hier.
|
|
27
|
+
* - `loose-member`: ein Glied ohne io-Eingang oder -Ausgang. Das ist die Bedingung, unter der
|
|
28
|
+
* FC-05 sich ENTHAELT (R-31 meldet das Glied) — ohne diesen Grund haette eine solche Kette weder
|
|
29
|
+
* einen FC-05-Befund noch einen Grund und bekaeme Zahlen.
|
|
30
|
+
* - `no-entry` / `no-exit`: kein FLOW von bzw. zu einem Knoten AUSSERHALB der Kette. Weiter als
|
|
31
|
+
* FC-04, das nur einen ACTOR gelten laesst: eine Kette, die eine andere fortsetzt, ist messbar.
|
|
32
|
+
* - `empty`: keine FUNC (R-15).
|
|
33
|
+
*
|
|
34
|
+
* ## Ein Kettenbegriff, ein Modulbegriff
|
|
35
|
+
*
|
|
36
|
+
* Glied = direkte `FCHAIN -compose-> FUNC`-Kante, ueber `chainsByFunc` (R-30s Definition).
|
|
37
|
+
* Modul einer FUNC = direkte `allocate`-Kante, wie `module-crossings.ts` und `moduleMetrics`.
|
|
38
|
+
* Der Spike erbte das Modul ueber compose-Vorfahren; gemessen sind 326 von 326 Kettengliedern
|
|
39
|
+
* des Korpus direkt alloziert — die Vererbung kauft nichts und waere eine zweite Vorstellung von
|
|
40
|
+
* Zugehoerigkeit im selben Paket.
|
|
41
|
+
*
|
|
42
|
+
* ## CR-SM-406 — die ORTE je Kette, nur erkannt
|
|
43
|
+
*
|
|
44
|
+
* Neben jeder Zahl steht, WO sie herkommt: die Mitglieder je Rueckkopplung, die geteilten
|
|
45
|
+
* Funktionen, die Grenzen je gerichtetem Modulpaar mit ihren Vertraegen, dazu Zulauf, Importe und
|
|
46
|
+
* Uebergaben. Reine Zusatzfelder — die Zahlen aus CR-SM-404 bleiben und sind die Laenge ihrer
|
|
47
|
+
* Liste. Weiter kein Urteil.
|
|
48
|
+
*
|
|
49
|
+
* - Import und Uebergabe zaehlen nur an Gliedern, die ALLEIN dieser Kette gehoeren. Was an einer
|
|
50
|
+
* geteilten Funktion haengt, steht an der geteilten Funktion — sie ist ein eigener Ort. Ohne
|
|
51
|
+
* diese Grenze meldete jede Kette durch Gate und Store deren Zufluesse als ihre eigenen
|
|
52
|
+
* (gemessen am graphcode-Modell 2026-10-10: 333 statt 16).
|
|
53
|
+
* - Ein ACTOR als Erzeuger oder Verbraucher ist der Ein- und Ausgang der Kette, kein Ort.
|
|
54
|
+
* - Vertrag = `contractsOfFlow` aus `module-crossings.ts` (SCHEMA, sonst `UNBOUND:<flow>`), hier je
|
|
55
|
+
* Kettenkante gelesen. `moduleCrossings().pairs` selbst traegt die Vertraege des GANZEN Graphen
|
|
56
|
+
* je ungerichtetem Paar und kann „elf Kanten auf einem Vertrag" in EINER Kette nicht zeigen.
|
|
57
|
+
*
|
|
58
|
+
* @author andreas@siglochconsulting
|
|
59
|
+
*/
|
|
60
|
+
import { z } from 'zod/v4';
|
|
61
|
+
import type { OntologyGraph } from './ontology.js';
|
|
62
|
+
export declare const ChainMetrics: z.ZodObject<{
|
|
63
|
+
chains: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
64
|
+
measurable: z.ZodLiteral<true>;
|
|
65
|
+
length: z.ZodNumber;
|
|
66
|
+
branching: z.ZodNumber;
|
|
67
|
+
moduleBoundaries: z.ZodNullable<z.ZodNumber>;
|
|
68
|
+
feedbackLoops: z.ZodNumber;
|
|
69
|
+
sharedFuncs: z.ZodNumber;
|
|
70
|
+
bottlenecks: z.ZodNumber;
|
|
71
|
+
syncDepth: z.ZodNull;
|
|
72
|
+
errorPathDepth: z.ZodNull;
|
|
73
|
+
memberCount: z.ZodNumber;
|
|
74
|
+
fanIn: z.ZodNumber;
|
|
75
|
+
loops: z.ZodArray<z.ZodObject<{
|
|
76
|
+
id: z.ZodString;
|
|
77
|
+
members: z.ZodArray<z.ZodString>;
|
|
78
|
+
}, z.core.$strict>>;
|
|
79
|
+
shared: z.ZodArray<z.ZodString>;
|
|
80
|
+
boundaries: z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
81
|
+
from: z.ZodString;
|
|
82
|
+
to: z.ZodString;
|
|
83
|
+
edges: z.ZodNumber;
|
|
84
|
+
contracts: z.ZodArray<z.ZodString>;
|
|
85
|
+
}, z.core.$strict>>>;
|
|
86
|
+
imports: z.ZodArray<z.ZodObject<{
|
|
87
|
+
flow: z.ZodString;
|
|
88
|
+
func: z.ZodString;
|
|
89
|
+
}, z.core.$strict>>;
|
|
90
|
+
handovers: z.ZodArray<z.ZodObject<{
|
|
91
|
+
flow: z.ZodString;
|
|
92
|
+
func: z.ZodString;
|
|
93
|
+
}, z.core.$strict>>;
|
|
94
|
+
chainId: z.ZodString;
|
|
95
|
+
chainName: z.ZodString;
|
|
96
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
97
|
+
measurable: z.ZodLiteral<false>;
|
|
98
|
+
reasons: z.ZodArray<z.ZodEnum<{
|
|
99
|
+
"FC-05": "FC-05";
|
|
100
|
+
"loose-member": "loose-member";
|
|
101
|
+
"no-entry": "no-entry";
|
|
102
|
+
"no-exit": "no-exit";
|
|
103
|
+
empty: "empty";
|
|
104
|
+
}>>;
|
|
105
|
+
chainId: z.ZodString;
|
|
106
|
+
chainName: z.ZodString;
|
|
107
|
+
}, z.core.$strict>], "measurable">>;
|
|
108
|
+
measurability: z.ZodObject<{
|
|
109
|
+
chains: z.ZodNumber;
|
|
110
|
+
measurable: z.ZodNumber;
|
|
111
|
+
ratio: z.ZodNullable<z.ZodNumber>;
|
|
112
|
+
}, z.core.$strip>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
export type ChainMetrics = z.infer<typeof ChainMetrics>;
|
|
115
|
+
/** Die Kennzahlen aller FCHAINs dieses Graphen und die Quote der bewertbaren. */
|
|
116
|
+
export declare function chainMetrics(graph: OntologyGraph): ChainMetrics;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-SM-404 — Kennzahlen der Wirkkette: je FCHAIN eine Zahl, oder der Grund, warum es keine gibt.
|
|
3
|
+
*
|
|
4
|
+
* Die Leitlinie verlangt, die Realisierungsarchitektur AUF DER WIRKKETTE zu bewerten (graphcode
|
|
5
|
+
* `docs/graphcode_architektur_konzept.md`, „Kennzahlen der Wirkkette"). Bis hierher rechnete das
|
|
6
|
+
* nur ein Spike in graphcode (`scripts/spike-kettenkennzahlen.mjs`); von dort ist die Rechnung
|
|
7
|
+
* portiert, nicht neu erfunden. Gemessen 2026-10-09 ueber 10 Familie-Graphen: 76 Ketten, 31
|
|
8
|
+
* bewertbar.
|
|
9
|
+
*
|
|
10
|
+
* ## Diese Datei URTEILT NICHT
|
|
11
|
+
*
|
|
12
|
+
* Keine Regel, keine Schwelle, kein Katalogeintrag — dieselbe Trennung wie `moduleMetrics` und
|
|
13
|
+
* `functionCriticality`. Ob eine Laenge von 6 gut ist, sagt erst ein Profil.
|
|
14
|
+
*
|
|
15
|
+
* ## Nicht bewertbar heisst: KEINE Zahl
|
|
16
|
+
*
|
|
17
|
+
* Eine FCHAIN ist grammatisch eine Menge; die Reihenfolge entsteht erst aus Erzeuger ->
|
|
18
|
+
* Verbraucher (`FUNC -io-> FLOW -io-> FUNC`, beide Enden Glied). Zerfaellt die Kette, beschriebe
|
|
19
|
+
* jede Kennzahl etwas, das es nicht gibt. Eine solche Kette traegt deshalb ihre Gruende und kein
|
|
20
|
+
* einziges Zahlenfeld — das Schema laesst es nicht zu. Sie zaehlt im NENNER der Quote
|
|
21
|
+
* (`measurability`), die neben jedem Wert stehen muss wie die Bindungsquote neben RC-*.
|
|
22
|
+
*
|
|
23
|
+
* ## Wer urteilt, ob eine Kette eine Kette ist
|
|
24
|
+
*
|
|
25
|
+
* - `FC-05`: die Regel FC-05, aufgerufen, nicht nachgebaut. Ursache (fehlendes Glied, Sack) und
|
|
26
|
+
* die Nebenkomponenten stehen in ihrem Befund (`context.side_components`), nicht hier.
|
|
27
|
+
* - `loose-member`: ein Glied ohne io-Eingang oder -Ausgang. Das ist die Bedingung, unter der
|
|
28
|
+
* FC-05 sich ENTHAELT (R-31 meldet das Glied) — ohne diesen Grund haette eine solche Kette weder
|
|
29
|
+
* einen FC-05-Befund noch einen Grund und bekaeme Zahlen.
|
|
30
|
+
* - `no-entry` / `no-exit`: kein FLOW von bzw. zu einem Knoten AUSSERHALB der Kette. Weiter als
|
|
31
|
+
* FC-04, das nur einen ACTOR gelten laesst: eine Kette, die eine andere fortsetzt, ist messbar.
|
|
32
|
+
* - `empty`: keine FUNC (R-15).
|
|
33
|
+
*
|
|
34
|
+
* ## Ein Kettenbegriff, ein Modulbegriff
|
|
35
|
+
*
|
|
36
|
+
* Glied = direkte `FCHAIN -compose-> FUNC`-Kante, ueber `chainsByFunc` (R-30s Definition).
|
|
37
|
+
* Modul einer FUNC = direkte `allocate`-Kante, wie `module-crossings.ts` und `moduleMetrics`.
|
|
38
|
+
* Der Spike erbte das Modul ueber compose-Vorfahren; gemessen sind 326 von 326 Kettengliedern
|
|
39
|
+
* des Korpus direkt alloziert — die Vererbung kauft nichts und waere eine zweite Vorstellung von
|
|
40
|
+
* Zugehoerigkeit im selben Paket.
|
|
41
|
+
*
|
|
42
|
+
* ## CR-SM-406 — die ORTE je Kette, nur erkannt
|
|
43
|
+
*
|
|
44
|
+
* Neben jeder Zahl steht, WO sie herkommt: die Mitglieder je Rueckkopplung, die geteilten
|
|
45
|
+
* Funktionen, die Grenzen je gerichtetem Modulpaar mit ihren Vertraegen, dazu Zulauf, Importe und
|
|
46
|
+
* Uebergaben. Reine Zusatzfelder — die Zahlen aus CR-SM-404 bleiben und sind die Laenge ihrer
|
|
47
|
+
* Liste. Weiter kein Urteil.
|
|
48
|
+
*
|
|
49
|
+
* - Import und Uebergabe zaehlen nur an Gliedern, die ALLEIN dieser Kette gehoeren. Was an einer
|
|
50
|
+
* geteilten Funktion haengt, steht an der geteilten Funktion — sie ist ein eigener Ort. Ohne
|
|
51
|
+
* diese Grenze meldete jede Kette durch Gate und Store deren Zufluesse als ihre eigenen
|
|
52
|
+
* (gemessen am graphcode-Modell 2026-10-10: 333 statt 16).
|
|
53
|
+
* - Ein ACTOR als Erzeuger oder Verbraucher ist der Ein- und Ausgang der Kette, kein Ort.
|
|
54
|
+
* - Vertrag = `contractsOfFlow` aus `module-crossings.ts` (SCHEMA, sonst `UNBOUND:<flow>`), hier je
|
|
55
|
+
* Kettenkante gelesen. `moduleCrossings().pairs` selbst traegt die Vertraege des GANZEN Graphen
|
|
56
|
+
* je ungerichtetem Paar und kann „elf Kanten auf einem Vertrag" in EINER Kette nicht zeigen.
|
|
57
|
+
*
|
|
58
|
+
* @author andreas@siglochconsulting
|
|
59
|
+
*/
|
|
60
|
+
import { z } from 'zod/v4';
|
|
61
|
+
import { indexOf } from './graph-index.js';
|
|
62
|
+
import { chainsByFunc } from './function-criticality.js';
|
|
63
|
+
import { fc05ChainConnected } from './fchain-quality-rules.js';
|
|
64
|
+
import { contractsOfFlow } from './module-crossings.js';
|
|
65
|
+
const count = z.number().int().nonnegative();
|
|
66
|
+
const identity = { chainId: z.string(), chainName: z.string() };
|
|
67
|
+
/** CR-SM-406: ein Fluss an der Kettengrenze und das Glied der Kette, an dem er haengt. */
|
|
68
|
+
const FlowAtFunc = z.strictObject({ flow: z.string(), func: z.string() });
|
|
69
|
+
/**
|
|
70
|
+
* Eine bewertbare Kette: die fuenf heute rechenbaren Kennzahlen plus die Engstellen-Schranke,
|
|
71
|
+
* dazu die Orte (CR-SM-406). Jede Liste ist uid-sortiert.
|
|
72
|
+
*/
|
|
73
|
+
const MeasuredChain = z.strictObject({
|
|
74
|
+
...identity,
|
|
75
|
+
measurable: z.literal(true),
|
|
76
|
+
/** Gesamtlaenge: FUNC-Schritte auf dem laengsten Pfad; eine Schleife zaehlt als EIN Schritt. */
|
|
77
|
+
length: count,
|
|
78
|
+
/** Verzweigungsgrad: groesster Ausgangsgrad eines Glieds INNERHALB der Kette. */
|
|
79
|
+
branching: count,
|
|
80
|
+
/**
|
|
81
|
+
* Modulgrenzen: Kettenkanten, deren Enden in verschiedenen MOD liegen.
|
|
82
|
+
* `null`, sobald ein Glied kein Modul hat (R-22 meldet es) — keine geratene Zahl.
|
|
83
|
+
*/
|
|
84
|
+
moduleBoundaries: count.nullable(),
|
|
85
|
+
/** Rueckkopplungen: nicht-triviale starke Komponenten ueber die Kettenkanten. */
|
|
86
|
+
feedbackLoops: count,
|
|
87
|
+
/** Geteilte Knoten: Glieder, die in mehr als einer Kette liegen. */
|
|
88
|
+
sharedFuncs: count,
|
|
89
|
+
/**
|
|
90
|
+
* Engstellen — OBERE SCHRANKE: geteilte Glieder mit Ein- UND Ausgang in der Kette. Der
|
|
91
|
+
* Konzeptbegriff verlangt „synchron durchlaufen"; ohne `FLOW.sync` zaehlt jeder Durchgang.
|
|
92
|
+
*/
|
|
93
|
+
bottlenecks: count,
|
|
94
|
+
/** Synchrone Tiefe — in dieser Stufe nie berechnet: braucht den Leser von `FLOW.sync` (CR-SM-364). */
|
|
95
|
+
syncDepth: z.null(),
|
|
96
|
+
/** Fehlerpfad-Tiefe — nicht berechenbar: Fehlerfluesse traegt kein Attribut (CR-SM-364, `kind` verworfen). */
|
|
97
|
+
errorPathDepth: z.null(),
|
|
98
|
+
/** Zahl der Glieder. */
|
|
99
|
+
memberCount: count,
|
|
100
|
+
/** Zulauf: groesster Eingangsgrad eines Glieds INNERHALB der Kette — das Gegenstueck zu `branching`. */
|
|
101
|
+
fanIn: count,
|
|
102
|
+
/**
|
|
103
|
+
* Die Rueckkopplungen, die `feedbackLoops` zaehlt. `id` entsteht allein aus den Mitgliedern
|
|
104
|
+
* (uids mit `+` verbunden) — dieselbe Schleife traegt in jeder Kette dieselbe Kennung.
|
|
105
|
+
*/
|
|
106
|
+
loops: z.array(z.strictObject({ id: z.string(), members: z.array(z.string()).min(2) })),
|
|
107
|
+
/** Die geteilten Glieder, die `sharedFuncs` zaehlt. */
|
|
108
|
+
shared: z.array(z.string()),
|
|
109
|
+
/**
|
|
110
|
+
* Die Modulgrenzen je GERICHTETEM Modulpaar: `edges` Kettenkanten von `from` nach `to` (ihre
|
|
111
|
+
* Summe ist `moduleBoundaries`) auf den verschiedenen Vertraegen `contracts`. `null` wie dort.
|
|
112
|
+
*/
|
|
113
|
+
boundaries: z.array(z.strictObject({
|
|
114
|
+
from: z.string(), to: z.string(), edges: count.min(1), contracts: z.array(z.string()).min(1),
|
|
115
|
+
})).nullable(),
|
|
116
|
+
/**
|
|
117
|
+
* Importe: ein Glied, das NUR in dieser Kette liegt, verbraucht einen Fluss, den mindestens
|
|
118
|
+
* eine FUNC erzeugt und kein Glied der Kette.
|
|
119
|
+
*/
|
|
120
|
+
imports: z.array(FlowAtFunc),
|
|
121
|
+
/**
|
|
122
|
+
* Uebergaben, gespiegelt: ein Glied, das nur in dieser Kette liegt, erzeugt einen Fluss, den
|
|
123
|
+
* mindestens eine FUNC verbraucht und kein Glied der Kette.
|
|
124
|
+
*/
|
|
125
|
+
handovers: z.array(FlowAtFunc),
|
|
126
|
+
});
|
|
127
|
+
/** Eine nicht bewertbare Kette: der Grund statt der Zahl. Reihenfolge der Gruende ist fest. */
|
|
128
|
+
const UnmeasurableChain = z.strictObject({
|
|
129
|
+
...identity,
|
|
130
|
+
measurable: z.literal(false),
|
|
131
|
+
reasons: z.array(z.enum(['FC-05', 'loose-member', 'no-entry', 'no-exit', 'empty'])).min(1),
|
|
132
|
+
});
|
|
133
|
+
// CR-SM-361: ein Zod-Vertrag, kein Typ — der Erzeuger prueft sein Ergebnis.
|
|
134
|
+
export const ChainMetrics = z.object({
|
|
135
|
+
/** Eine Zeile je FCHAIN, in GRAPH-Reihenfolge — keine Rangfolge, die Zahl ist kein Urteil. */
|
|
136
|
+
chains: z.array(z.discriminatedUnion('measurable', [MeasuredChain, UnmeasurableChain])),
|
|
137
|
+
/** Die Reichweite der Aussage. `ratio` ist `null` ohne Ketten — kein Ersatzwert. */
|
|
138
|
+
measurability: z.object({ chains: count, measurable: count, ratio: z.number().min(0).max(1).nullable() }),
|
|
139
|
+
});
|
|
140
|
+
/** Die Kennzahlen aller FCHAINs dieses Graphen und die Quote der bewertbaren. */
|
|
141
|
+
export function chainMetrics(graph) {
|
|
142
|
+
const idx = indexOf(graph);
|
|
143
|
+
const chainsOf = chainsByFunc(graph);
|
|
144
|
+
// FC-05 ist der EINE Leser fuer „gerichtet zusammenhaengend" — hier zaehlt nur, ob er meldet.
|
|
145
|
+
const fc05 = new Set(fc05ChainConnected(graph).map((v) => v.element_id));
|
|
146
|
+
const flowsInto = (id) => idx.in(id, 'io').map((t) => t.source).filter((s) => idx.typeOf(s) === 'FLOW');
|
|
147
|
+
const flowsOutOf = (id) => idx.out(id, 'io').map((t) => t.target).filter((s) => idx.typeOf(s) === 'FLOW');
|
|
148
|
+
const moduleOf = (func) => idx.out(func, 'allocate').map((t) => t.target).find((m) => idx.typeOf(m) === 'MOD');
|
|
149
|
+
const chains = idx.elementsOfType('FCHAIN').map((fc) => {
|
|
150
|
+
const who = { chainId: fc.id, chainName: fc.name };
|
|
151
|
+
const members = [...new Set(idx.out(fc.id, 'compose').map((t) => t.target).filter((id) => idx.typeOf(id) === 'FUNC'))].sort();
|
|
152
|
+
if (members.length === 0)
|
|
153
|
+
return { ...who, measurable: false, reasons: ['empty'] };
|
|
154
|
+
const inChain = new Set(members);
|
|
155
|
+
// Kettenkanten Erzeuger -> Verbraucher; dazu, wo die Kette von aussen gespeist wird und wo sie hinausliefert.
|
|
156
|
+
const next = new Map(members.map((m) => [m, new Set()]));
|
|
157
|
+
const carriers = new Map(); // Kettenkante `a b` -> die FLOWs, die sie tragen
|
|
158
|
+
let entry = false;
|
|
159
|
+
let exit = false;
|
|
160
|
+
let loose = false;
|
|
161
|
+
for (const m of members) {
|
|
162
|
+
const ins = flowsInto(m);
|
|
163
|
+
const outs = flowsOutOf(m);
|
|
164
|
+
if (ins.length === 0 || outs.length === 0)
|
|
165
|
+
loose = true;
|
|
166
|
+
for (const flow of ins)
|
|
167
|
+
if (idx.in(flow, 'io').some((t) => !inChain.has(t.source)))
|
|
168
|
+
entry = true;
|
|
169
|
+
for (const flow of outs) {
|
|
170
|
+
for (const t of idx.out(flow, 'io')) {
|
|
171
|
+
if (t.target === m)
|
|
172
|
+
continue;
|
|
173
|
+
if (!inChain.has(t.target)) {
|
|
174
|
+
exit = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
next.get(m).add(t.target);
|
|
178
|
+
const edge = `${m} ${t.target}`;
|
|
179
|
+
(carriers.get(edge) ?? carriers.set(edge, new Set()).get(edge)).add(flow);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const reasons = [];
|
|
184
|
+
if (fc05.has(fc.id))
|
|
185
|
+
reasons.push('FC-05');
|
|
186
|
+
if (loose)
|
|
187
|
+
reasons.push('loose-member');
|
|
188
|
+
if (!entry)
|
|
189
|
+
reasons.push('no-entry');
|
|
190
|
+
if (!exit)
|
|
191
|
+
reasons.push('no-exit');
|
|
192
|
+
if (reasons.length > 0)
|
|
193
|
+
return { ...who, measurable: false, reasons };
|
|
194
|
+
// Rueckkopplungen: Tarjan — jede starke Komponente mit mehr als einem Glied ist eine Schleife.
|
|
195
|
+
const order = new Map();
|
|
196
|
+
const low = new Map();
|
|
197
|
+
const onStack = new Set();
|
|
198
|
+
const stack = [];
|
|
199
|
+
const sccOf = new Map();
|
|
200
|
+
const loops = [];
|
|
201
|
+
const visit = (v) => {
|
|
202
|
+
order.set(v, order.size);
|
|
203
|
+
low.set(v, order.get(v));
|
|
204
|
+
stack.push(v);
|
|
205
|
+
onStack.add(v);
|
|
206
|
+
for (const w of next.get(v)) {
|
|
207
|
+
if (!order.has(w)) {
|
|
208
|
+
visit(w);
|
|
209
|
+
low.set(v, Math.min(low.get(v), low.get(w)));
|
|
210
|
+
}
|
|
211
|
+
else if (onStack.has(w))
|
|
212
|
+
low.set(v, Math.min(low.get(v), order.get(w)));
|
|
213
|
+
}
|
|
214
|
+
if (low.get(v) !== order.get(v))
|
|
215
|
+
return;
|
|
216
|
+
const comp = [];
|
|
217
|
+
let w;
|
|
218
|
+
do {
|
|
219
|
+
w = stack.pop();
|
|
220
|
+
onStack.delete(w);
|
|
221
|
+
comp.push(w);
|
|
222
|
+
} while (w !== v);
|
|
223
|
+
if (comp.length > 1)
|
|
224
|
+
loops.push({ id: comp.sort().join('+'), members: comp });
|
|
225
|
+
for (const c of comp)
|
|
226
|
+
sccOf.set(c, v);
|
|
227
|
+
};
|
|
228
|
+
for (const m of members)
|
|
229
|
+
if (!order.has(m))
|
|
230
|
+
visit(m);
|
|
231
|
+
loops.sort((a, b) => (a.id < b.id ? -1 : 1));
|
|
232
|
+
// Gesamtlaenge: laengster Pfad auf dem Kondensat — eine Schleife ist dort ein Knoten.
|
|
233
|
+
const condensed = new Map();
|
|
234
|
+
for (const [a, targets] of next) {
|
|
235
|
+
for (const b of targets) {
|
|
236
|
+
const ca = sccOf.get(a);
|
|
237
|
+
const cb = sccOf.get(b);
|
|
238
|
+
if (ca !== cb)
|
|
239
|
+
(condensed.get(ca) ?? condensed.set(ca, new Set()).get(ca)).add(cb);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const longestFrom = new Map();
|
|
243
|
+
const longest = (c) => {
|
|
244
|
+
const known = longestFrom.get(c);
|
|
245
|
+
if (known !== undefined)
|
|
246
|
+
return known;
|
|
247
|
+
let best = 1;
|
|
248
|
+
for (const n of condensed.get(c) ?? [])
|
|
249
|
+
best = Math.max(best, 1 + longest(n));
|
|
250
|
+
longestFrom.set(c, best);
|
|
251
|
+
return best;
|
|
252
|
+
};
|
|
253
|
+
const length = Math.max(...members.map((m) => longest(sccOf.get(m))));
|
|
254
|
+
const allocated = members.every((m) => moduleOf(m) !== undefined);
|
|
255
|
+
let branching = 0;
|
|
256
|
+
const inDegree = new Map();
|
|
257
|
+
const pairs = new Map();
|
|
258
|
+
for (const [a, targets] of next) {
|
|
259
|
+
branching = Math.max(branching, targets.size);
|
|
260
|
+
for (const b of targets) {
|
|
261
|
+
inDegree.set(b, (inDegree.get(b) ?? 0) + 1);
|
|
262
|
+
const from = moduleOf(a);
|
|
263
|
+
const to = moduleOf(b);
|
|
264
|
+
if (from === undefined || to === undefined || from === to)
|
|
265
|
+
continue;
|
|
266
|
+
const key = `${from} ${to}`;
|
|
267
|
+
const pair = pairs.get(key) ?? pairs.set(key, { from, to, edges: 0, contracts: new Set() }).get(key);
|
|
268
|
+
pair.edges++;
|
|
269
|
+
for (const flow of carriers.get(`${a} ${b}`))
|
|
270
|
+
for (const c of contractsOfFlow(idx, flow))
|
|
271
|
+
pair.contracts.add(c);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const boundaries = [...pairs.entries()].sort(([a], [b]) => (a < b ? -1 : 1))
|
|
275
|
+
.map(([, p]) => ({ ...p, contracts: [...p.contracts].sort() }));
|
|
276
|
+
const shared = members.filter((m) => (chainsOf.get(m)?.size ?? 0) > 1);
|
|
277
|
+
// Importe und Uebergaben: nur an Gliedern, die allein dieser Kette gehoeren; die andere Seite
|
|
278
|
+
// des Flusses ist mindestens eine FUNC und kein Glied (ein ACTOR ist Ein-/Ausgang, kein Ort).
|
|
279
|
+
const own = members.filter((m) => !shared.includes(m));
|
|
280
|
+
const foreign = (ends) => {
|
|
281
|
+
const funcs = ends.filter((id) => idx.typeOf(id) === 'FUNC');
|
|
282
|
+
return funcs.length > 0 && !funcs.some((f) => inChain.has(f));
|
|
283
|
+
};
|
|
284
|
+
const at = (flows, farEnds) => own.flatMap((func) => [...new Set(flows(func))].sort()
|
|
285
|
+
.filter((flow) => foreign(farEnds(flow))).map((flow) => ({ flow, func })));
|
|
286
|
+
return {
|
|
287
|
+
...who,
|
|
288
|
+
measurable: true,
|
|
289
|
+
length,
|
|
290
|
+
branching,
|
|
291
|
+
moduleBoundaries: allocated ? boundaries.reduce((n, p) => n + p.edges, 0) : null,
|
|
292
|
+
feedbackLoops: loops.length,
|
|
293
|
+
sharedFuncs: shared.length,
|
|
294
|
+
bottlenecks: shared.filter((m) => inDegree.has(m) && next.get(m).size > 0).length,
|
|
295
|
+
syncDepth: null,
|
|
296
|
+
errorPathDepth: null,
|
|
297
|
+
memberCount: members.length,
|
|
298
|
+
fanIn: Math.max(0, ...inDegree.values()),
|
|
299
|
+
loops,
|
|
300
|
+
shared,
|
|
301
|
+
boundaries: allocated ? boundaries : null,
|
|
302
|
+
imports: at(flowsInto, (flow) => idx.in(flow, 'io').map((t) => t.source)),
|
|
303
|
+
handovers: at(flowsOutOf, (flow) => idx.out(flow, 'io').map((t) => t.target)),
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
const measurable = chains.filter((c) => c.measurable).length;
|
|
307
|
+
return ChainMetrics.parse({
|
|
308
|
+
chains,
|
|
309
|
+
measurability: { chains: chains.length, measurable, ratio: chains.length > 0 ? measurable / chains.length : null },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
@@ -10,9 +10,8 @@ import { type MarkType, type RuleRole, type RuleStage } from './readiness.js';
|
|
|
10
10
|
* All prescribed rule definitions (single source of truth for the catalog).
|
|
11
11
|
*
|
|
12
12
|
* CR-SM-235: `domain` reicht mit durch — die Grundgesamtheit, ueber die eine Regel feuert.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* wenn es keine zweite gibt.
|
|
13
|
+
* Die Faelligkeit (`isDue`) liest sie hier statt eine eigene Tabelle zu fuehren. Eine zweite
|
|
14
|
+
* Tabelle kann nicht hinterherhinken, wenn es keine zweite gibt.
|
|
16
15
|
*/
|
|
17
16
|
/** Profile groupings — see CATALOGS. */
|
|
18
17
|
export type ProfileId = 'default' | 'se' | 'coding' | 'conformance';
|
package/dist/se/evaluate-all.js
CHANGED
|
@@ -10,7 +10,7 @@ import { CR_RULES } from './cr-quality-rules.js';
|
|
|
10
10
|
import { ND_RULES } from './near-duplicate-rules.js';
|
|
11
11
|
import { AO_RULES } from './ao-rules.js';
|
|
12
12
|
import { BQ_RULES } from './quality-rules.js';
|
|
13
|
-
import { AF_RULES
|
|
13
|
+
import { AF_RULES } from './analysis-freshness-rules.js';
|
|
14
14
|
import { CODE_CONFORMANCE_RULES } from './conformance-rules.js';
|
|
15
15
|
import { markOfStage } from './readiness.js';
|
|
16
16
|
/**
|
|
@@ -37,7 +37,6 @@ const CATALOGS = [
|
|
|
37
37
|
{ profile: 'se', rules: FM_RULES },
|
|
38
38
|
{ profile: 'se', rules: VIEW_RULES },
|
|
39
39
|
{ profile: 'se', rules: AF_RULES },
|
|
40
|
-
{ profile: 'se', rules: TASK_OUTCOME_RULES }, // CR-SM-355: Task-Ausgaenge trade / irr
|
|
41
40
|
// Sprach-/Textqualitaet statt SE-Struktur — das ist die Trennlinie, die die Praefixliste meinte.
|
|
42
41
|
{ profile: 'coding', rules: BQ_RULES },
|
|
43
42
|
{ profile: 'coding', rules: ND_RULES },
|
|
@@ -76,7 +75,6 @@ export function getRuleDefsForProfile(profile) {
|
|
|
76
75
|
const EVALUATED_CATALOGS = [
|
|
77
76
|
V3_RULES, BQ_RULES, UC_RULES, FC_RULES, SC_RULES, ND_RULES, MT_RULES, CR_RULES, AO_RULES,
|
|
78
77
|
FM_RULES, VIEW_RULES, AF_RULES,
|
|
79
|
-
TASK_OUTCOME_RULES, // CR-SM-355
|
|
80
78
|
];
|
|
81
79
|
/**
|
|
82
80
|
* Evaluate all rules against a graph. Single call replaces the individual evaluator calls.
|