@sigloch/contracts 10.14.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.
Files changed (40) hide show
  1. package/dist/se/analysis-freshness-rules.d.ts +37 -30
  2. package/dist/se/analysis-freshness-rules.js +41 -81
  3. package/dist/se/ao-rules.js +3 -3
  4. package/dist/se/chain-metrics.d.ts +116 -0
  5. package/dist/se/chain-metrics.js +311 -0
  6. package/dist/se/conformance-rules.d.ts +4 -0
  7. package/dist/se/conformance-rules.js +37 -32
  8. package/dist/se/cr-quality-rules.js +4 -76
  9. package/dist/se/evaluate-all.d.ts +7 -3
  10. package/dist/se/evaluate-all.js +3 -4
  11. package/dist/se/fchain-quality-rules.js +4 -4
  12. package/dist/se/fmea-rules.js +4 -4
  13. package/dist/se/format-e-parser.js +1 -1
  14. package/dist/se/grammar-snapshot.d.ts +6 -6
  15. package/dist/se/grammar-snapshot.js +90 -103
  16. package/dist/se/index.d.ts +4 -2
  17. package/dist/se/index.js +6 -2
  18. package/dist/se/metric-rules.d.ts +9 -5
  19. package/dist/se/metric-rules.js +10 -9
  20. package/dist/se/module-crossings.d.ts +10 -0
  21. package/dist/se/module-crossings.js +3 -1
  22. package/dist/se/near-duplicate-rules.d.ts +2 -0
  23. package/dist/se/near-duplicate-rules.js +2 -2
  24. package/dist/se/ontology.d.ts +23 -7
  25. package/dist/se/ontology.js +29 -26
  26. package/dist/se/quality-rules.js +5 -5
  27. package/dist/se/readiness.d.ts +41 -149
  28. package/dist/se/readiness.js +50 -280
  29. package/dist/se/rule-due.d.ts +26 -0
  30. package/dist/se/rule-due.js +72 -0
  31. package/dist/se/rule-help.d.ts +3 -1
  32. package/dist/se/rule-help.js +47 -31
  33. package/dist/se/rules.d.ts +23 -13
  34. package/dist/se/rules.js +112 -72
  35. package/dist/se/schema-quality-rules.d.ts +1 -1
  36. package/dist/se/schema-quality-rules.js +1 -1
  37. package/dist/se/similarity.js +4 -10
  38. package/dist/se/uc-quality-rules.js +4 -4
  39. package/dist/se/view-rules.js +6 -4
  40. package/package.json +1 -1
@@ -1,64 +1,71 @@
1
1
  /**
2
- * CR-SM-227: Analysis-Freshness-Legs — the third leg-kind a review gate checks
3
- * (rule legs, layer-presence legs [R-28, entfallen mit CR-SM-247], now analysis-freshness legs).
4
- * Presence/Resolution-Split (wie R-19/R-20/R-26/R-27 vs. RC-01..05): these five
5
- * rules are the PRÜFBARE FORM only — does a `graphVersion` stamp exist for the
6
- * artifact, as a graph attribute. They never compare the stamp against the
7
- * live graphVersion (that staleness/currency check is I/O — a consumer
8
- * concern, `CreationCurrencyProvider` in `@sigloch/graphcode-client`).
2
+ * CR-SM-227: the analysis rules — was each judgment-work artifact carried out?
3
+ *
4
+ * AF-01..04 ask for the stamp `SYS.attributes.analysisFreshness.<artifact>`: its presence is the
5
+ * only record that the analysis was done (an FMEA may legitimately end without a single risk, so
6
+ * the result cannot stand in for it). They never compare the stamp against the live graphVersion.
7
+ *
8
+ * AF-05 is different since CR-SM-395: the implementation plan is not a stamp but the set of open
9
+ * CRs. The rule reads "there is something unbuilt, but no open order" and falls due again with
10
+ * every change — which a one-time stamp could not express.
9
11
  */
10
12
  import type { OntologyGraph } from './ontology.js';
