@sigloch/graphcode-client 1.5.1 → 2.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/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/panels.d.ts +26 -60
- package/dist/panels.js +21 -55
- package/dist/readiness.d.ts +59 -156
- package/dist/readiness.js +38 -254
- package/package.json +3 -3
- package/dist/readiness-completeness.d.ts +0 -71
- package/dist/readiness-completeness.js +0 -100
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,5 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export { HOST_SOCK_BASENAME, callHost, type ShimRequest, type ShimResponse, } from './host-client.js';
|
|
20
20
|
export { MARKDOWN_VIEWS, VIEW_FILENAMES, type MarkdownView } from './view-catalog.js';
|
|
21
|
-
export { GRAPHCODE_INCOSE_SCOPE,
|
|
22
|
-
export { NEUTRAL_COMPLETENESS, COMPLETENESS_SLICES, scoreCompleteness, typeOf, type CGraph, type CNode, GateCompleteness, type CompletenessLeg, } from './readiness-completeness.js';
|
|
21
|
+
export { GRAPHCODE_INCOSE_SCOPE, MARK_LABELS, computeMarks, summarizeReadiness, computeReadiness, scoreReadiness, getFamilyRuleIds, type IncoseScope, type ReadinessDimension, type ReadinessMark, type ReadinessReport, } from './readiness.js';
|
|
23
22
|
export * from './panels.js';
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,5 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export { HOST_SOCK_BASENAME, callHost, } from './host-client.js';
|
|
20
20
|
export { MARKDOWN_VIEWS, VIEW_FILENAMES } from './view-catalog.js';
|
|
21
|
-
export { GRAPHCODE_INCOSE_SCOPE,
|
|
22
|
-
export { NEUTRAL_COMPLETENESS, COMPLETENESS_SLICES, scoreCompleteness, typeOf, GateCompleteness, } from './readiness-completeness.js';
|
|
21
|
+
export { GRAPHCODE_INCOSE_SCOPE, MARK_LABELS, computeMarks, summarizeReadiness, computeReadiness, scoreReadiness, getFamilyRuleIds, } from './readiness.js';
|
|
23
22
|
export * from './panels.js';
|
package/dist/panels.d.ts
CHANGED
|
@@ -14,29 +14,15 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @author andreas@siglochconsulting
|
|
16
16
|
*/
|
|
17
|
-
import type { ReadinessReport,
|
|
17
|
+
import type { ReadinessReport, ReadinessMark } from './readiness.js';
|
|
18
18
|
import type { RuleViolation, LiveUpdateEvent } from '@sigloch/contracts/harness';
|
|
19
19
|
import type { Graph } from '@sigloch/graph-api-core';
|
|
20
|
-
export interface GatePanel {
|
|
21
|
-
id: string;
|
|
22
|
-
label: string;
|
|
23
|
-
passed: boolean;
|
|
24
|
-
score: number;
|
|
25
|
-
/** The blocking elements — the drill-down behind the traffic light. */
|
|
26
|
-
blocking: string[];
|
|
27
|
-
/**
|
|
28
|
-
* Structural completeness (CR-GC-250): ONE value per gate (covered/total) for
|
|
29
|
-
* the panel; `missing[]` is the on-click per-leg drill-down, same mechanic as
|
|
30
|
-
* `blocking`. REQ-completeness-single-value.
|
|
31
|
-
*/
|
|
32
|
-
completeness: ReadinessGate['completeness'];
|
|
33
|
-
}
|
|
34
20
|
export interface ReadinessPanel {
|
|
35
21
|
compliancePct: number;
|
|
36
22
|
totalElements: number;
|
|
37
23
|
elementsWithErrors: number;
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
/** SRR, PDR, CDR, TRR, Bau — reached or not, with the findings that hold each (CR-SM-395). */
|
|
25
|
+
marks: ReadinessMark[];
|
|
40
26
|
}
|
|
41
27
|
export declare function readinessPanel(report: ReadinessReport): ReadinessPanel;
|
|
42
28
|
export interface RecommendationItem {
|
|
@@ -85,11 +71,12 @@ export interface ViolationGroup {
|
|
|
85
71
|
export declare function groupViolations(violations: RuleViolation[], elementLimit?: number): ViolationGroup[];
|
|
86
72
|
export type Freshness = 'live' | 'stale' | 'absent';
|
|
87
73
|
/**
|
|
88
|
-
* Two artifact kinds
|
|
74
|
+
* Two artifact kinds (CR-GC-222):
|
|
89
75
|
* - `render` — a deterministic `docs/views/*.md` projection; stale via file mtime vs graph.
|
|
90
|
-
* - `analysis` — judgment work (FMEA/ConOps/Trade
|
|
91
|
-
*
|
|
92
|
-
* an
|
|
76
|
+
* - `analysis` — judgment work (FMEA/ConOps/Trade/Assumption Review); carried out or not. Its
|
|
77
|
+
* only record is the stamp under `SYS.attributes.analysisFreshness` — there is no
|
|
78
|
+
* "stale" for an analysis (CR-SM-395: the currency computation is gone; it ran
|
|
79
|
+
* without effect in the product path since CR-GC-259).
|
|
93
80
|
*/
|
|
94
81
|
export type ArtifactKind = 'render' | 'analysis';
|
|
95
82
|
/** INCOSE/SE-standard vs graphcode-specific — so non-INCOSE rows are not mislabeled as INCOSE. */
|
|
@@ -103,15 +90,16 @@ export interface ArtifactCatalogEntry {
|
|
|
103
90
|
}
|
|
104
91
|
/**
|
|
105
92
|
* The canonical INCOSE-tab artifact catalog (readiness-artifact-model.md §6). Renders are the
|
|
106
|
-
* deterministic CR-GC-220 views; the
|
|
107
|
-
* (`conops`/`assumption-review`/`fmea`/`trade
|
|
108
|
-
*
|
|
93
|
+
* deterministic CR-GC-220 views; the four analysis ids are exactly contracts' `AnalysisArtifactId`
|
|
94
|
+
* (`conops`/`assumption-review`/`fmea`/`trade`). `irr` is renamed to "Assumption Review" and
|
|
95
|
+
* grouped graphcode-specific (it is not a standard INCOSE artifact).
|
|
96
|
+
*
|
|
97
|
+
* CR-SM-395: the implementation plan is a RENDER. The plan is the set of open CRs, not a stamp;
|
|
98
|
+
* its document (`implplan.md`) is a projection of the CR/MS structure like any other view.
|
|
109
99
|
*/
|
|
110
100
|
export declare const ARTIFACT_CATALOG: readonly ArtifactCatalogEntry[];
|
|
111
101
|
/** Classify a RENDER artifact. `exists` = doc present; `staleVsGraph` = graph changed since render. */
|
|
112
102
|
export declare function artifactFreshness(exists: boolean, staleVsGraph: boolean): Freshness;
|
|
113
|
-
/** Classify an ANALYSIS artifact from its scope-currency — 🟢 current / 🟡 stale / 🔴 absent. */
|
|
114
|
-
export declare function analysisFreshness(currency: CreationCurrency): Freshness;
|
|
115
103
|
export interface ArtifactStatus {
|
|
116
104
|
id: string;
|
|
117
105
|
label: string;
|
|
@@ -119,15 +107,13 @@ export interface ArtifactStatus {
|
|
|
119
107
|
group: ArtifactGroup;
|
|
120
108
|
freshness: Freshness;
|
|
121
109
|
}
|
|
122
|
-
/** One artifact's
|
|
110
|
+
/** One artifact's signal. An analysis row reads `exists` only: the stamp is there, or it is not. */
|
|
123
111
|
export interface ArtifactSignal {
|
|
124
112
|
id: string;
|
|
125
|
-
/** RENDER: a materialized doc is present. */
|
|
113
|
+
/** RENDER: a materialized doc is present. ANALYSIS: the analysis is on record (stamp present). */
|
|
126
114
|
exists?: boolean;
|
|
127
|
-
/** RENDER: the graph changed since the doc was generated (mtime). */
|
|
115
|
+
/** RENDER only: the graph changed since the doc was generated (mtime). */
|
|
128
116
|
staleVsGraph?: boolean;
|
|
129
|
-
/** ANALYSIS: scope-currency of the judgment work (the ONLY signal read for analysis rows). */
|
|
130
|
-
currency?: CreationCurrency;
|
|
131
117
|
}
|
|
132
118
|
export interface ArtifactsPanel {
|
|
133
119
|
artifacts: ArtifactStatus[];
|
|
@@ -136,39 +122,19 @@ export interface ArtifactsPanel {
|
|
|
136
122
|
absentCount: number;
|
|
137
123
|
}
|
|
138
124
|
/**
|
|
139
|
-
* Shape artifact
|
|
140
|
-
* kind/group/label; the kind decides
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
125
|
+
* Shape artifact signals into the tab view-model. The catalog decides each row's
|
|
126
|
+
* kind/group/label; the kind decides how it is classified — a render row by mtime, an analysis
|
|
127
|
+
* row by presence alone (`staleVsGraph` is ignored there: an analysis is NEVER classified by
|
|
128
|
+
* mtime). Renderer groups by `group` to show "INCOSE / SE-standard" and "graphcode-specific"
|
|
129
|
+
* separately.
|
|
144
130
|
*/
|
|
145
131
|
export declare function artifactsPanel(signals: ArtifactSignal[]): ArtifactsPanel;
|
|
146
132
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
133
|
+
* The signals of the four analysis artifacts, read off the graph: an analysis is on record iff
|
|
134
|
+
* its stamp under `SYS.attributes.analysisFreshness.<id>` is valid (CR-SM-227 — the same
|
|
135
|
+
* condition AF-01..04 check). No comparison against the live graphVersion (CR-SM-395).
|
|
150
136
|
*/
|
|
151
|
-
export declare function
|
|
152
|
-
id: string;
|
|
153
|
-
currency: CreationCurrency;
|
|
154
|
-
}>): CreationCurrencyProvider;
|
|
155
|
-
/**
|
|
156
|
-
* Real scope-currency for the 5 analysis artifacts (CR-SM-227) — replaces the
|
|
157
|
-
* `ABSENT_CREATION_PROVIDER` placeholder (CR-GVE-225 F10: GVE showed `absent`
|
|
158
|
-
* correctly because no scope-currency check existed yet). Reads the
|
|
159
|
-
* `graphVersion` freshness stamp AF-01..05 (contracts) check for presence of
|
|
160
|
-
* (`SYS.attributes.analysisFreshness.<id>`) and classifies it against the
|
|
161
|
-
* live graphVersion: missing/invalid stamp → absent; stamp older than the
|
|
162
|
-
* current graphVersion → stale; stamp at (or ahead of) it → current.
|
|
163
|
-
* Grober Graph-Versions-Stempel only, no scope-hash — CR-SM-227's declared
|
|
164
|
-
* first-cut limitation ("Achtung" section), not full scope-precise staleness.
|
|
165
|
-
*/
|
|
166
|
-
export declare function computeAnalysisCurrency(graph: Pick<Graph, 'nodes'>, currentGraphVersion: number): Array<{
|
|
167
|
-
id: string;
|
|
168
|
-
currency: CreationCurrency;
|
|
169
|
-
}>;
|
|
170
|
-
/** Convenience: build the real CreationCurrencyProvider straight off the graph + its live version. */
|
|
171
|
-
export declare function analysisCreationCurrencyProvider(graph: Pick<Graph, 'nodes'>, currentGraphVersion: number): CreationCurrencyProvider;
|
|
137
|
+
export declare function analysisSignals(graph: Pick<Graph, 'nodes'>): ArtifactSignal[];
|
|
172
138
|
export interface ImpactPanel {
|
|
173
139
|
root: string;
|
|
174
140
|
blastRadiusNodes: number;
|
package/dist/panels.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { AnalysisArtifactId, AnalysisFreshnessStampSchema } from '@sigloch/contracts/se';
|
|
2
|
-
function toGatePanel(g) {
|
|
3
|
-
return { id: g.id, label: g.label, passed: g.passed, score: g.score, blocking: g.blocking, completeness: g.completeness };
|
|
4
|
-
}
|
|
5
2
|
export function readinessPanel(report) {
|
|
6
3
|
return {
|
|
7
4
|
compliancePct: Math.round(report.compliance.score * 1000) / 10,
|
|
8
5
|
totalElements: report.compliance.totalElements,
|
|
9
6
|
elementsWithErrors: report.compliance.elementsWithErrors,
|
|
10
|
-
|
|
11
|
-
implGates: report.implGates.map(toGatePanel),
|
|
7
|
+
marks: report.marks,
|
|
12
8
|
};
|
|
13
9
|
}
|
|
14
10
|
const SEVERITY_RANK = { error: 0, warning: 1, info: 2 };
|
|
@@ -70,9 +66,12 @@ export function groupViolations(violations, elementLimit = 10) {
|
|
|
70
66
|
}
|
|
71
67
|
/**
|
|
72
68
|
* The canonical INCOSE-tab artifact catalog (readiness-artifact-model.md §6). Renders are the
|
|
73
|
-
* deterministic CR-GC-220 views; the
|
|
74
|
-
* (`conops`/`assumption-review`/`fmea`/`trade
|
|
75
|
-
*
|
|
69
|
+
* deterministic CR-GC-220 views; the four analysis ids are exactly contracts' `AnalysisArtifactId`
|
|
70
|
+
* (`conops`/`assumption-review`/`fmea`/`trade`). `irr` is renamed to "Assumption Review" and
|
|
71
|
+
* grouped graphcode-specific (it is not a standard INCOSE artifact).
|
|
72
|
+
*
|
|
73
|
+
* CR-SM-395: the implementation plan is a RENDER. The plan is the set of open CRs, not a stamp;
|
|
74
|
+
* its document (`implplan.md`) is a projection of the CR/MS structure like any other view.
|
|
76
75
|
*/
|
|
77
76
|
export const ARTIFACT_CATALOG = [
|
|
78
77
|
// Renders — INCOSE/SE-standard deterministic views (mtime-classified).
|
|
@@ -86,11 +85,11 @@ export const ARTIFACT_CATALOG = [
|
|
|
86
85
|
{ id: 'intplan', label: 'Integration & Test Plan', kind: 'render', group: 'incose' },
|
|
87
86
|
{ id: 'changelog', label: 'Change Log', kind: 'render', group: 'incose' },
|
|
88
87
|
{ id: 'references', label: 'Requirements Traceability', kind: 'render', group: 'incose' },
|
|
89
|
-
|
|
88
|
+
{ id: 'implplan', label: 'Implementation Plan', kind: 'render', group: 'incose' },
|
|
89
|
+
// Analyses — judgment work (carried out or not, NEVER mtime).
|
|
90
90
|
{ id: 'conops', label: 'Concept of Operations', kind: 'analysis', group: 'incose' },
|
|
91
91
|
{ id: 'fmea', label: 'FMEA', kind: 'analysis', group: 'incose' },
|
|
92
92
|
{ id: 'trade', label: 'Trade Study', kind: 'analysis', group: 'incose' },
|
|
93
|
-
{ id: 'implplan', label: 'Implementation Plan', kind: 'analysis', group: 'incose' },
|
|
94
93
|
{ id: 'assumption-review', label: 'Assumption Review', kind: 'analysis', group: 'graphcode' },
|
|
95
94
|
];
|
|
96
95
|
/** Classify a RENDER artifact. `exists` = doc present; `staleVsGraph` = graph changed since render. */
|
|
@@ -99,25 +98,19 @@ export function artifactFreshness(exists, staleVsGraph) {
|
|
|
99
98
|
return 'absent';
|
|
100
99
|
return staleVsGraph ? 'stale' : 'live';
|
|
101
100
|
}
|
|
102
|
-
/** Classify an ANALYSIS artifact from its scope-currency — 🟢 current / 🟡 stale / 🔴 absent. */
|
|
103
|
-
export function analysisFreshness(currency) {
|
|
104
|
-
return currency === 'current' ? 'live' : currency === 'stale' ? 'stale' : 'absent';
|
|
105
|
-
}
|
|
106
101
|
/**
|
|
107
|
-
* Shape artifact
|
|
108
|
-
* kind/group/label; the kind decides
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
102
|
+
* Shape artifact signals into the tab view-model. The catalog decides each row's
|
|
103
|
+
* kind/group/label; the kind decides how it is classified — a render row by mtime, an analysis
|
|
104
|
+
* row by presence alone (`staleVsGraph` is ignored there: an analysis is NEVER classified by
|
|
105
|
+
* mtime). Renderer groups by `group` to show "INCOSE / SE-standard" and "graphcode-specific"
|
|
106
|
+
* separately.
|
|
112
107
|
*/
|
|
113
108
|
export function artifactsPanel(signals) {
|
|
114
109
|
const byId = new Map(ARTIFACT_CATALOG.map((e) => [e.id, e]));
|
|
115
110
|
const statuses = signals.map((s) => {
|
|
116
111
|
const entry = byId.get(s.id);
|
|
117
112
|
const kind = entry?.kind ?? 'render';
|
|
118
|
-
const freshness = kind === '
|
|
119
|
-
? analysisFreshness(s.currency ?? 'absent')
|
|
120
|
-
: artifactFreshness(s.exists ?? false, s.staleVsGraph ?? false);
|
|
113
|
+
const freshness = artifactFreshness(s.exists ?? false, kind === 'render' && (s.staleVsGraph ?? false));
|
|
121
114
|
return { id: s.id, label: entry?.label ?? s.id, kind, group: entry?.group ?? 'graphcode', freshness };
|
|
122
115
|
});
|
|
123
116
|
return {
|
|
@@ -128,41 +121,14 @@ export function artifactsPanel(signals) {
|
|
|
128
121
|
};
|
|
129
122
|
}
|
|
130
123
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*/
|
|
135
|
-
export function creationCurrencyProvider(analysis) {
|
|
136
|
-
const m = new Map(analysis.map((a) => [a.id, a.currency]));
|
|
137
|
-
return (creation) => m.get(creation) ?? 'absent';
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Real scope-currency for the 5 analysis artifacts (CR-SM-227) — replaces the
|
|
141
|
-
* `ABSENT_CREATION_PROVIDER` placeholder (CR-GVE-225 F10: GVE showed `absent`
|
|
142
|
-
* correctly because no scope-currency check existed yet). Reads the
|
|
143
|
-
* `graphVersion` freshness stamp AF-01..05 (contracts) check for presence of
|
|
144
|
-
* (`SYS.attributes.analysisFreshness.<id>`) and classifies it against the
|
|
145
|
-
* live graphVersion: missing/invalid stamp → absent; stamp older than the
|
|
146
|
-
* current graphVersion → stale; stamp at (or ahead of) it → current.
|
|
147
|
-
* Grober Graph-Versions-Stempel only, no scope-hash — CR-SM-227's declared
|
|
148
|
-
* first-cut limitation ("Achtung" section), not full scope-precise staleness.
|
|
124
|
+
* The signals of the four analysis artifacts, read off the graph: an analysis is on record iff
|
|
125
|
+
* its stamp under `SYS.attributes.analysisFreshness.<id>` is valid (CR-SM-227 — the same
|
|
126
|
+
* condition AF-01..04 check). No comparison against the live graphVersion (CR-SM-395).
|
|
149
127
|
*/
|
|
150
|
-
export function
|
|
128
|
+
export function analysisSignals(graph) {
|
|
151
129
|
const sys = graph.nodes.find((n) => n.type === 'SYS');
|
|
152
130
|
const stamps = (sys?.attributes?.analysisFreshness ?? {});
|
|
153
|
-
return AnalysisArtifactId.options.map((id) => {
|
|
154
|
-
const parsed = AnalysisFreshnessStampSchema.safeParse(stamps[id]);
|
|
155
|
-
const currency = !parsed.success
|
|
156
|
-
? 'absent'
|
|
157
|
-
: parsed.data.graphVersion >= currentGraphVersion
|
|
158
|
-
? 'current'
|
|
159
|
-
: 'stale';
|
|
160
|
-
return { id, currency };
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
/** Convenience: build the real CreationCurrencyProvider straight off the graph + its live version. */
|
|
164
|
-
export function analysisCreationCurrencyProvider(graph, currentGraphVersion) {
|
|
165
|
-
return creationCurrencyProvider(computeAnalysisCurrency(graph, currentGraphVersion));
|
|
131
|
+
return AnalysisArtifactId.options.map((id) => ({ id, exists: AnalysisFreshnessStampSchema.safeParse(stamps[id]).success }));
|
|
166
132
|
}
|
|
167
133
|
export function impactPanel(impact) {
|
|
168
134
|
return { root: impact.rootId, blastRadiusNodes: impact.nodeCount, blastRadiusEdges: impact.edgeCount };
|
|
@@ -182,7 +148,7 @@ export function healthPanel(health) {
|
|
|
182
148
|
const DOMAIN_TO_PANELS = {
|
|
183
149
|
graph: ['graph', 'impact', 'artifacts'],
|
|
184
150
|
rules: ['recommendations', 'readiness'],
|
|
185
|
-
readiness: ['readiness', '
|
|
151
|
+
readiness: ['readiness', 'artifacts'],
|
|
186
152
|
suggestions: ['recommendations'],
|
|
187
153
|
};
|
|
188
154
|
/** Which panel ids a viewer should refresh for this invalidation event. */
|
package/dist/readiness.d.ts
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Readiness — compliance and the marks (CR-GC-107, CR-GC-125; rebuilt CR-SM-395).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the in-repo deliverable that proves readiness is measured against the FAMILY
|
|
8
|
-
* rule base, NOT the aimprove predecessor rules (BQ-2.0.0 / INCOSE-style
|
|
9
|
-
* BQ-06/BQ-02). The foreign BQ path is explicitly NOT used; it is removed from
|
|
10
|
-
* family readiness scoring.
|
|
4
|
+
* Computed purely from rule violations of `@sigloch/contracts` (the family rule base, never a
|
|
5
|
+
* foreign rule set) and the stage each rule carries at its definition. There is no gate
|
|
6
|
+
* computation of its own in this package any more.
|
|
11
7
|
*
|
|
12
|
-
*
|
|
8
|
+
* THE MARKS. The rule catalog orders its sets in twelve stages (`STAGE_SETS`); a mark lies
|
|
9
|
+
* between two stages: SRR after stage 4, PDR after 7, CDR after 8, TRR after 9, Bau after 12.
|
|
10
|
+
* A mark is REACHED iff no finding holds it. A finding holds a mark when its rule lies at or
|
|
11
|
+
* before the mark's stage — or at every stage (`'immer'`) — and it is
|
|
12
|
+
* - an error, or
|
|
13
|
+
* - a finding of an existence rule: a set the stage requires is missing.
|
|
14
|
+
* Other warnings do not hold a mark; "free of warnings" is a separate statement.
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* (SRS/SDD/RTM/ICD/…) is a DETERMINISTIC PROJECTION of it (CR-GC-220) — not a
|
|
18
|
-
* separate hand-authored deliverable. CREATION artifacts (ConOps, FMEA,
|
|
19
|
-
* Assumption Review, Trade, Impl Plan) ARE in scope as lightweight judgment
|
|
20
|
-
* inputs and gate preconditions — tracked in the artifact tab (CR-GC-222) and
|
|
21
|
-
* required by the gates (CR-GC-221). Only ASIL-D-grade evidence (full FMEA
|
|
22
|
-
* S/O/D rigor, ASIL isolation proof) stays out of scope — graphcode carries no
|
|
23
|
-
* `asil`-rated elements. Refined model: docs/proposals/readiness-artifact-model.md §3.
|
|
16
|
+
* The mark Bau additionally is held by every warning of the stages 10 to 12 (plan, binding,
|
|
17
|
+
* conformance): those findings ARE the work list of the build, and the mark reads "nothing left
|
|
18
|
+
* to plan, bind or reconcile". It opens again with every change.
|
|
24
19
|
*
|
|
25
|
-
*
|
|
26
|
-
* DISJOINT + EXHAUSTIVE partition of the element-level V3_RULES. A gate is
|
|
27
|
-
* `passed` iff none of its rules fire an error-severity violation; warnings/
|
|
28
|
-
* info are advisory (`open`). The four gates collectively == element
|
|
29
|
-
* compliance, so nothing is measured outside V3_RULES.
|
|
20
|
+
* A NOTE (severity `info`, e.g. MS-03, VR-01) holds no mark, at no stage (CR-SM-396).
|
|
30
21
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* error-severity violation. Derived from the MS nodes + element status, i.e.
|
|
36
|
-
* the MS-01/MS-02 milestone-rule concerns — the remaining 2 of the 17 rules.
|
|
22
|
+
* AN ACCEPTED FINDING HOLDS ITS MARK (CR-SM-396). The rules keep firing on a finding the human
|
|
23
|
+
* accepted with a reason (`acceptedFindings`), and the marks are computed from all of them: an
|
|
24
|
+
* accepted AF-05 ("unbuilt, no order") still holds Bau. Acceptance takes a finding out of the
|
|
25
|
+
* work focus; it is no way to read a mark as reached. Callers pass the findings UNFILTERED.
|
|
37
26
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
27
|
+
* Why "0 of 0" cannot read as reached: every mandatory set is demanded by an existence rule of
|
|
28
|
+
* the stage before it (no system → R-33, no use case → R-17, …). An empty set is therefore either
|
|
29
|
+
* voluntary, or the existence rule before it is open and holds the mark. Stage 8 (schema) carries
|
|
30
|
+
* no existence rule of its own (CR-SM-396): a data flow without a schema is a grammar error
|
|
31
|
+
* (R-18, every stage), and no data flow at all is held by R-31 (stage 6).
|
|
32
|
+
*
|
|
33
|
+
* What is gone with CR-SM-395: the three-valued gate state and its labels, the gate score, the
|
|
34
|
+
* gate completeness and its legs (`COMPLETENESS_SLICES`), the four build gates bound to milestone
|
|
35
|
+
* ids, the required creations per gate and the currency of analysis artifacts. The assignment
|
|
36
|
+
* rule → mark is `ALL_RULE_DEFS[].mark` in contracts, not a list here.
|
|
37
|
+
*
|
|
38
|
+
* Host/dashboard wiring (panels) lives in `panels.ts`; this module provides the scoring
|
|
39
|
+
* primitive only — no dashboard, no renderer.
|
|
40
40
|
*
|
|
41
41
|
* @author andreas@siglochconsulting
|
|
42
42
|
*/
|
|
43
43
|
import type { Graph } from '@sigloch/graph-api-core';
|
|
44
|
+
import { type MarkType } from '@sigloch/contracts/se';
|
|
44
45
|
import type { RuleViolation } from '@sigloch/contracts/harness';
|
|
45
|
-
import { type GateCompleteness } from './readiness-completeness.js';
|
|
46
46
|
/**
|
|
47
47
|
* A readiness dimension score in [0, 1].
|
|
48
48
|
* 0 = fully non-compliant, 1 = fully compliant.
|
|
@@ -59,111 +59,30 @@ export interface ReadinessDimension {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* INCOSE artifact scope for the system under governance.
|
|
62
|
-
* `lean` — graph = SSOT; renders are deterministic projections,
|
|
63
|
-
*
|
|
64
|
-
* refined CR-GC-226). NOT "no artifacts".
|
|
62
|
+
* `lean` — graph = SSOT; renders are deterministic projections, analyses are lightweight
|
|
63
|
+
* judgment inputs (graphcode's stance, refined CR-GC-226). NOT "no artifacts".
|
|
65
64
|
* `full` — ASIL-D-grade evidence (full FMEA S/O/D rigor, ASIL isolation) (not graphcode).
|
|
66
65
|
*/
|
|
67
66
|
export type IncoseScope = 'lean' | 'full';
|
|
68
|
-
/** graphcode's declared scope
|
|
67
|
+
/** graphcode's declared scope. */
|
|
69
68
|
export declare const GRAPHCODE_INCOSE_SCOPE: IncoseScope;
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* rule lands here without an edit. Before, this was `['MS-01','MS-02']` written out,
|
|
77
|
-
* frozen at the two rules that existed when it was authored.
|
|
78
|
-
*/
|
|
79
|
-
export declare const IMPL_GATE_RULES: readonly string[];
|
|
80
|
-
/**
|
|
81
|
-
* Phase-Readiness gates (INCOSE technical reviews) → the rules each gate owns.
|
|
82
|
-
* DISJOINT + EXHAUSTIVE over the rule catalog; asserted by TEST-readiness-model
|
|
83
|
-
* against SE_DESCRIPTOR at runtime, never a hardcoded count.
|
|
84
|
-
*
|
|
85
|
-
* DERIVED from `RULE_TO_PHASE` (CR-GC-312). This used to be a hand-written literal —
|
|
86
|
-
* and contracts had already replaced it: CR-SM-226 removed `emergentPhase`/`phaseScore`
|
|
87
|
-
* as "a third, undocumented phase construct next to RULE_TO_DIMENSION and the (then
|
|
88
|
-
* still ad-hoc) phase-gate grouping", introducing `RULE_TO_PHASE` as the one
|
|
89
|
-
* replacement, "exported so no consumer reinvents it". That ad-hoc grouping was THIS
|
|
90
|
-
* object, and the replacement was never carried through here.
|
|
91
|
-
*
|
|
92
|
-
* The cost of leaving it: the literal and `RULE_TO_PHASE` had drifted apart on 21 of
|
|
93
|
-
* 33 shared rules — R-01 SRR vs TRR, R-08 TRR vs PDR, RD-01..03 CDR vs SRR, and so on.
|
|
94
|
-
* Two live gate assignments for the same rules, and readiness was scored against the
|
|
95
|
-
* frozen one. Deriving moves those rules onto the assignment contracts declares; gate
|
|
96
|
-
* scores change accordingly, which is the correction, not a side effect.
|
|
97
|
-
*/
|
|
98
|
-
export declare const PHASE_GATE_RULES: Record<string, readonly string[]>;
|
|
99
|
-
/** Human labels for the phase gates. */
|
|
100
|
-
export declare const PHASE_GATE_LABELS: Record<string, string>;
|
|
101
|
-
/**
|
|
102
|
-
* Creation artifacts each phase gate requires (CR-GC-221): the judgment work
|
|
103
|
-
* ("rule-green ≠ analysis-done") a rule-clean structure cannot prove was ever
|
|
104
|
-
* done. A gate is only `passed` if its required creations are 🟢-current.
|
|
105
|
-
* TRR carries none (tests are inline in the graph, not a separate creation).
|
|
106
|
-
*/
|
|
107
|
-
export declare const PHASE_GATE_CREATIONS: Record<string, readonly string[]>;
|
|
108
|
-
/** Impl gate → the phase whose creations it inherits as an anti-vacuous-green precondition. */
|
|
109
|
-
export declare const IMPL_GATE_PHASE: Record<string, string>;
|
|
110
|
-
/** Currency of a creation artifact: 🟢 current / 🟡 stale / 🔴 absent. */
|
|
111
|
-
export type CreationCurrency = 'current' | 'stale' | 'absent';
|
|
112
|
-
/**
|
|
113
|
-
* Resolves whether a creation artifact is current/stale/absent (CR-GC-221 = the
|
|
114
|
-
* INTERFACE; CR-GC-222's classifier supplies the real implementation). When no
|
|
115
|
-
* provider is passed to `computeReadiness`, creation enforcement is OFF
|
|
116
|
-
* (back-compat) — the gates still report their `creationArtifacts`, but currency
|
|
117
|
-
* is not checked until 222 wires a provider in.
|
|
118
|
-
*/
|
|
119
|
-
export type CreationCurrencyProvider = (creation: string) => CreationCurrency;
|
|
120
|
-
/** Default stub provider (CR-GC-221, "Default 🔴"): everything absent until CR-GC-222 lands. */
|
|
121
|
-
export declare const ABSENT_CREATION_PROVIDER: CreationCurrencyProvider;
|
|
122
|
-
/** Format a "<Creation> not performed (<gate> creation)" blocking message.
|
|
123
|
-
* Exported so the help layer (CR-GC-228) can identify creation blockers in
|
|
124
|
-
* `ReadinessGate.blocking[]` without re-deriving the format (single source). */
|
|
125
|
-
export declare function creationBlockingMsg(creation: string, gateId: string): string;
|
|
126
|
-
/**
|
|
127
|
-
* Implementation-Readiness gates (program/build acceptance) → milestone tier.
|
|
128
|
-
* Ready iff every CR assigned to the MS is done + the MS scope is error-clean.
|
|
129
|
-
*/
|
|
130
|
-
export declare const IMPL_GATE_MILESTONES: Record<string, {
|
|
131
|
-
ms: string;
|
|
132
|
-
label: string;
|
|
133
|
-
}>;
|
|
134
|
-
/**
|
|
135
|
-
* A single readiness gate outcome.
|
|
136
|
-
* `passed` is the hard verdict (no blocking/error item); `score` ∈ [0,1] is a
|
|
137
|
-
* continuous maturity indicator; `open` items are advisory (warnings/CR debt).
|
|
138
|
-
*/
|
|
139
|
-
export interface ReadinessGate {
|
|
140
|
-
/** Gate id — SRR/PDR/CDR/TRR (phase) or SAR/FCA/SVR/FRR (impl). */
|
|
141
|
-
id: string;
|
|
142
|
-
/** Human-readable review name. */
|
|
69
|
+
/** Human labels for the marks. */
|
|
70
|
+
export declare const MARK_LABELS: Readonly<Record<MarkType, string>>;
|
|
71
|
+
/** One mark: reached or not, and the findings that hold it. */
|
|
72
|
+
export interface ReadinessMark {
|
|
73
|
+
id: MarkType;
|
|
74
|
+
/** Human-readable name. */
|
|
143
75
|
label: string;
|
|
144
|
-
/** True iff
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
/** Blocking items that hold the gate closed (errors / open CRs / scope errors). */
|
|
149
|
-
blocking: string[];
|
|
150
|
-
/** Advisory items surfaced but not blocking (warnings, info, scope notes). */
|
|
151
|
-
open: string[];
|
|
152
|
-
/** Creation artifacts this gate requires (CR-GC-221) — judgment work, not rules. */
|
|
153
|
-
creationArtifacts: string[];
|
|
154
|
-
/**
|
|
155
|
-
* Structural completeness of the derivation chain owned by this gate
|
|
156
|
-
* (CR-GC-250) — orthogonal to violation severity. `covered/total` over the
|
|
157
|
-
* DRIVING population, so absence counts (0 FCHAIN → all UCs incomplete → SRR
|
|
158
|
-
* red). One value per gate for the dashboard; `missing[]` is the on-click
|
|
159
|
-
* per-leg detail. A gate cannot pass while `covered < total`. Impl gates carry
|
|
160
|
-
* a neutral `{0,0,[]}` (completeness is a phase-gate concept).
|
|
161
|
-
*/
|
|
162
|
-
completeness: GateCompleteness;
|
|
76
|
+
/** True iff `holding` is empty. */
|
|
77
|
+
reached: boolean;
|
|
78
|
+
/** The findings that hold the mark — the answer to "why not". */
|
|
79
|
+
holding: RuleViolation[];
|
|
163
80
|
}
|
|
81
|
+
/** The marks in order, each with the findings that hold it. */
|
|
82
|
+
export declare function computeMarks(violations: RuleViolation[]): ReadinessMark[];
|
|
164
83
|
/**
|
|
165
|
-
* Full readiness snapshot derived from
|
|
166
|
-
* All violation ruleIds are contracts rule-IDs
|
|
84
|
+
* Full readiness snapshot derived from rule violations.
|
|
85
|
+
* All violation ruleIds are contracts rule-IDs — never a foreign rule set.
|
|
167
86
|
*/
|
|
168
87
|
export interface ReadinessReport {
|
|
169
88
|
/**
|
|
@@ -173,48 +92,32 @@ export interface ReadinessReport {
|
|
|
173
92
|
compliance: ReadinessDimension;
|
|
174
93
|
/** Declared INCOSE artifact scope for this system (graphcode = `lean`). */
|
|
175
94
|
incoseScope: IncoseScope;
|
|
176
|
-
/**
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
implGates: ReadinessGate[];
|
|
180
|
-
/**
|
|
181
|
-
* Per-contracts-rule-ID violation counts. Keys are exactly the
|
|
182
|
-
* `SE_DESCRIPTOR.rules` members that fired. Foreign BQ-* IDs cannot appear —
|
|
183
|
-
* the scorer delegates exclusively to the V3 rule engine.
|
|
184
|
-
*/
|
|
95
|
+
/** The marks SRR, PDR, CDR, TRR, Bau — in order. */
|
|
96
|
+
marks: ReadinessMark[];
|
|
97
|
+
/** Per-contracts-rule-ID violation counts. */
|
|
185
98
|
violationsByRule: Record<string, number>;
|
|
186
99
|
/** All raw violations, ordered by severity (error → warning → info). */
|
|
187
100
|
violations: RuleViolation[];
|
|
188
|
-
/**
|
|
189
|
-
* Whether creation-artifact currency was ENFORCED in this report (CR-GC-259).
|
|
190
|
-
* `off` means no `CreationCurrencyProvider` was supplied, so every gate's
|
|
191
|
-
* `creationArtifacts` was REPORTED but never checked — a gate can read
|
|
192
|
-
* `passed` with a never-performed FMEA/ConOps behind it. Made explicit because
|
|
193
|
-
* a silently-unenforced gate is the same false-green class CR-GC-250 closed:
|
|
194
|
-
* a consumer cannot distinguish "no creation blockers" from "not checked".
|
|
195
|
-
* graphcode itself has no classifier yet (the real provider is fed by the
|
|
196
|
-
* dashboard), so the product path reports `off` until one lands.
|
|
197
|
-
*/
|
|
198
|
-
creationEnforcement: 'on' | 'off';
|
|
199
101
|
/** Timestamp (ISO 8601) when this report was computed. */
|
|
200
102
|
computedAt: string;
|
|
201
103
|
}
|
|
202
104
|
/**
|
|
203
105
|
* Summary projection of a ReadinessReport (CR-GC-203 item 2): drops the heavy
|
|
204
|
-
* per-element lists — `violations` and every
|
|
205
|
-
*
|
|
106
|
+
* per-element lists — `violations` and every mark's `holding` — while keeping
|
|
107
|
+
* `reached`, counts and `violationsByRule`. `graph_readiness` returns this
|
|
206
108
|
* by default; `detail:true` returns the full lists. On a fully-red graph the
|
|
207
|
-
* full report inlined every
|
|
109
|
+
* full report inlined every finding (86k+ chars, past the MCP tool
|
|
208
110
|
* result limit) forcing a file-spill; the summary stays small.
|
|
209
111
|
*/
|
|
210
112
|
export declare function summarizeReadiness(report: ReadinessReport): ReadinessReport;
|
|
211
113
|
/**
|
|
212
114
|
* Compute readiness from a violations array + the full graph (nodes + edges).
|
|
213
115
|
*
|
|
214
|
-
* @param violations - from `harness.evaluateRules()` (contracts
|
|
116
|
+
* @param violations - from `harness.evaluateRules()` (contracts rule IDs), UNFILTERED: a finding
|
|
117
|
+
* the human accepted with a reason still holds its mark (CR-SM-396).
|
|
215
118
|
* @param graph - from `harness.getGraph()` (elements + traces)
|
|
216
119
|
*/
|
|
217
|
-
export declare function computeReadiness(violations: RuleViolation[], graph: Pick<Graph, 'nodes' | 'edges'
|
|
120
|
+
export declare function computeReadiness(violations: RuleViolation[], graph: Pick<Graph, 'nodes' | 'edges'>): ReadinessReport;
|
|
218
121
|
/**
|
|
219
122
|
* Convenience wrapper: runs `evaluateRules()` + `getGraph()` on the harness
|
|
220
123
|
* and returns the full ReadinessReport.
|
package/dist/readiness.js
CHANGED
|
@@ -1,278 +1,68 @@
|
|
|
1
1
|
import { SE_DESCRIPTOR } from '@sigloch/graph-api-core';
|
|
2
|
-
import { CODE_CONFORMANCE_RULES,
|
|
3
|
-
|
|
4
|
-
/** graphcode's declared scope — see the module header for the rationale. */
|
|
2
|
+
import { ALL_RULE_DEFS, CODE_CONFORMANCE_RULES, Mark, MARK_STAGE } from '@sigloch/contracts/se';
|
|
3
|
+
/** graphcode's declared scope. */
|
|
5
4
|
export const GRAPHCODE_INCOSE_SCOPE = 'lean';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* Derived from the prefix over the contracts catalog (CR-GC-312) — a new `MS-`/`CR-R`
|
|
12
|
-
* rule lands here without an edit. Before, this was `['MS-01','MS-02']` written out,
|
|
13
|
-
* frozen at the two rules that existed when it was authored.
|
|
14
|
-
*/
|
|
15
|
-
export const IMPL_GATE_RULES = Object.keys(RULE_TO_PHASE)
|
|
16
|
-
.filter((id) => /^(MS-|CR-R)/.test(id))
|
|
17
|
-
.sort();
|
|
18
|
-
/**
|
|
19
|
-
* `RULE_TO_PHASE` covers the whole catalog including the CODING profile (`BQ-` and
|
|
20
|
-
* `ND-` prefixes). The readiness model is the SE profile only — `SE_DESCRIPTOR.rules`
|
|
21
|
-
* deliberately excludes those, so a gate must not claim one and then never see it fire.
|
|
22
|
-
*/
|
|
23
|
-
const isCodingProfile = (id) => /^(BQ-|ND-)/.test(id);
|
|
24
|
-
/**
|
|
25
|
-
* Which conformance rule sits on which gate. RC-* are deliberately outside
|
|
26
|
-
* `ALL_RULE_DEFS` (they need `CodeFacts` from a repo checkout, which contracts has no
|
|
27
|
-
* access to), so `RULE_TO_PHASE` cannot carry them and this is the one legitimate
|
|
28
|
-
* local addition.
|
|
29
|
-
*
|
|
30
|
-
* Still not hand-picked: each RC rule inherits the gate of the PRESENCE rule it
|
|
31
|
-
* resolves — the Presence/Resolution split contracts documents (R-19/R-20/R-26 declare
|
|
32
|
-
* a binding, RC-01/02/03/04 check it actually resolves on disk). RC-05 (cross-module
|
|
33
|
-
* import drift) has no presence partner; it is module coupling, so it follows R-23.
|
|
34
|
-
*
|
|
35
|
-
* CR-SM-262: RC-06 folgt RC-05 aus demselben Grund. Ein Praesenz-Partner kaeme nicht in
|
|
36
|
-
* Frage — die Regel prueft ausschliesslich `external === true`, und genau diese Knoten sind
|
|
37
|
-
* von R-20 AUSGENOMMEN ("exempt from the R-20 realRef-binding requirement", ELEMENT_ATTRIBUTES).
|
|
38
|
-
* Sie loest also keine Praesenzforderung auf, sondern stellt eine eigene: das Paket, auf das
|
|
39
|
-
* eine externe Bindung zeigt, muss in den dependencies stehen. Ihr Gegenstand ist damit die
|
|
40
|
-
* Paket-Abhaengigkeit, die groebste Form von Modul-Kopplung — dieselbe Kategorie wie RC-05.
|
|
41
|
-
*/
|
|
42
|
-
const RC_PRESENCE_PARTNER = {
|
|
43
|
-
'RC-01': 'R-20', // FUNC realRef → declared symbol
|
|
44
|
-
'RC-02': 'R-19', // testRefs → runnable test cases
|
|
45
|
-
'RC-03': 'R-26', // SCHEMA realRef → declared export
|
|
46
|
-
'RC-04': 'R-26', // SCHEMA realRef parsed at its interface
|
|
47
|
-
'RC-05': 'R-23', // cross-module import the graph does not document
|
|
48
|
-
'RC-06': 'R-23', // external binding on a package the consumer does not depend on
|
|
49
|
-
'RC-07': 'CR-R01', // CR node agrees with docs/cr — the CR presence rule's gate (CR-SM-329)
|
|
50
|
-
'RC-08': 'R-26', // SCHEMA realRef is a Zod schema, not a type (CR-SM-358)
|
|
51
|
-
'RC-09': 'R-26', // SCHEMA parsed only at its modelled interface — code → model (CR-SM-358)
|
|
52
|
-
'RC-10': 'R-23', // MOD without resolvable files — RC-05 is blind there, module coupling (CR-SM-377)
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Phase-Readiness gates (INCOSE technical reviews) → the rules each gate owns.
|
|
56
|
-
* DISJOINT + EXHAUSTIVE over the rule catalog; asserted by TEST-readiness-model
|
|
57
|
-
* against SE_DESCRIPTOR at runtime, never a hardcoded count.
|
|
58
|
-
*
|
|
59
|
-
* DERIVED from `RULE_TO_PHASE` (CR-GC-312). This used to be a hand-written literal —
|
|
60
|
-
* and contracts had already replaced it: CR-SM-226 removed `emergentPhase`/`phaseScore`
|
|
61
|
-
* as "a third, undocumented phase construct next to RULE_TO_DIMENSION and the (then
|
|
62
|
-
* still ad-hoc) phase-gate grouping", introducing `RULE_TO_PHASE` as the one
|
|
63
|
-
* replacement, "exported so no consumer reinvents it". That ad-hoc grouping was THIS
|
|
64
|
-
* object, and the replacement was never carried through here.
|
|
65
|
-
*
|
|
66
|
-
* The cost of leaving it: the literal and `RULE_TO_PHASE` had drifted apart on 21 of
|
|
67
|
-
* 33 shared rules — R-01 SRR vs TRR, R-08 TRR vs PDR, RD-01..03 CDR vs SRR, and so on.
|
|
68
|
-
* Two live gate assignments for the same rules, and readiness was scored against the
|
|
69
|
-
* frozen one. Deriving moves those rules onto the assignment contracts declares; gate
|
|
70
|
-
* scores change accordingly, which is the correction, not a side effect.
|
|
71
|
-
*/
|
|
72
|
-
export const PHASE_GATE_RULES = (() => {
|
|
73
|
-
const implOwned = new Set(IMPL_GATE_RULES);
|
|
74
|
-
const gates = { SRR: [], PDR: [], CDR: [], TRR: [] };
|
|
75
|
-
for (const [ruleId, gate] of Object.entries(RULE_TO_PHASE)) {
|
|
76
|
-
if (implOwned.has(ruleId))
|
|
77
|
-
continue; // the impl axis owns these
|
|
78
|
-
if (isCodingProfile(ruleId))
|
|
79
|
-
continue; // not part of the SE readiness model
|
|
80
|
-
gates[gate]?.push(ruleId);
|
|
81
|
-
}
|
|
82
|
-
for (const [rcId, partner] of Object.entries(RC_PRESENCE_PARTNER)) {
|
|
83
|
-
const gate = RULE_TO_PHASE[partner];
|
|
84
|
-
if (gate)
|
|
85
|
-
gates[gate].push(rcId);
|
|
86
|
-
}
|
|
87
|
-
for (const ids of Object.values(gates))
|
|
88
|
-
ids.sort();
|
|
89
|
-
return gates;
|
|
90
|
-
})();
|
|
91
|
-
/** Human labels for the phase gates. */
|
|
92
|
-
export const PHASE_GATE_LABELS = {
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Marks (CR-SM-395)
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
/** Human labels for the marks. */
|
|
9
|
+
export const MARK_LABELS = {
|
|
93
10
|
SRR: 'System Requirements Review',
|
|
94
11
|
PDR: 'Preliminary Design Review',
|
|
95
12
|
CDR: 'Critical Design Review',
|
|
96
13
|
TRR: 'Test Readiness Review',
|
|
14
|
+
Bau: 'Bau',
|
|
97
15
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
118
|
-
/** Impl gate → the phase whose creations it inherits as an anti-vacuous-green precondition. */
|
|
119
|
-
export const IMPL_GATE_PHASE = {
|
|
120
|
-
SAR: 'SRR',
|
|
121
|
-
FCA: 'PDR',
|
|
122
|
-
SVR: 'CDR',
|
|
123
|
-
FRR: 'TRR',
|
|
124
|
-
};
|
|
125
|
-
/** Default stub provider (CR-GC-221, "Default 🔴"): everything absent until CR-GC-222 lands. */
|
|
126
|
-
export const ABSENT_CREATION_PROVIDER = () => 'absent';
|
|
127
|
-
/** Format a "<Creation> not performed (<gate> creation)" blocking message.
|
|
128
|
-
* Exported so the help layer (CR-GC-228) can identify creation blockers in
|
|
129
|
-
* `ReadinessGate.blocking[]` without re-deriving the format (single source). */
|
|
130
|
-
export function creationBlockingMsg(creation, gateId) {
|
|
131
|
-
return `${CREATION_LABELS[creation] ?? creation} not performed (${gateId} creation)`;
|
|
16
|
+
const RULES = new Map(ALL_RULE_DEFS.map((r) => [r.id, r]));
|
|
17
|
+
/** Does this finding hold this mark? See the module header for the rule. */
|
|
18
|
+
function holds(v, mark) {
|
|
19
|
+
if (v.severity === 'info')
|
|
20
|
+
return false; // a note holds no mark (CR-SM-396)
|
|
21
|
+
const rule = RULES.get(v.ruleId);
|
|
22
|
+
// A finding of a rule the catalog does not know cannot be placed on the ladder: like `'immer'`.
|
|
23
|
+
const stage = rule?.stage ?? 'immer';
|
|
24
|
+
if (stage !== 'immer' && stage > MARK_STAGE[mark])
|
|
25
|
+
return false;
|
|
26
|
+
if (v.severity === 'error' || rule?.role === 'existence')
|
|
27
|
+
return true;
|
|
28
|
+
return mark === 'Bau' && stage !== 'immer' && stage > MARK_STAGE.TRR;
|
|
29
|
+
}
|
|
30
|
+
/** The marks in order, each with the findings that hold it. */
|
|
31
|
+
export function computeMarks(violations) {
|
|
32
|
+
return Mark.options.map((id) => {
|
|
33
|
+
const holding = violations.filter((v) => holds(v, id));
|
|
34
|
+
return { id, label: MARK_LABELS[id], reached: holding.length === 0, holding };
|
|
35
|
+
});
|
|
132
36
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Implementation-Readiness gates (program/build acceptance) → milestone tier.
|
|
135
|
-
* Ready iff every CR assigned to the MS is done + the MS scope is error-clean.
|
|
136
|
-
*/
|
|
137
|
-
export const IMPL_GATE_MILESTONES = {
|
|
138
|
-
SAR: { ms: 'MS-1-specification', label: 'System Acceptance Review' },
|
|
139
|
-
FCA: { ms: 'MS-2-coding-vv', label: 'Functional Configuration Audit' },
|
|
140
|
-
SVR: { ms: 'MS-3-mvp-readiness', label: 'System Verification Review' },
|
|
141
|
-
FRR: { ms: 'MS-4-mvp2', label: 'Functional Readiness Review' },
|
|
142
|
-
};
|
|
143
37
|
/**
|
|
144
38
|
* Summary projection of a ReadinessReport (CR-GC-203 item 2): drops the heavy
|
|
145
|
-
* per-element lists — `violations` and every
|
|
146
|
-
*
|
|
39
|
+
* per-element lists — `violations` and every mark's `holding` — while keeping
|
|
40
|
+
* `reached`, counts and `violationsByRule`. `graph_readiness` returns this
|
|
147
41
|
* by default; `detail:true` returns the full lists. On a fully-red graph the
|
|
148
|
-
* full report inlined every
|
|
42
|
+
* full report inlined every finding (86k+ chars, past the MCP tool
|
|
149
43
|
* result limit) forcing a file-spill; the summary stays small.
|
|
150
44
|
*/
|
|
151
45
|
export function summarizeReadiness(report) {
|
|
152
|
-
const stripGate = (g) => ({
|
|
153
|
-
...g,
|
|
154
|
-
blocking: [],
|
|
155
|
-
open: [],
|
|
156
|
-
// Keep the one-value covered/total for the dashboard; drop the per-leg drill-down.
|
|
157
|
-
completeness: { ...g.completeness, missing: [] },
|
|
158
|
-
});
|
|
159
46
|
return {
|
|
160
47
|
...report,
|
|
161
|
-
|
|
162
|
-
implGates: report.implGates.map(stripGate),
|
|
48
|
+
marks: report.marks.map((m) => ({ ...m, holding: [] })),
|
|
163
49
|
violations: [],
|
|
164
50
|
};
|
|
165
51
|
}
|
|
166
52
|
// ---------------------------------------------------------------------------
|
|
167
53
|
// Scorer
|
|
168
54
|
// ---------------------------------------------------------------------------
|
|
169
|
-
/** Group violations by element-id for per-element error lookups. */
|
|
170
|
-
function elementsWithErrorSet(violations) {
|
|
171
|
-
return new Set(violations
|
|
172
|
-
.filter((v) => v.severity === 'error' && v.elementId !== undefined)
|
|
173
|
-
.map((v) => v.elementId));
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Score one phase gate from the violations whose ruleId it owns, plus its
|
|
177
|
-
* required creation artifacts (CR-GC-221): a gate is only `passed` if it is
|
|
178
|
-
* rule-clean AND every required creation is 🟢-current. When `currency` is
|
|
179
|
-
* undefined, creation enforcement is OFF (back-compat) — `creationArtifacts`
|
|
180
|
-
* is still reported. Creations affect `passed`/`blocking`, not the rule `score`.
|
|
181
|
-
*/
|
|
182
|
-
function scorePhaseGate(id, ruleIds, violations, creations, graph, currency) {
|
|
183
|
-
const owned = violations.filter((v) => ruleIds.includes(v.ruleId));
|
|
184
|
-
const errors = owned.filter((v) => v.severity === 'error');
|
|
185
|
-
const rulesWithError = new Set(errors.map((v) => v.ruleId));
|
|
186
|
-
const ruleBlocking = errors.map((v) => `${v.ruleId}: ${v.message}`);
|
|
187
|
-
const open = owned.filter((v) => v.severity !== 'error').map((v) => `${v.ruleId}: ${v.message}`);
|
|
188
|
-
// Phase gate requires every creation 🟢-current — stale OR absent blocks.
|
|
189
|
-
const creationBlocking = currency
|
|
190
|
-
? creations.filter((c) => currency(c) !== 'current').map((c) => creationBlockingMsg(c, id))
|
|
191
|
-
: [];
|
|
192
|
-
// Structural completeness (CR-GC-250): one aggregated blocking line, not N warnings.
|
|
193
|
-
// CR-SM-226: reads off the SAME violations array (the rule stream), no re-walk.
|
|
194
|
-
const completeness = scoreCompleteness(id, graph, violations);
|
|
195
|
-
const complete = completeness.covered === completeness.total;
|
|
196
|
-
const completenessBlocking = complete
|
|
197
|
-
? []
|
|
198
|
-
: [`${id} completeness ${completeness.covered}/${completeness.total} — ${completeness.missing.join('; ')}`];
|
|
199
|
-
const blocking = [...ruleBlocking, ...creationBlocking, ...completenessBlocking];
|
|
200
|
-
// Maturity = share of the gate's hard checks with no error, floored by completeness coverage:
|
|
201
|
-
// a gate cannot read green while a mandated chain leg is empty.
|
|
202
|
-
const ruleScore = ruleIds.length > 0 ? (ruleIds.length - rulesWithError.size) / ruleIds.length : 1;
|
|
203
|
-
const completenessRatio = completeness.total > 0 ? completeness.covered / completeness.total : 1;
|
|
204
|
-
return {
|
|
205
|
-
id,
|
|
206
|
-
label: PHASE_GATE_LABELS[id] ?? id,
|
|
207
|
-
passed: rulesWithError.size === 0 && creationBlocking.length === 0 && complete,
|
|
208
|
-
score: Math.min(ruleScore, completenessRatio),
|
|
209
|
-
blocking,
|
|
210
|
-
open,
|
|
211
|
-
creationArtifacts: [...creations],
|
|
212
|
-
completeness,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Score one implementation gate from its milestone's CRs + scope (MS + status),
|
|
217
|
-
* plus an anti-vacuous-green creation check (CR-GC-221): the gate additionally
|
|
218
|
-
* blocks when a creation required by its phase is 🔴 **absent** — even with no
|
|
219
|
-
* representing CR (a never-done FMEA must not pass silently). Only `absent`
|
|
220
|
-
* blocks here (not `stale`); enforcement is off when `currency` is undefined.
|
|
221
|
-
*/
|
|
222
|
-
function scoreImplGate(id, ms, label, graph, elementsWithError, creations, currency) {
|
|
223
|
-
const msNode = graph.nodes.find((n) => n.uid === ms);
|
|
224
|
-
if (!msNode) {
|
|
225
|
-
return {
|
|
226
|
-
id,
|
|
227
|
-
label,
|
|
228
|
-
passed: false,
|
|
229
|
-
score: 0,
|
|
230
|
-
blocking: [`milestone ${ms} missing`],
|
|
231
|
-
open: [],
|
|
232
|
-
creationArtifacts: [...creations],
|
|
233
|
-
completeness: NEUTRAL_COMPLETENESS,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
const nodeById = new Map(graph.nodes.map((n) => [n.uid, n]));
|
|
237
|
-
// CRs assigned to this milestone: CR -relation-> MS (the MS-01 scope semantics).
|
|
238
|
-
const crs = graph.edges
|
|
239
|
-
.filter((e) => e.edgeType === 'relation' && e.targetId === ms)
|
|
240
|
-
.map((e) => nodeById.get(e.sourceId))
|
|
241
|
-
.filter((n) => !!n && n.type === 'CR');
|
|
242
|
-
const openCrs = crs.filter((c) => c.attributes?.status !== 'done');
|
|
243
|
-
// Milestone scope: composed elements (MS -compose-> X) must be error-clean.
|
|
244
|
-
const scopeErrors = graph.edges
|
|
245
|
-
.filter((e) => e.sourceId === ms && e.edgeType === 'compose')
|
|
246
|
-
.map((e) => e.targetId)
|
|
247
|
-
.filter((id2) => elementsWithError.has(id2));
|
|
248
|
-
// Anti-vacuous-green: a phase creation that is 🔴 absent blocks the impl gate
|
|
249
|
-
// even when every CR is done and the scope is clean (the analysis was never done).
|
|
250
|
-
const creationBlocking = currency
|
|
251
|
-
? creations.filter((c) => currency(c) === 'absent').map((c) => creationBlockingMsg(c, id))
|
|
252
|
-
: [];
|
|
253
|
-
const blocking = [
|
|
254
|
-
...openCrs.map((c) => `${c.uid} not done`),
|
|
255
|
-
...scopeErrors.map((id2) => `${id2} has an error-severity violation`),
|
|
256
|
-
...creationBlocking,
|
|
257
|
-
];
|
|
258
|
-
const msStatus = msNode.attributes?.status;
|
|
259
|
-
const score = crs.length > 0
|
|
260
|
-
? (crs.length - openCrs.length) / crs.length
|
|
261
|
-
: msStatus === 'done' || msStatus === 'reviewed' ? 1 : 0;
|
|
262
|
-
return { id, label, passed: blocking.length === 0, score, blocking, open: [], creationArtifacts: [...creations], completeness: NEUTRAL_COMPLETENESS };
|
|
263
|
-
}
|
|
264
55
|
/**
|
|
265
56
|
* Compute readiness from a violations array + the full graph (nodes + edges).
|
|
266
57
|
*
|
|
267
|
-
* @param violations - from `harness.evaluateRules()` (contracts
|
|
58
|
+
* @param violations - from `harness.evaluateRules()` (contracts rule IDs), UNFILTERED: a finding
|
|
59
|
+
* the human accepted with a reason still holds its mark (CR-SM-396).
|
|
268
60
|
* @param graph - from `harness.getGraph()` (elements + traces)
|
|
269
61
|
*/
|
|
270
|
-
export function computeReadiness(violations, graph
|
|
62
|
+
export function computeReadiness(violations, graph) {
|
|
271
63
|
const totalElements = graph.nodes.length;
|
|
272
|
-
const
|
|
273
|
-
const elementsWithErrors = elementsWithError.size;
|
|
64
|
+
const elementsWithErrors = new Set(violations.filter((v) => v.severity === 'error' && v.elementId !== undefined).map((v) => v.elementId)).size;
|
|
274
65
|
const complianceScore = totalElements > 0 ? (totalElements - elementsWithErrors) / totalElements : 1;
|
|
275
|
-
// Violation counts keyed by contracts rule-ID (never BQ-*).
|
|
276
66
|
const violationsByRule = {};
|
|
277
67
|
for (const v of violations) {
|
|
278
68
|
violationsByRule[v.ruleId] = (violationsByRule[v.ruleId] ?? 0) + 1;
|
|
@@ -280,8 +70,6 @@ export function computeReadiness(violations, graph, creationCurrency) {
|
|
|
280
70
|
// Sort: error → warning → info.
|
|
281
71
|
const severityRank = { error: 0, warning: 1, info: 2 };
|
|
282
72
|
const sorted = [...violations].sort((a, b) => (severityRank[a.severity] ?? 3) - (severityRank[b.severity] ?? 3));
|
|
283
|
-
const phaseGates = Object.entries(PHASE_GATE_RULES).map(([id, ruleIds]) => scorePhaseGate(id, ruleIds, violations, PHASE_GATE_CREATIONS[id] ?? [], graph, creationCurrency));
|
|
284
|
-
const implGates = Object.entries(IMPL_GATE_MILESTONES).map(([id, { ms, label }]) => scoreImplGate(id, ms, label, graph, elementsWithError, PHASE_GATE_CREATIONS[IMPL_GATE_PHASE[id]] ?? [], creationCurrency));
|
|
285
73
|
return {
|
|
286
74
|
compliance: {
|
|
287
75
|
score: complianceScore,
|
|
@@ -290,11 +78,9 @@ export function computeReadiness(violations, graph, creationCurrency) {
|
|
|
290
78
|
elementsWithErrors,
|
|
291
79
|
},
|
|
292
80
|
incoseScope: GRAPHCODE_INCOSE_SCOPE,
|
|
293
|
-
|
|
294
|
-
implGates,
|
|
81
|
+
marks: computeMarks(violations),
|
|
295
82
|
violationsByRule,
|
|
296
83
|
violations: sorted,
|
|
297
|
-
creationEnforcement: creationCurrency ? 'on' : 'off',
|
|
298
84
|
computedAt: new Date().toISOString(),
|
|
299
85
|
};
|
|
300
86
|
}
|
|
@@ -306,9 +92,7 @@ export function computeReadiness(violations, graph, creationCurrency) {
|
|
|
306
92
|
* directly without importing its class (avoids circular dep risk in consumers).
|
|
307
93
|
*/
|
|
308
94
|
export function scoreReadiness(harness) {
|
|
309
|
-
|
|
310
|
-
const graph = harness.getGraph();
|
|
311
|
-
return computeReadiness(violations, graph);
|
|
95
|
+
return computeReadiness(harness.evaluateRules(), harness.getGraph());
|
|
312
96
|
}
|
|
313
97
|
/**
|
|
314
98
|
* The authoritative set of family rule-IDs (derived at runtime from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigloch/graphcode-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"zod": "^4.3.6"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@sigloch/contracts": ">=
|
|
43
|
+
"@sigloch/contracts": ">=11 <12",
|
|
44
44
|
"@sigloch/graph-api-core": ">=5 <6"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@sigloch/contracts": ">=
|
|
47
|
+
"@sigloch/contracts": ">=11 <12",
|
|
48
48
|
"@sigloch/graph-api-core": ">=5.4 <6"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* readiness-completeness.ts — the COMPLETENESS dimension of readiness (CR-GC-250,
|
|
3
|
-
* split out of readiness.ts by CR-GC-260; rebuilt on the rule stream CR-SM-226).
|
|
4
|
-
*
|
|
5
|
-
* Its own module because it answers a different question than the rest of readiness:
|
|
6
|
-
* compliance/gates ask "does any element violate a rule", completeness asks "is the
|
|
7
|
-
* structural chain covered at all" — the absence case rules cannot see (0 FCHAIN → a
|
|
8
|
-
* per-element warning rule fires 0 times and the gate reads green).
|
|
9
|
-
*
|
|
10
|
-
* CR-SM-226: every leg now reads off a named `@sigloch/contracts` rule instead of
|
|
11
|
-
* re-walking the graph locally. The former local out-edge/actor-boundary/testRefs/
|
|
12
|
-
* codeRef/leaf-function helpers were a SECOND, drifting implementation of exactly
|
|
13
|
-
* what R-15/UC-01/UC-03/FC-04/SC-04/R-19/R-20 already check in `@sigloch/contracts/se`
|
|
14
|
-
* — deleted. The rule engine stays the single source of truth; this file only
|
|
15
|
-
* re-groups its violations by the DRIVING population per leg, so an EMPTY
|
|
16
|
-
* population still counts against the gate (the vacuous-complete case a
|
|
17
|
-
* per-element warning rule cannot see on its own — closed for real by
|
|
18
|
-
* `@sigloch/contracts/se`'s R-28 Ebenen-Präsenz, CR-SM-226; die Regel selbst ist mit CR-SM-247 entfallen).
|
|
19
|
-
*
|
|
20
|
-
* BROWSER-SAFE — no `node:*` import may enter this file: the panels layer builds on it.
|
|
21
|
-
*
|
|
22
|
-
* @author andreas@siglochconsulting
|
|
23
|
-
*/
|
|
24
|
-
import { z } from 'zod/v4';
|
|
25
|
-
import type { Graph } from '@sigloch/graph-api-core';
|
|
26
|
-
import type { RuleViolation } from '@sigloch/contracts/harness';
|
|
27
|
-
export type CGraph = Pick<Graph, 'nodes' | 'edges'>;
|
|
28
|
-
export type CNode = Graph['nodes'][number];
|
|
29
|
-
/** A gate's structural completeness over its driving population — ein Zod-Vertrag (CR-SM-361). */
|
|
30
|
-
export declare const GateCompleteness: z.ZodObject<{
|
|
31
|
-
covered: z.ZodNumber;
|
|
32
|
-
total: z.ZodNumber;
|
|
33
|
-
missing: z.ZodArray<z.ZodString>;
|
|
34
|
-
}, z.core.$strip>;
|
|
35
|
-
export type GateCompleteness = z.infer<typeof GateCompleteness>;
|
|
36
|
-
/** Neutral completeness for gates that carry no chain slice (impl gates). */
|
|
37
|
-
export declare const NEUTRAL_COMPLETENESS: GateCompleteness;
|
|
38
|
-
export declare const typeOf: (byId: Map<string, CNode>, id: string) => string | undefined;
|
|
39
|
-
/**
|
|
40
|
-
* One completeness leg: every node of `sourceType` (the driving population)
|
|
41
|
-
* must carry NO violation of `ruleId` (its `@sigloch/contracts` rule twin),
|
|
42
|
-
* else it is a `missing` source for the owning gate. Cardinality legs use the
|
|
43
|
-
* id `"<SRC>→<TGT>"` so the drift test can match them against the
|
|
44
|
-
* meta-model's `1..*` patterns.
|
|
45
|
-
*/
|
|
46
|
-
export interface CompletenessLeg {
|
|
47
|
-
id: string;
|
|
48
|
-
sourceType: string;
|
|
49
|
-
ruleId: string;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Phase-gate → chain legs that must be complete AT THAT PHASE, each backed by
|
|
53
|
-
* its `@sigloch/contracts` rule twin (CR-SM-226):
|
|
54
|
-
* SRR: SYS→UC ≈ R-17 (SYS must have compose; not UC-typed specifically —
|
|
55
|
-
* no dedicated SYS→UC rule exists, R-17 is the closest twin),
|
|
56
|
-
* UC→FCHAIN = UC-03, UC→REQ = UC-01.
|
|
57
|
-
* PDR: FCHAIN→FUNC = R-15, FCHAIN-actor-bounded = FC-04 (trigger+consumer).
|
|
58
|
-
* CDR: FLOW→SCHEMA = R-18 (seit CR-SM-271: die Untergrenze ist Grammatik,
|
|
59
|
-
* `FLOW -relation-> SCHEMA [1..1]` — SC-04 existiert nicht mehr; ein
|
|
60
|
-
* R-18-Befund am FLOW heisst "Kanten ungrammatisch", inkl. fehlendem
|
|
61
|
-
* oder doppeltem SCHEMA).
|
|
62
|
-
* TRR: TEST.testRefs = R-19, FUNC.realRef = R-20.
|
|
63
|
-
*/
|
|
64
|
-
export declare const COMPLETENESS_SLICES: Record<string, CompletenessLeg[]>;
|
|
65
|
-
/**
|
|
66
|
-
* Score one phase gate's structural completeness over its driving population.
|
|
67
|
-
* `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
|
|
68
|
-
* `violations` is the full rule-stream output (e.g. `harness.evaluateRules()`);
|
|
69
|
-
* only the leg's own `ruleId` is consulted per node.
|
|
70
|
-
*/
|
|
71
|
-
export declare function scoreCompleteness(gateId: string, graph: CGraph, violations: RuleViolation[]): GateCompleteness;
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* readiness-completeness.ts — the COMPLETENESS dimension of readiness (CR-GC-250,
|
|
3
|
-
* split out of readiness.ts by CR-GC-260; rebuilt on the rule stream CR-SM-226).
|
|
4
|
-
*
|
|
5
|
-
* Its own module because it answers a different question than the rest of readiness:
|
|
6
|
-
* compliance/gates ask "does any element violate a rule", completeness asks "is the
|
|
7
|
-
* structural chain covered at all" — the absence case rules cannot see (0 FCHAIN → a
|
|
8
|
-
* per-element warning rule fires 0 times and the gate reads green).
|
|
9
|
-
*
|
|
10
|
-
* CR-SM-226: every leg now reads off a named `@sigloch/contracts` rule instead of
|
|
11
|
-
* re-walking the graph locally. The former local out-edge/actor-boundary/testRefs/
|
|
12
|
-
* codeRef/leaf-function helpers were a SECOND, drifting implementation of exactly
|
|
13
|
-
* what R-15/UC-01/UC-03/FC-04/SC-04/R-19/R-20 already check in `@sigloch/contracts/se`
|
|
14
|
-
* — deleted. The rule engine stays the single source of truth; this file only
|
|
15
|
-
* re-groups its violations by the DRIVING population per leg, so an EMPTY
|
|
16
|
-
* population still counts against the gate (the vacuous-complete case a
|
|
17
|
-
* per-element warning rule cannot see on its own — closed for real by
|
|
18
|
-
* `@sigloch/contracts/se`'s R-28 Ebenen-Präsenz, CR-SM-226; die Regel selbst ist mit CR-SM-247 entfallen).
|
|
19
|
-
*
|
|
20
|
-
* BROWSER-SAFE — no `node:*` import may enter this file: the panels layer builds on it.
|
|
21
|
-
*
|
|
22
|
-
* @author andreas@siglochconsulting
|
|
23
|
-
*/
|
|
24
|
-
import { z } from 'zod/v4';
|
|
25
|
-
/** A gate's structural completeness over its driving population — ein Zod-Vertrag (CR-SM-361). */
|
|
26
|
-
export const GateCompleteness = z.object({
|
|
27
|
-
/** Source elements whose chain leg is complete. */
|
|
28
|
-
covered: z.number().int().nonnegative(),
|
|
29
|
-
/** Total source elements evaluated across the gate's legs (the denominator). */
|
|
30
|
-
total: z.number().int().nonnegative(),
|
|
31
|
-
/** Per-leg misses as `"<leg>: <uid>"` — the on-click drill-down detail. */
|
|
32
|
-
missing: z.array(z.string()),
|
|
33
|
-
});
|
|
34
|
-
/** Neutral completeness for gates that carry no chain slice (impl gates). */
|
|
35
|
-
export const NEUTRAL_COMPLETENESS = GateCompleteness.parse({ covered: 0, total: 0, missing: [] });
|
|
36
|
-
export const typeOf = (byId, id) => byId.get(id)?.type;
|
|
37
|
-
/**
|
|
38
|
-
* Phase-gate → chain legs that must be complete AT THAT PHASE, each backed by
|
|
39
|
-
* its `@sigloch/contracts` rule twin (CR-SM-226):
|
|
40
|
-
* SRR: SYS→UC ≈ R-17 (SYS must have compose; not UC-typed specifically —
|
|
41
|
-
* no dedicated SYS→UC rule exists, R-17 is the closest twin),
|
|
42
|
-
* UC→FCHAIN = UC-03, UC→REQ = UC-01.
|
|
43
|
-
* PDR: FCHAIN→FUNC = R-15, FCHAIN-actor-bounded = FC-04 (trigger+consumer).
|
|
44
|
-
* CDR: FLOW→SCHEMA = R-18 (seit CR-SM-271: die Untergrenze ist Grammatik,
|
|
45
|
-
* `FLOW -relation-> SCHEMA [1..1]` — SC-04 existiert nicht mehr; ein
|
|
46
|
-
* R-18-Befund am FLOW heisst "Kanten ungrammatisch", inkl. fehlendem
|
|
47
|
-
* oder doppeltem SCHEMA).
|
|
48
|
-
* TRR: TEST.testRefs = R-19, FUNC.realRef = R-20.
|
|
49
|
-
*/
|
|
50
|
-
export const COMPLETENESS_SLICES = {
|
|
51
|
-
// Behavioral spine exists: system has use cases, every UC derives a chain AND requirements.
|
|
52
|
-
SRR: [
|
|
53
|
-
{ id: 'SYS→UC', sourceType: 'SYS', ruleId: 'R-17' },
|
|
54
|
-
{ id: 'UC→FCHAIN', sourceType: 'UC', ruleId: 'UC-03' },
|
|
55
|
-
{ id: 'UC→REQ', sourceType: 'UC', ruleId: 'UC-01' },
|
|
56
|
-
],
|
|
57
|
-
// Functional architecture: every chain has functions AND is actor-bounded (trigger + consumer).
|
|
58
|
-
PDR: [
|
|
59
|
-
{ id: 'FCHAIN→FUNC', sourceType: 'FCHAIN', ruleId: 'R-15' },
|
|
60
|
-
{ id: 'FCHAIN-actor-bounded', sourceType: 'FCHAIN', ruleId: 'FC-04' },
|
|
61
|
-
],
|
|
62
|
-
// Interfaces carry a data contract: every FLOW has a SCHEMA (CR-GC-106 schema-before-code).
|
|
63
|
-
// CR-SM-271: SC-04 entfiel — der Fall meldet als R-18 (Untergrenzen-Bein) am FLOW.
|
|
64
|
-
CDR: [
|
|
65
|
-
{ id: 'FLOW→SCHEMA', sourceType: 'FLOW', ruleId: 'R-18' },
|
|
66
|
-
],
|
|
67
|
-
// Binding: tests are runnable and functions carry code — concept/external exemptions
|
|
68
|
-
// are R-19/R-20's own business now (single source of truth), not re-decided here.
|
|
69
|
-
TRR: [
|
|
70
|
-
{ id: 'TEST.testRefs', sourceType: 'TEST', ruleId: 'R-19' },
|
|
71
|
-
{ id: 'FUNC.realRef', sourceType: 'FUNC', ruleId: 'R-20' },
|
|
72
|
-
],
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* Score one phase gate's structural completeness over its driving population.
|
|
76
|
-
* `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
|
|
77
|
-
* `violations` is the full rule-stream output (e.g. `harness.evaluateRules()`);
|
|
78
|
-
* only the leg's own `ruleId` is consulted per node.
|
|
79
|
-
*/
|
|
80
|
-
export function scoreCompleteness(gateId, graph, violations) {
|
|
81
|
-
const legs = COMPLETENESS_SLICES[gateId] ?? [];
|
|
82
|
-
let covered = 0;
|
|
83
|
-
let total = 0;
|
|
84
|
-
const missing = [];
|
|
85
|
-
for (const leg of legs) {
|
|
86
|
-
const violatingIds = new Set(violations
|
|
87
|
-
.filter((v) => v.ruleId === leg.ruleId && v.elementId !== undefined)
|
|
88
|
-
.map((v) => v.elementId));
|
|
89
|
-
for (const node of graph.nodes) {
|
|
90
|
-
if (node.type !== leg.sourceType)
|
|
91
|
-
continue;
|
|
92
|
-
total += 1;
|
|
93
|
-
if (violatingIds.has(node.uid))
|
|
94
|
-
missing.push(`${leg.id}: ${node.uid}`);
|
|
95
|
-
else
|
|
96
|
-
covered += 1;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return GateCompleteness.parse({ covered, total, missing }); // CR-SM-361: der Erzeuger prueft seinen Vertrag
|
|
100
|
-
}
|