11
- import type { RuleViolation } from './rules.js';
13
+ import { type RuleViolation } from './rules.js';
14
+ /**
15
+ * AF-05 (CR-SM-395): es gibt Ungebautes, aber keinen offenen Auftrag → der Bauplan ist faellig.
16
+ *
17
+ * Ungebaut ist, woran die Bindungsregeln melden wuerden (`unbuiltElementIds`: TEST, FUNC, SCHEMA
18
+ * ohne Bindung, `external` und Zerlegungs-Eltern ausgenommen wie dort). Es reicht, dass EIN offener
19
+ * Auftrag existiert; er muss nicht auf die Elemente zeigen — ob er die richtigen trifft, zeigt der
20
+ * Git-Diff. Mit dem Auftrag verstummt diese Regel, und die Bindungsregeln der Stufe 11 werden
21
+ * faellig: ihre Befunde sind die Arbeitsliste. Beim ersten Bau sind das alle Elemente, bei einer
22
+ * Aenderung die neuen.
23
+ *
24
+ * EIN Befund je Graph, am SYS: dort wird er abgenommen („Bau nicht beauftragt"), und eine Abnahme
25
+ * je Element waere mit jedem neuen Element wieder offen. Die Meldung nennt die ungebauten Elemente.
26
+ */
27
+ declare function buildPlanDue(graph: OntologyGraph): RuleViolation[];
12
28
  export declare const AF_RULES: readonly [{
13
29
  readonly id: "AF-01";
14
30
  readonly name: "ConOps freshness stamp present";
15
31
  readonly severity: "warning";
32
+ readonly stage: 3;
33
+ readonly role: "analysis";
16
34
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
17
35
  readonly domain: readonly ["graph"];
18
36
  }, {
19
37
  readonly id: "AF-02";
20
38
  readonly name: "Trade Study freshness stamp present";
21
39
  readonly severity: "warning";
40
+ readonly stage: 7;
41
+ readonly role: "analysis";
22
42
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
23
43
  readonly domain: readonly ["graph"];
24
44
  }, {
25
45
  readonly id: "AF-03";
26
46
  readonly name: "Assumption Review freshness stamp present";
27
47
  readonly severity: "warning";
48
+ readonly stage: 7;
49
+ readonly role: "analysis";
28
50
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
29
51
  readonly domain: readonly ["graph"];
30
52
  }, {
31
53
  readonly id: "AF-04";
32
54
  readonly name: "FMEA freshness stamp present";
33
55
  readonly severity: "warning";
56
+ readonly stage: 8;
57
+ readonly role: "analysis";
34
58
  readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
35
59
  readonly domain: readonly ["graph"];
36
60
  }, {
37
61
  readonly id: "AF-05";
38
- readonly name: "Implementation Plan freshness stamp present";
39
- readonly severity: "warning";
40
- readonly evaluate: (graph: OntologyGraph) => RuleViolation[];
41
- readonly domain: readonly ["graph"];
42
- }];
43
- /** TR-01: a stamped trade study names >=1 existing CR, each carrying a relation(label: decides) edge. */
44
- export declare function tradeDecisionRecorded(graph: OntologyGraph): RuleViolation[];
45
- /** IR-01: every CR named by a stamped assumption review exists; an empty list is a legitimate outcome. */
46
- export declare function assumptionReviewPromoted(graph: OntologyGraph): RuleViolation[];
47
- /** CR-SM-355: the task-outcome rules — owned by trade / irr (TASK_OWNED_RULES), never in the kern focus. */
48
- export declare const TASK_OUTCOME_RULES: readonly [{
49
- readonly id: "TR-01";
50
- readonly name: "Trade decision recorded as CR";
51
- readonly severity: "warning";
52
- readonly evaluate: typeof tradeDecisionRecorded;
53
- readonly domain: readonly ["graph"];
54
- }, {
55
- readonly id: "IR-01";
56
- readonly name: "Assumption review promoted to CR";
62
+ readonly name: "Unbuilt elements have an open CR";
57
63
  readonly severity: "warning";
58
- readonly evaluate: typeof assumptionReviewPromoted;
64
+ readonly stage: 10;
65
+ readonly role: "existence";
66
+ readonly evaluate: typeof buildPlanDue;
59
67
  readonly domain: readonly ["graph"];
60
68
  }];
61
- /** Evaluate the task-outcome rules (CR-SM-355). */
62
- export declare function evaluateTaskOutcomeRules(graph: OntologyGraph): RuleViolation[];
63
69
  /** Evaluate all analysis-freshness-presence rules. */
64
70
  export declare function evaluateAFRules(graph: OntologyGraph): RuleViolation[];
71
+ export {};
@@ -1,10 +1,11 @@
1
- import { AnalysisFreshnessStampSchema } from './ontology.js';
1
+ import { readAnalysisStamp } from './ontology.js';
2
+ import { unbuiltElementIds } from './rules.js';
3
+ import { hasOpenOrder } from './rule-due.js';
2
4
  const ARTIFACT_LABEL = {
3
5
  conops: 'Concept of Operations',
4
6
  trade: 'Trade Study',
5
7
  'assumption-review': 'Assumption Review',
6
8
  fmea: 'FMEA',
7
- implplan: 'Implementation Plan',
8
9
  };
9
10
  /**
10
11
  * CR-SM-382: was der Hinweis nennt — den Task, seinen Skill und den ersten Arbeitsschritt.
@@ -29,20 +30,16 @@ const ANALYSIS_WORK = {
29
30
  },
30
31
  trade: {
31
32
  skill: 'se-trade',
32
- firstStep: 'name the open decision with its options and criteria, then record the chosen option as a CR with relation(label: decides) edges to what it decides',
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',
33
34
  },
34
35
  'assumption-review': {
35
36
  skill: 'se-irr',
36
- firstStep: 'list every unproven assumption the model rests on (unmeasured numbers, unverified external dependencies) in a record under docs/records/, then promote the load-bearing ones to CRs',
37
+ firstStep: 'list every unproven assumption the model rests on (unmeasured numbers, unverified external dependencies) in a record under docs/records/, then write each load-bearing one as a REQ with its verifying TEST',
37
38
  },
38
39
  fmea: {
39
40
  skill: 'se-fmea',
40
41
  firstStep: 'for each FCHAIN name its failure modes and add each one as a REQ with role risk (severity, occurrence, detection) plus a mitigation REQ and its TEST',
41
42
  },
42
- implplan: {
43
- skill: 'se-plan',
44
- firstStep: 'cut one CR per leaf REQ at its carrier and group the CRs into MS milestones in dependency order',
45
- },
46
43
  };
47
44
  /** Presence-check for one analysis artifact's freshness stamp, anchored on SYS. */
48
45
  function analysisFreshnessPresence(ruleId, artifactId) {
@@ -50,8 +47,7 @@ function analysisFreshnessPresence(ruleId, artifactId) {
50
47
  const sys = graph.elements.find(e => e.type === 'SYS');
51
48
  if (!sys)
52
49
  return []; // nothing to anchor on yet (vacuous-complete exemption)
53
- const stamp = sys.attributes?.analysisFreshness?.[artifactId];
54
- if (AnalysisFreshnessStampSchema.safeParse(stamp).success)
50
+ if (readAnalysisStamp(graph, artifactId).state === 'bound')
55
51
  return [];
56
52
  const label = ARTIFACT_LABEL[artifactId];
57
53
  const work = ANALYSIS_WORK[artifactId];
@@ -69,85 +65,49 @@ const conopsFreshnessPresent = analysisFreshnessPresence('AF-01', 'conops');
69
65
  const tradeFreshnessPresent = analysisFreshnessPresence('AF-02', 'trade');
70
66
  const assumptionReviewFreshnessPresent = analysisFreshnessPresence('AF-03', 'assumption-review');
71
67
  const fmeaFreshnessPresent = analysisFreshnessPresence('AF-04', 'fmea');
72
- const implplanFreshnessPresent = analysisFreshnessPresence('AF-05', 'implplan');
73
- export const AF_RULES = [
74
- { id: 'AF-01', name: 'ConOps freshness stamp present', severity: 'warning', evaluate: conopsFreshnessPresent, domain: ['graph'] },
75
- { id: 'AF-02', name: 'Trade Study freshness stamp present', severity: 'warning', evaluate: tradeFreshnessPresent, domain: ['graph'] },
76
- { id: 'AF-03', name: 'Assumption Review freshness stamp present', severity: 'warning', evaluate: assumptionReviewFreshnessPresent, domain: ['graph'] },
77
- { id: 'AF-04', name: 'FMEA freshness stamp present', severity: 'warning', evaluate: fmeaFreshnessPresent, domain: ['graph'] },
78
- { id: 'AF-05', name: 'Implementation Plan freshness stamp present', severity: 'warning', evaluate: implplanFreshnessPresent, domain: ['graph'] },
79
- ];
80
68
  /**
81
- * CR-SM-355: Task-Ausgangsregeln fuer trade und irr — die aus dem Task entstandenen Massnahmen sind
82
- * als CR dokumentiert. Traeger ist `crRefs` im Frischestempel. Ohne Stempel feuern sie nicht (dann
83
- * feuert der Eintritt AF-02/AF-03, und der Task ist nicht durch). `label` wird als `Trace.label`
84
- * gelesen wie bei MS-02 — `decides` ist kein TraceType, sondern das SP-1-Label einer relation-Kante.
69
+ * AF-05 (CR-SM-395): es gibt Ungebautes, aber keinen offenen Auftrag → der Bauplan ist faellig.
70
+ *
71
+ * Ungebaut ist, woran die Bindungsregeln melden wuerden (`unbuiltElementIds`: TEST, FUNC, SCHEMA
72
+ * ohne Bindung, `external` und Zerlegungs-Eltern ausgenommen wie dort). Es reicht, dass EIN offener
73
+ * Auftrag existiert; er muss nicht auf die Elemente zeigen — ob er die richtigen trifft, zeigt der
74
+ * Git-Diff. Mit dem Auftrag verstummt diese Regel, und die Bindungsregeln der Stufe 11 werden
75
+ * faellig: ihre Befunde sind die Arbeitsliste. Beim ersten Bau sind das alle Elemente, bei einer
76
+ * Aenderung die neuen.
77
+ *
78
+ * EIN Befund je Graph, am SYS: dort wird er abgenommen („Bau nicht beauftragt"), und eine Abnahme
79
+ * je Element waere mit jedem neuen Element wieder offen. Die Meldung nennt die ungebauten Elemente.
85
80
  */
86
- function stampedCrRefs(graph, artifactId) {
81
+ function buildPlanDue(graph) {
87
82
  const sys = graph.elements.find(e => e.type === 'SYS');
88
83
  if (!sys)
89
- return null;
90
- const raw = sys.attributes?.analysisFreshness?.[artifactId];
91
- const parsed = AnalysisFreshnessStampSchema.safeParse(raw);
92
- if (!parsed.success)
93
- return null;
94
- return { sys, crRefs: parsed.data.crRefs ?? [] };
95
- }
96
- /** TR-01: a stamped trade study names >=1 existing CR, each carrying a relation(label: decides) edge. */
97
- export function tradeDecisionRecorded(graph) {
98
- const stamped = stampedCrRefs(graph, 'trade');
99
- if (!stamped)
84
+ return []; // kein Anker — R-33 meldet das fehlende System
85
+ if (hasOpenOrder(graph))
100
86
  return [];
101
- const { sys, crRefs } = stamped;
102
- const base = { rule_id: 'TR-01', severity: 'warning', context: { element_type: sys.type, element_name: sys.name } };
103
- if (crRefs.length === 0) {
104
- return [{
105
- ...base,
106
- element_id: sys.id,
107
- 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',
108
- fix_hint: 'Record the decision as a CR with relation(label: decides) edges and list its id in analysisFreshness.trade.crRefs (se-trade)',
109
- }];
110
- }
111
- const byId = new Map(graph.elements.map(e => [e.id, e]));
112
- const decidesFrom = new Set(graph.traces.filter(t => t.type === 'relation' && t.label === 'decides').map(t => t.source));
113
- return crRefs.flatMap(id => {
114
- const cr = byId.get(id);
115
- if (!cr || cr.type !== 'CR') {
116
- 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)' }];
117
- }
118
- if (!decidesFrom.has(id)) {
119
- 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)' }];
120
- }
121
- return [];
122
- });
123
- }
124
- /** IR-01: every CR named by a stamped assumption review exists; an empty list is a legitimate outcome. */
125
- export function assumptionReviewPromoted(graph) {
126
- const stamped = stampedCrRefs(graph, 'assumption-review');
127
- if (!stamped)
87
+ const unbuilt = unbuiltElementIds(graph);
88
+ if (unbuilt.length === 0)
128
89
  return [];
129
- const { sys, crRefs } = stamped;
130
- const byId = new Map(graph.elements.map(e => [e.id, e]));
131
- return crRefs
132
- .filter(id => byId.get(id)?.type !== 'CR')
133
- .map(id => ({
134
- rule_id: 'IR-01',
135
- severity: 'warning',
136
- element_id: sys.id,
137
- message: `Assumption Review names ${id} as a promoted assumption, but no CR with that id exists`,
138
- fix_hint: 'Point analysisFreshness.assumption-review.crRefs at an existing CR, or add the CR through graph_mutate (se-irr)',
139
- context: { element_type: sys.type, element_name: sys.name },
140
- }));
90
+ const shown = unbuilt.slice(0, 3).join(', ') + (unbuilt.length > 3 ? ` and ${unbuilt.length - 3} more` : '');
91
+ return [{
92
+ rule_id: 'AF-05',
93
+ severity: 'warning',
94
+ element_id: sys.id,
95
+ message: `${unbuilt.length} ${unbuilt.length === 1 ? 'element is' : 'elements are'} not built yet (${shown}) and no open CR orders the work — the implementation plan is due`,
96
+ fix_hint: 'Carry out the Implementation Plan: work through the skill se-plan step by step. First step: cut one CR per leaf REQ at its carrier and group the CRs into MS milestones in dependency order. The open CRs are the plan — with the first one the binding rules list what to build',
97
+ context: { element_type: sys.type, element_name: sys.name },
98
+ }];
141
99
  }
142
- /** CR-SM-355: the task-outcome rules — owned by trade / irr (TASK_OWNED_RULES), never in the kern focus. */
143
- export const TASK_OUTCOME_RULES = [
144
- { id: 'TR-01', name: 'Trade decision recorded as CR', severity: 'warning', evaluate: tradeDecisionRecorded, domain: ['graph'] },
145
- { id: 'IR-01', name: 'Assumption review promoted to CR', severity: 'warning', evaluate: assumptionReviewPromoted, domain: ['graph'] },
100
+ // CR-SM-396: AF-01 liegt auf Stufe 3, HINTER den Anforderungen — eine Ausnahme von „Stufe = spaeteste
101
+ // Menge, die die Regel braucht" (das Einsatzkonzept braucht nur die Anwendungsfaelle, Stufe 2).
102
+ // Entscheidung des Autors 2026-10-06: zuerst sagt der Anwendungsfall, was verlangt ist (UC-01), dann
103
+ // folgt die Analyse des Betriebs. In einer Stufe steht die Existenz-Regel vor der Analyse.
104
+ export const AF_RULES = [
105
+ { id: 'AF-01', name: 'ConOps freshness stamp present', severity: 'warning', stage: 3, role: 'analysis', evaluate: conopsFreshnessPresent, domain: ['graph'] },
106
+ { id: 'AF-02', name: 'Trade Study freshness stamp present', severity: 'warning', stage: 7, role: 'analysis', evaluate: tradeFreshnessPresent, domain: ['graph'] },
107
+ { id: 'AF-03', name: 'Assumption Review freshness stamp present', severity: 'warning', stage: 7, role: 'analysis', evaluate: assumptionReviewFreshnessPresent, domain: ['graph'] },
108
+ { id: 'AF-04', name: 'FMEA freshness stamp present', severity: 'warning', stage: 8, role: 'analysis', evaluate: fmeaFreshnessPresent, domain: ['graph'] },
109
+ { id: 'AF-05', name: 'Unbuilt elements have an open CR', severity: 'warning', stage: 10, role: 'existence', evaluate: buildPlanDue, domain: ['graph'] },
146
110
  ];
147
- /** Evaluate the task-outcome rules (CR-SM-355). */
148
- export function evaluateTaskOutcomeRules(graph) {
149
- return TASK_OUTCOME_RULES.flatMap(r => r.evaluate(graph));
150
- }
151
111
  /** Evaluate all analysis-freshness-presence rules. */
152
112
  export function evaluateAFRules(graph) {
153
113
  return AF_RULES.flatMap(r => r.evaluate(graph));
@@ -260,12 +260,12 @@ export function io01CrossModuleCompleteness(graph) {
260
260
  return violations;
261
261
  }
262
262
  export const AO_RULES = [
263
- { id: 'BW-02', name: 'Whitebox boundary width', severity: 'warning', evaluate: bw02WhiteboxWidth, domain: ['FUNC'] },
264
- { id: 'CR-01', name: 'CrossingFlowCount', severity: 'warning', evaluate: cr01CrossingFlowCount, domain: ['MOD'] },
263
+ { id: 'BW-02', name: 'Whitebox boundary width', severity: 'warning', stage: 5, evaluate: bw02WhiteboxWidth, domain: ['FUNC'] },
264
+ { id: 'CR-01', name: 'CrossingFlowCount', severity: 'warning', stage: 7, evaluate: cr01CrossingFlowCount, domain: ['MOD'] },
265
265
  // CR-SM-242: domain ist FUNC, nicht MOD — die Regel laeuft ueber FUNC-Paare in FCHAINs und
266
266
  // meldet am FUNC. Mit ['MOD'] uebertraf ihr Zaehler (21) ihren eigenen Nenner-Beitrag (10);
267
267
  // nur der Clamp verhinderte einen negativen Score.
268
- { id: 'IO-01', name: 'FuncPairIOCompleteness', severity: 'warning', evaluate: io01CrossModuleCompleteness, domain: ['FUNC'] },
268
+ { id: 'IO-01', name: 'FuncPairIOCompleteness', severity: 'warning', stage: 5, evaluate: io01CrossModuleCompleteness, domain: ['FUNC'] },
269
269
  ];
270
270
  export function evaluateAORules(graph, policy) {
271
271
  return AO_RULES.flatMap(r => r.evaluate(graph, policy));
@@ -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;