@sigloch/graphcode-client 1.6.0 → 3.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 -63
- package/dist/panels.js +21 -60
- package/dist/readiness.d.ts +49 -190
- package/dist/readiness.js +96 -259
- package/dist/view-catalog.d.ts +1 -1
- package/dist/view-catalog.js +0 -2
- package/package.json +3 -3
- package/dist/readiness-completeness.d.ts +0 -78
- package/dist/readiness-completeness.js +0 -109
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, countByStage, summarizeReadiness, computeReadiness, scoreReadiness, getFamilyRuleIds, type IncoseScope, type ReadinessMark, type StageCount, 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, countByStage, summarizeReadiness, computeReadiness, scoreReadiness, getFamilyRuleIds, } from './readiness.js';
|
|
23
22
|
export * from './panels.js';
|
package/dist/panels.d.ts
CHANGED
|
@@ -14,29 +14,12 @@
|
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
elementsWithErrors: number;
|
|
38
|
-
phaseGates: GatePanel[];
|
|
39
|
-
implGates: GatePanel[];
|
|
21
|
+
/** SRR, PDR, CDR, TRR, Bau — reached or not, with the findings that hold each (CR-SM-395). */
|
|
22
|
+
marks: ReadinessMark[];
|
|
40
23
|
}
|
|
41
24
|
export declare function readinessPanel(report: ReadinessReport): ReadinessPanel;
|
|
42
25
|
export interface RecommendationItem {
|
|
@@ -85,11 +68,12 @@ export interface ViolationGroup {
|
|
|
85
68
|
export declare function groupViolations(violations: RuleViolation[], elementLimit?: number): ViolationGroup[];
|
|
86
69
|
export type Freshness = 'live' | 'stale' | 'absent';
|
|
87
70
|
/**
|
|
88
|
-
* Two artifact kinds
|
|
71
|
+
* Two artifact kinds (CR-GC-222):
|
|
89
72
|
* - `render` — a deterministic `docs/views/*.md` projection; stale via file mtime vs graph.
|
|
90
|
-
* - `analysis` — judgment work (FMEA/ConOps/Trade
|
|
91
|
-
*
|
|
92
|
-
* an
|
|
73
|
+
* - `analysis` — judgment work (FMEA/ConOps/Trade/Assumption Review); carried out or not. Its
|
|
74
|
+
* only record is the stamp under `SYS.attributes.analysisFreshness` — there is no
|
|
75
|
+
* "stale" for an analysis (CR-SM-395: the currency computation is gone; it ran
|
|
76
|
+
* without effect in the product path since CR-GC-259).
|
|
93
77
|
*/
|
|
94
78
|
export type ArtifactKind = 'render' | 'analysis';
|
|
95
79
|
/** INCOSE/SE-standard vs graphcode-specific — so non-INCOSE rows are not mislabeled as INCOSE. */
|
|
@@ -103,15 +87,16 @@ export interface ArtifactCatalogEntry {
|
|
|
103
87
|
}
|
|
104
88
|
/**
|
|
105
89
|
* 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
|
-
*
|
|
90
|
+
* deterministic CR-GC-220 views; the four analysis ids are exactly contracts' `AnalysisArtifactId`
|
|
91
|
+
* (`conops`/`assumption-review`/`fmea`/`trade`). `irr` is renamed to "Assumption Review" and
|
|
92
|
+
* grouped graphcode-specific (it is not a standard INCOSE artifact).
|
|
93
|
+
*
|
|
94
|
+
* CR-SM-395: the implementation plan is a RENDER. The plan is the set of open CRs, not a stamp;
|
|
95
|
+
* its document (`implplan.md`) is a projection of the CR/MS structure like any other view.
|
|
109
96
|
*/
|
|
110
97
|
export declare const ARTIFACT_CATALOG: readonly ArtifactCatalogEntry[];
|
|
111
98
|
/** Classify a RENDER artifact. `exists` = doc present; `staleVsGraph` = graph changed since render. */
|
|
112
99
|
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
100
|
export interface ArtifactStatus {
|
|
116
101
|
id: string;
|
|
117
102
|
label: string;
|
|
@@ -119,15 +104,13 @@ export interface ArtifactStatus {
|
|
|
119
104
|
group: ArtifactGroup;
|
|
120
105
|
freshness: Freshness;
|
|
121
106
|
}
|
|
122
|
-
/** One artifact's
|
|
107
|
+
/** One artifact's signal. An analysis row reads `exists` only: the stamp is there, or it is not. */
|
|
123
108
|
export interface ArtifactSignal {
|
|
124
109
|
id: string;
|
|
125
|
-
/** RENDER: a materialized doc is present. */
|
|
110
|
+
/** RENDER: a materialized doc is present. ANALYSIS: the analysis is on record (stamp present). */
|
|
126
111
|
exists?: boolean;
|
|
127
|
-
/** RENDER: the graph changed since the doc was generated (mtime). */
|
|
112
|
+
/** RENDER only: the graph changed since the doc was generated (mtime). */
|
|
128
113
|
staleVsGraph?: boolean;
|
|
129
|
-
/** ANALYSIS: scope-currency of the judgment work (the ONLY signal read for analysis rows). */
|
|
130
|
-
currency?: CreationCurrency;
|
|
131
114
|
}
|
|
132
115
|
export interface ArtifactsPanel {
|
|
133
116
|
artifacts: ArtifactStatus[];
|
|
@@ -136,39 +119,19 @@ export interface ArtifactsPanel {
|
|
|
136
119
|
absentCount: number;
|
|
137
120
|
}
|
|
138
121
|
/**
|
|
139
|
-
* Shape artifact
|
|
140
|
-
* kind/group/label; the kind decides
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
122
|
+
* Shape artifact signals into the tab view-model. The catalog decides each row's
|
|
123
|
+
* kind/group/label; the kind decides how it is classified — a render row by mtime, an analysis
|
|
124
|
+
* row by presence alone (`staleVsGraph` is ignored there: an analysis is NEVER classified by
|
|
125
|
+
* mtime). Renderer groups by `group` to show "INCOSE / SE-standard" and "graphcode-specific"
|
|
126
|
+
* separately.
|
|
144
127
|
*/
|
|
145
128
|
export declare function artifactsPanel(signals: ArtifactSignal[]): ArtifactsPanel;
|
|
146
129
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
130
|
+
* The signals of the four analysis artifacts, read off the graph: an analysis is on record iff
|
|
131
|
+
* its stamp under `SYS.attributes.analysisFreshness.<id>` is valid (CR-SM-227 — the same
|
|
132
|
+
* condition AF-01..04 check). No comparison against the live graphVersion (CR-SM-395).
|
|
150
133
|
*/
|
|
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;
|
|
134
|
+
export declare function analysisSignals(graph: Pick<Graph, 'nodes'>): ArtifactSignal[];
|
|
172
135
|
export interface ImpactPanel {
|
|
173
136
|
root: string;
|
|
174
137
|
blastRadiusNodes: number;
|
package/dist/panels.js
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
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
|
-
return {
|
|
7
|
-
compliancePct: Math.round(report.compliance.score * 1000) / 10,
|
|
8
|
-
totalElements: report.compliance.totalElements,
|
|
9
|
-
elementsWithErrors: report.compliance.elementsWithErrors,
|
|
10
|
-
phaseGates: report.phaseGates.map(toGatePanel),
|
|
11
|
-
implGates: report.implGates.map(toGatePanel),
|
|
12
|
-
};
|
|
3
|
+
return { marks: report.marks };
|
|
13
4
|
}
|
|
14
5
|
const SEVERITY_RANK = { error: 0, warning: 1, info: 2 };
|
|
15
6
|
export function recommendationsPanel(violations, limit = 5) {
|
|
@@ -70,9 +61,12 @@ export function groupViolations(violations, elementLimit = 10) {
|
|
|
70
61
|
}
|
|
71
62
|
/**
|
|
72
63
|
* 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
|
-
*
|
|
64
|
+
* deterministic CR-GC-220 views; the four analysis ids are exactly contracts' `AnalysisArtifactId`
|
|
65
|
+
* (`conops`/`assumption-review`/`fmea`/`trade`). `irr` is renamed to "Assumption Review" and
|
|
66
|
+
* grouped graphcode-specific (it is not a standard INCOSE artifact).
|
|
67
|
+
*
|
|
68
|
+
* CR-SM-395: the implementation plan is a RENDER. The plan is the set of open CRs, not a stamp;
|
|
69
|
+
* its document (`implplan.md`) is a projection of the CR/MS structure like any other view.
|
|
76
70
|
*/
|
|
77
71
|
export const ARTIFACT_CATALOG = [
|
|
78
72
|
// Renders — INCOSE/SE-standard deterministic views (mtime-classified).
|
|
@@ -86,11 +80,11 @@ export const ARTIFACT_CATALOG = [
|
|
|
86
80
|
{ id: 'intplan', label: 'Integration & Test Plan', kind: 'render', group: 'incose' },
|
|
87
81
|
{ id: 'changelog', label: 'Change Log', kind: 'render', group: 'incose' },
|
|
88
82
|
{ id: 'references', label: 'Requirements Traceability', kind: 'render', group: 'incose' },
|
|
89
|
-
|
|
83
|
+
{ id: 'implplan', label: 'Implementation Plan', kind: 'render', group: 'incose' },
|
|
84
|
+
// Analyses — judgment work (carried out or not, NEVER mtime).
|
|
90
85
|
{ id: 'conops', label: 'Concept of Operations', kind: 'analysis', group: 'incose' },
|
|
91
86
|
{ id: 'fmea', label: 'FMEA', kind: 'analysis', group: 'incose' },
|
|
92
87
|
{ id: 'trade', label: 'Trade Study', kind: 'analysis', group: 'incose' },
|
|
93
|
-
{ id: 'implplan', label: 'Implementation Plan', kind: 'analysis', group: 'incose' },
|
|
94
88
|
{ id: 'assumption-review', label: 'Assumption Review', kind: 'analysis', group: 'graphcode' },
|
|
95
89
|
];
|
|
96
90
|
/** Classify a RENDER artifact. `exists` = doc present; `staleVsGraph` = graph changed since render. */
|
|
@@ -99,25 +93,19 @@ export function artifactFreshness(exists, staleVsGraph) {
|
|
|
99
93
|
return 'absent';
|
|
100
94
|
return staleVsGraph ? 'stale' : 'live';
|
|
101
95
|
}
|
|
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
96
|
/**
|
|
107
|
-
* Shape artifact
|
|
108
|
-
* kind/group/label; the kind decides
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
97
|
+
* Shape artifact signals into the tab view-model. The catalog decides each row's
|
|
98
|
+
* kind/group/label; the kind decides how it is classified — a render row by mtime, an analysis
|
|
99
|
+
* row by presence alone (`staleVsGraph` is ignored there: an analysis is NEVER classified by
|
|
100
|
+
* mtime). Renderer groups by `group` to show "INCOSE / SE-standard" and "graphcode-specific"
|
|
101
|
+
* separately.
|
|
112
102
|
*/
|
|
113
103
|
export function artifactsPanel(signals) {
|
|
114
104
|
const byId = new Map(ARTIFACT_CATALOG.map((e) => [e.id, e]));
|
|
115
105
|
const statuses = signals.map((s) => {
|
|
116
106
|
const entry = byId.get(s.id);
|
|
117
107
|
const kind = entry?.kind ?? 'render';
|
|
118
|
-
const freshness = kind === '
|
|
119
|
-
? analysisFreshness(s.currency ?? 'absent')
|
|
120
|
-
: artifactFreshness(s.exists ?? false, s.staleVsGraph ?? false);
|
|
108
|
+
const freshness = artifactFreshness(s.exists ?? false, kind === 'render' && (s.staleVsGraph ?? false));
|
|
121
109
|
return { id: s.id, label: entry?.label ?? s.id, kind, group: entry?.group ?? 'graphcode', freshness };
|
|
122
110
|
});
|
|
123
111
|
return {
|
|
@@ -128,41 +116,14 @@ export function artifactsPanel(signals) {
|
|
|
128
116
|
};
|
|
129
117
|
}
|
|
130
118
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
119
|
+
* The signals of the four analysis artifacts, read off the graph: an analysis is on record iff
|
|
120
|
+
* its stamp under `SYS.attributes.analysisFreshness.<id>` is valid (CR-SM-227 — the same
|
|
121
|
+
* condition AF-01..04 check). No comparison against the live graphVersion (CR-SM-395).
|
|
134
122
|
*/
|
|
135
|
-
export function
|
|
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.
|
|
149
|
-
*/
|
|
150
|
-
export function computeAnalysisCurrency(graph, currentGraphVersion) {
|
|
123
|
+
export function analysisSignals(graph) {
|
|
151
124
|
const sys = graph.nodes.find((n) => n.type === 'SYS');
|
|
152
125
|
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));
|
|
126
|
+
return AnalysisArtifactId.options.map((id) => ({ id, exists: AnalysisFreshnessStampSchema.safeParse(stamps[id]).success }));
|
|
166
127
|
}
|
|
167
128
|
export function impactPanel(impact) {
|
|
168
129
|
return { root: impact.rootId, blastRadiusNodes: impact.nodeCount, blastRadiusEdges: impact.edgeCount };
|
|
@@ -182,7 +143,7 @@ export function healthPanel(health) {
|
|
|
182
143
|
const DOMAIN_TO_PANELS = {
|
|
183
144
|
graph: ['graph', 'impact', 'artifacts'],
|
|
184
145
|
rules: ['recommendations', 'readiness'],
|
|
185
|
-
readiness: ['readiness', '
|
|
146
|
+
readiness: ['readiness', 'artifacts'],
|
|
186
147
|
suggestions: ['recommendations'],
|
|
187
148
|
};
|
|
188
149
|
/** Which panel ids a viewer should refresh for this invalidation event. */
|
package/dist/readiness.d.ts
CHANGED
|
@@ -1,230 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
* ReadinessScorer — CR-GC-107 (compliance) + CR-GC-125 (readiness model).
|
|
3
|
-
*
|
|
4
|
-
* Computes readiness for a graphcode graph purely from `@sigloch/contracts`
|
|
5
|
-
* V3_RULES violations (via `harness.evaluateRules()`, the L2 gate — see
|
|
6
|
-
* FCHAIN-apply-gate) PLUS the MS milestone nodes and element status. This is
|
|
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.
|
|
11
|
-
*
|
|
12
|
-
* REQ-readiness-model (CR-GC-125) — the model, defined here AND in the graph:
|
|
13
|
-
*
|
|
14
|
-
* INCOSE-Artifact-Scope = LEAN, refined (CR-GC-226, supersedes the CR-GC-125
|
|
15
|
-
* `lean = no artifacts` stance). graphcode is a headless QM-level governance
|
|
16
|
-
* harness, not an ASIL-rated system. The graph is the SSOT and every RENDER
|
|
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.
|
|
24
|
-
*
|
|
25
|
-
* Phase-Readiness — 4 INCOSE technical-review gates (SRR/PDR/CDR/TRR), each a
|
|
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.
|
|
30
|
-
*
|
|
31
|
-
* Implementation-Readiness — 4 program/build acceptance gates
|
|
32
|
-
* (SAR/FCA/SVR/FRR), each bound to a milestone tier (MS-1..MS-4). A gate is
|
|
33
|
-
* `ready` iff every CR assigned to its milestone (CR -relation-> MS) is
|
|
34
|
-
* status=done AND the milestone scope (MS -compose-> X) carries no
|
|
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.
|
|
37
|
-
*
|
|
38
|
-
* Host/dashboard wiring (panels) lives in MOD-dashboard (CR-GC-115); this module
|
|
39
|
-
* provides the scoring primitive only — no dashboard, no renderer.
|
|
40
|
-
*
|
|
41
|
-
* @author andreas@siglochconsulting
|
|
42
|
-
*/
|
|
43
|
-
import type { Graph } from '@sigloch/graph-api-core';
|
|
1
|
+
import { type MarkType, type RuleStage } from '@sigloch/contracts/se';
|
|
44
2
|
import type { RuleViolation } from '@sigloch/contracts/harness';
|
|
45
|
-
import { type GateCompleteness } from './readiness-completeness.js';
|
|
46
|
-
/**
|
|
47
|
-
* A readiness dimension score in [0, 1].
|
|
48
|
-
* 0 = fully non-compliant, 1 = fully compliant.
|
|
49
|
-
*/
|
|
50
|
-
export interface ReadinessDimension {
|
|
51
|
-
/** Score in [0, 1]. */
|
|
52
|
-
score: number;
|
|
53
|
-
/** Short human-readable label for display. */
|
|
54
|
-
label: string;
|
|
55
|
-
/** How many elements were evaluated. */
|
|
56
|
-
totalElements: number;
|
|
57
|
-
/** How many elements carry at least one error-severity violation. */
|
|
58
|
-
elementsWithErrors: number;
|
|
59
|
-
}
|
|
60
3
|
/**
|
|
61
4
|
* 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".
|
|
5
|
+
* `lean` — graph = SSOT; renders are deterministic projections, analyses are lightweight
|
|
6
|
+
* judgment inputs (graphcode's stance, refined CR-GC-226). NOT "no artifacts".
|
|
65
7
|
* `full` — ASIL-D-grade evidence (full FMEA S/O/D rigor, ASIL isolation) (not graphcode).
|
|
66
8
|
*/
|
|
67
9
|
export type IncoseScope = 'lean' | 'full';
|
|
68
|
-
/** graphcode's declared scope
|
|
10
|
+
/** graphcode's declared scope. */
|
|
69
11
|
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;
|
|
12
|
+
/** Human labels for the marks. */
|
|
13
|
+
export declare const MARK_LABELS: Readonly<Record<MarkType, string>>;
|
|
14
|
+
/** One mark: reached or not, and the findings that hold it. */
|
|
15
|
+
export interface ReadinessMark {
|
|
16
|
+
id: MarkType;
|
|
17
|
+
/** Human-readable name. */
|
|
132
18
|
label: string;
|
|
133
|
-
|
|
19
|
+
/** True iff `holding` is empty. */
|
|
20
|
+
reached: boolean;
|
|
21
|
+
/** The findings that hold the mark — the answer to "why not". */
|
|
22
|
+
holding: RuleViolation[];
|
|
23
|
+
}
|
|
24
|
+
/** The marks in order, each with the findings that hold it. */
|
|
25
|
+
export declare function computeMarks(violations: RuleViolation[]): ReadinessMark[];
|
|
26
|
+
/** One stage of the rule catalog and the number of findings whose rule lies at it. */
|
|
27
|
+
export interface StageCount {
|
|
28
|
+
/** 1–12, or `'immer'` for the rules over all elements. */
|
|
29
|
+
stage: RuleStage;
|
|
30
|
+
/** The set of the stage (`STAGE_SETS`); `'immer'` carries its own value. */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Number of findings of the rules at this stage — every severity, accepted or not. */
|
|
33
|
+
findings: number;
|
|
34
|
+
}
|
|
134
35
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
36
|
+
* The findings counted per stage: the stages 1 to 12 in order, then `'immer'` — always 13 entries.
|
|
37
|
+
* Derived from the stage each rule carries at its definition and from nothing else: no table, no
|
|
38
|
+
* denominator, no percentage. The sum over the entries is the number of findings. A finding of a
|
|
39
|
+
* rule the catalog does not know throws, as in `holds`.
|
|
40
|
+
*
|
|
41
|
+
* This replaces the eight readiness dimensions and their scores (decision of the author,
|
|
42
|
+
* 2026-10-07: "Stufe statt Dimension, Befunde zaehlen statt Prozent").
|
|
138
43
|
*/
|
|
139
|
-
export
|
|
140
|
-
/** Gate id — SRR/PDR/CDR/TRR (phase) or SAR/FCA/SVR/FRR (impl). */
|
|
141
|
-
id: string;
|
|
142
|
-
/** Human-readable review name. */
|
|
143
|
-
label: string;
|
|
144
|
-
/** True iff no blocking (error-severity / not-done) item remains. */
|
|
145
|
-
passed: boolean;
|
|
146
|
-
/** Continuous maturity in [0, 1]. */
|
|
147
|
-
score: number;
|
|
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;
|
|
163
|
-
}
|
|
44
|
+
export declare function countByStage(violations: RuleViolation[]): StageCount[];
|
|
164
45
|
/**
|
|
165
|
-
* Full readiness snapshot derived from
|
|
166
|
-
* All violation ruleIds are contracts rule-IDs
|
|
46
|
+
* Full readiness snapshot derived from rule violations.
|
|
47
|
+
* All violation ruleIds are contracts rule-IDs — never a foreign rule set.
|
|
167
48
|
*/
|
|
168
49
|
export interface ReadinessReport {
|
|
169
|
-
/**
|
|
170
|
-
* Compliance dimension: fraction of graph elements with NO error-severity
|
|
171
|
-
* violation. 1.0 = all elements clean, 0.0 = all elements have ≥1 error.
|
|
172
|
-
*/
|
|
173
|
-
compliance: ReadinessDimension;
|
|
174
50
|
/** Declared INCOSE artifact scope for this system (graphcode = `lean`). */
|
|
175
51
|
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
|
-
*/
|
|
52
|
+
/** The marks SRR, PDR, CDR, TRR, Bau — in order. */
|
|
53
|
+
marks: ReadinessMark[];
|
|
54
|
+
/** Per-contracts-rule-ID violation counts. */
|
|
185
55
|
violationsByRule: Record<string, number>;
|
|
186
56
|
/** All raw violations, ordered by severity (error → warning → info). */
|
|
187
57
|
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
58
|
/** Timestamp (ISO 8601) when this report was computed. */
|
|
200
59
|
computedAt: string;
|
|
201
60
|
}
|
|
202
61
|
/**
|
|
203
62
|
* Summary projection of a ReadinessReport (CR-GC-203 item 2): drops the heavy
|
|
204
|
-
* per-element lists — `violations` and every
|
|
205
|
-
*
|
|
63
|
+
* per-element lists — `violations` and every mark's `holding` — while keeping
|
|
64
|
+
* `reached`, counts and `violationsByRule`. `graph_readiness` returns this
|
|
206
65
|
* by default; `detail:true` returns the full lists. On a fully-red graph the
|
|
207
|
-
* full report inlined every
|
|
66
|
+
* full report inlined every finding (86k+ chars, past the MCP tool
|
|
208
67
|
* result limit) forcing a file-spill; the summary stays small.
|
|
209
68
|
*/
|
|
210
69
|
export declare function summarizeReadiness(report: ReadinessReport): ReadinessReport;
|
|
211
70
|
/**
|
|
212
|
-
* Compute readiness from a violations array
|
|
71
|
+
* Compute readiness from a violations array — and from nothing else (CR-SM-402: the graph was
|
|
72
|
+
* read for the compliance percentage only).
|
|
213
73
|
*
|
|
214
|
-
* @param violations - from `harness.evaluateRules()` (contracts
|
|
215
|
-
*
|
|
74
|
+
* @param violations - from `harness.evaluateRules()` (contracts rule IDs), UNFILTERED: a finding
|
|
75
|
+
* the human accepted with a reason still holds its mark (CR-SM-396).
|
|
216
76
|
*/
|
|
217
|
-
export declare function computeReadiness(violations: RuleViolation[]
|
|
77
|
+
export declare function computeReadiness(violations: RuleViolation[]): ReadinessReport;
|
|
218
78
|
/**
|
|
219
|
-
* Convenience wrapper: runs `evaluateRules()`
|
|
220
|
-
*
|
|
79
|
+
* Convenience wrapper: runs `evaluateRules()` on the harness and returns the full
|
|
80
|
+
* ReadinessReport.
|
|
221
81
|
*
|
|
222
82
|
* Accepts a minimal harness-duck-type so callers can pass `GraphCodeHarness`
|
|
223
83
|
* directly without importing its class (avoids circular dep risk in consumers).
|
|
224
84
|
*/
|
|
225
85
|
export declare function scoreReadiness(harness: {
|
|
226
86
|
evaluateRules(): RuleViolation[];
|
|
227
|
-
getGraph(): Pick<Graph, 'nodes' | 'edges'>;
|
|
228
87
|
}): ReadinessReport;
|
|
229
88
|
/**
|
|
230
89
|
* The authoritative set of family rule-IDs (derived at runtime from
|
package/dist/readiness.js
CHANGED
|
@@ -1,278 +1,127 @@
|
|
|
1
|
-
import { SE_DESCRIPTOR } from '@sigloch/graph-api-core';
|
|
2
|
-
import { CODE_CONFORMANCE_RULES, RULE_TO_PHASE } from '@sigloch/contracts/se';
|
|
3
|
-
import { scoreCompleteness, NEUTRAL_COMPLETENESS, } from './readiness-completeness.js';
|
|
4
|
-
/** graphcode's declared scope — see the module header for the rationale. */
|
|
5
|
-
export const GRAPHCODE_INCOSE_SCOPE = 'lean';
|
|
6
1
|
/**
|
|
7
|
-
*
|
|
8
|
-
* gates ARE the milestone/CR burndown (SAR/FCA/SVR/FRR ↔ MS-1..MS-4), so a rule about
|
|
9
|
-
* a milestone or a change request belongs there and not on a design review.
|
|
2
|
+
* Readiness — the marks and the findings (CR-GC-107, CR-GC-125; rebuilt CR-SM-395).
|
|
10
3
|
*
|
|
11
|
-
*
|
|
12
|
-
* rule
|
|
13
|
-
*
|
|
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.
|
|
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.
|
|
29
7
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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.
|
|
34
15
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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.
|
|
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.
|
|
19
|
+
*
|
|
20
|
+
* A NOTE (severity `info`, e.g. MS-03, VR-01) holds no mark, at no stage (CR-SM-396).
|
|
21
|
+
*
|
|
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.
|
|
58
26
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* object, and the replacement was never carried through here.
|
|
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).
|
|
65
32
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
+
* What is gone with CR-SM-402: the compliance percentage (share of elements without an error
|
|
39
|
+
* finding) with its denominator and its two counts. The report carries marks and findings — what
|
|
40
|
+
* the agent reads and acts on — and nothing a viewer would see on top of that.
|
|
41
|
+
*
|
|
42
|
+
* Host/dashboard wiring (panels) lives in `panels.ts`; this module provides the scoring
|
|
43
|
+
* primitive only — no dashboard, no renderer.
|
|
44
|
+
*
|
|
45
|
+
* @author andreas@siglochconsulting
|
|
71
46
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 = {
|
|
47
|
+
import { SE_DESCRIPTOR } from '@sigloch/graph-api-core';
|
|
48
|
+
import { ALL_RULE_DEFS, CODE_CONFORMANCE_RULES, Mark, MARK_STAGE, STAGE_SETS } from '@sigloch/contracts/se';
|
|
49
|
+
/** graphcode's declared scope. */
|
|
50
|
+
export const GRAPHCODE_INCOSE_SCOPE = 'lean';
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Marks (CR-SM-395)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
/** Human labels for the marks. */
|
|
55
|
+
export const MARK_LABELS = {
|
|
93
56
|
SRR: 'System Requirements Review',
|
|
94
57
|
PDR: 'Preliminary Design Review',
|
|
95
58
|
CDR: 'Critical Design Review',
|
|
96
59
|
TRR: 'Test Readiness Review',
|
|
60
|
+
Bau: 'Bau',
|
|
97
61
|
};
|
|
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)`;
|
|
62
|
+
const RULES = new Map(ALL_RULE_DEFS.map((r) => [r.id, r]));
|
|
63
|
+
/** Does this finding hold this mark? See the module header for the rule. */
|
|
64
|
+
function holds(v, mark) {
|
|
65
|
+
// A finding of a rule the catalog does not know is a programming error and throws (CR-SM-399).
|
|
66
|
+
const rule = RULES.get(v.ruleId);
|
|
67
|
+
const stage = rule.stage;
|
|
68
|
+
if (v.severity === 'info')
|
|
69
|
+
return false; // a note holds no mark (CR-SM-396)
|
|
70
|
+
if (stage !== 'immer' && stage > MARK_STAGE[mark])
|
|
71
|
+
return false;
|
|
72
|
+
if (v.severity === 'error' || rule.role === 'existence')
|
|
73
|
+
return true;
|
|
74
|
+
return mark === 'Bau' && stage !== 'immer' && stage > MARK_STAGE.TRR;
|
|
75
|
+
}
|
|
76
|
+
/** The marks in order, each with the findings that hold it. */
|
|
77
|
+
export function computeMarks(violations) {
|
|
78
|
+
return Mark.options.map((id) => {
|
|
79
|
+
const holding = violations.filter((v) => holds(v, id));
|
|
80
|
+
return { id, label: MARK_LABELS[id], reached: holding.length === 0, holding };
|
|
81
|
+
});
|
|
132
82
|
}
|
|
133
83
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
84
|
+
* The findings counted per stage: the stages 1 to 12 in order, then `'immer'` — always 13 entries.
|
|
85
|
+
* Derived from the stage each rule carries at its definition and from nothing else: no table, no
|
|
86
|
+
* denominator, no percentage. The sum over the entries is the number of findings. A finding of a
|
|
87
|
+
* rule the catalog does not know throws, as in `holds`.
|
|
88
|
+
*
|
|
89
|
+
* This replaces the eight readiness dimensions and their scores (decision of the author,
|
|
90
|
+
* 2026-10-07: "Stufe statt Dimension, Befunde zaehlen statt Prozent").
|
|
136
91
|
*/
|
|
137
|
-
export
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
92
|
+
export function countByStage(violations) {
|
|
93
|
+
const count = (stage) => violations.filter((v) => RULES.get(v.ruleId).stage === stage).length;
|
|
94
|
+
return [
|
|
95
|
+
...STAGE_SETS.map((name, i) => ({ stage: (i + 1), name, findings: count((i + 1)) })),
|
|
96
|
+
{ stage: 'immer', name: 'immer', findings: count('immer') },
|
|
97
|
+
];
|
|
98
|
+
}
|
|
143
99
|
/**
|
|
144
100
|
* Summary projection of a ReadinessReport (CR-GC-203 item 2): drops the heavy
|
|
145
|
-
* per-element lists — `violations` and every
|
|
146
|
-
*
|
|
101
|
+
* per-element lists — `violations` and every mark's `holding` — while keeping
|
|
102
|
+
* `reached`, counts and `violationsByRule`. `graph_readiness` returns this
|
|
147
103
|
* by default; `detail:true` returns the full lists. On a fully-red graph the
|
|
148
|
-
* full report inlined every
|
|
104
|
+
* full report inlined every finding (86k+ chars, past the MCP tool
|
|
149
105
|
* result limit) forcing a file-spill; the summary stays small.
|
|
150
106
|
*/
|
|
151
107
|
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
108
|
return {
|
|
160
109
|
...report,
|
|
161
|
-
|
|
162
|
-
implGates: report.implGates.map(stripGate),
|
|
110
|
+
marks: report.marks.map((m) => ({ ...m, holding: [] })),
|
|
163
111
|
violations: [],
|
|
164
112
|
};
|
|
165
113
|
}
|
|
166
114
|
// ---------------------------------------------------------------------------
|
|
167
115
|
// Scorer
|
|
168
116
|
// ---------------------------------------------------------------------------
|
|
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
117
|
/**
|
|
265
|
-
* Compute readiness from a violations array
|
|
118
|
+
* Compute readiness from a violations array — and from nothing else (CR-SM-402: the graph was
|
|
119
|
+
* read for the compliance percentage only).
|
|
266
120
|
*
|
|
267
|
-
* @param violations - from `harness.evaluateRules()` (contracts
|
|
268
|
-
*
|
|
121
|
+
* @param violations - from `harness.evaluateRules()` (contracts rule IDs), UNFILTERED: a finding
|
|
122
|
+
* the human accepted with a reason still holds its mark (CR-SM-396).
|
|
269
123
|
*/
|
|
270
|
-
export function computeReadiness(violations
|
|
271
|
-
const totalElements = graph.nodes.length;
|
|
272
|
-
const elementsWithError = elementsWithErrorSet(violations);
|
|
273
|
-
const elementsWithErrors = elementsWithError.size;
|
|
274
|
-
const complianceScore = totalElements > 0 ? (totalElements - elementsWithErrors) / totalElements : 1;
|
|
275
|
-
// Violation counts keyed by contracts rule-ID (never BQ-*).
|
|
124
|
+
export function computeReadiness(violations) {
|
|
276
125
|
const violationsByRule = {};
|
|
277
126
|
for (const v of violations) {
|
|
278
127
|
violationsByRule[v.ruleId] = (violationsByRule[v.ruleId] ?? 0) + 1;
|
|
@@ -280,35 +129,23 @@ export function computeReadiness(violations, graph, creationCurrency) {
|
|
|
280
129
|
// Sort: error → warning → info.
|
|
281
130
|
const severityRank = { error: 0, warning: 1, info: 2 };
|
|
282
131
|
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
132
|
return {
|
|
286
|
-
compliance: {
|
|
287
|
-
score: complianceScore,
|
|
288
|
-
label: 'Compliance (V3_RULES, error-severity)',
|
|
289
|
-
totalElements,
|
|
290
|
-
elementsWithErrors,
|
|
291
|
-
},
|
|
292
133
|
incoseScope: GRAPHCODE_INCOSE_SCOPE,
|
|
293
|
-
|
|
294
|
-
implGates,
|
|
134
|
+
marks: computeMarks(violations),
|
|
295
135
|
violationsByRule,
|
|
296
136
|
violations: sorted,
|
|
297
|
-
creationEnforcement: creationCurrency ? 'on' : 'off',
|
|
298
137
|
computedAt: new Date().toISOString(),
|
|
299
138
|
};
|
|
300
139
|
}
|
|
301
140
|
/**
|
|
302
|
-
* Convenience wrapper: runs `evaluateRules()`
|
|
303
|
-
*
|
|
141
|
+
* Convenience wrapper: runs `evaluateRules()` on the harness and returns the full
|
|
142
|
+
* ReadinessReport.
|
|
304
143
|
*
|
|
305
144
|
* Accepts a minimal harness-duck-type so callers can pass `GraphCodeHarness`
|
|
306
145
|
* directly without importing its class (avoids circular dep risk in consumers).
|
|
307
146
|
*/
|
|
308
147
|
export function scoreReadiness(harness) {
|
|
309
|
-
|
|
310
|
-
const graph = harness.getGraph();
|
|
311
|
-
return computeReadiness(violations, graph);
|
|
148
|
+
return computeReadiness(harness.evaluateRules());
|
|
312
149
|
}
|
|
313
150
|
/**
|
|
314
151
|
* The authoritative set of family rule-IDs (derived at runtime from
|
package/dist/view-catalog.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* grouped by type and answered the same question as `srs` (the ISO-29148 narrative)
|
|
23
23
|
* with different completeness — two requirements documents, one truth. `srs` stays.
|
|
24
24
|
*/
|
|
25
|
-
export declare const MARKDOWN_VIEWS: readonly ["architecture", "cr-list", "references", "srs", "nfr", "rtm", "icd", "testconcept", "testmatrix", "intplan", "changelog", "fmea", "conops", "
|
|
25
|
+
export declare const MARKDOWN_VIEWS: readonly ["architecture", "cr-list", "references", "srs", "nfr", "rtm", "icd", "testconcept", "testmatrix", "intplan", "changelog", "fmea", "conops", "implplan"];
|
|
26
26
|
export type MarkdownView = (typeof MARKDOWN_VIEWS)[number];
|
|
27
27
|
/** Default file name for each view (under docs/views/). */
|
|
28
28
|
export declare const VIEW_FILENAMES: Record<MarkdownView, string>;
|
package/dist/view-catalog.js
CHANGED
|
@@ -38,7 +38,6 @@ export const MARKDOWN_VIEWS = [
|
|
|
38
38
|
'changelog',
|
|
39
39
|
'fmea',
|
|
40
40
|
'conops',
|
|
41
|
-
'trade',
|
|
42
41
|
'implplan',
|
|
43
42
|
];
|
|
44
43
|
/** Default file name for each view (under docs/views/). */
|
|
@@ -56,6 +55,5 @@ export const VIEW_FILENAMES = {
|
|
|
56
55
|
changelog: 'changelog.md',
|
|
57
56
|
fmea: 'fmea.md',
|
|
58
57
|
conops: 'conops.md',
|
|
59
|
-
trade: 'trade.md',
|
|
60
58
|
implplan: 'implplan.md',
|
|
61
59
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigloch/graphcode-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.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": ">=12 <13",
|
|
44
44
|
"@sigloch/graph-api-core": ">=5 <6"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@sigloch/contracts": ">=
|
|
47
|
+
"@sigloch/contracts": ">=12 <13",
|
|
48
48
|
"@sigloch/graph-api-core": ">=5.4 <6"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -1,78 +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
|
-
* FUNC→MOD = R-22 (CR-SM-389: jede FUNC wohnt in einem Modul). Der Pattern ist
|
|
59
|
-
* `0..1` — seine Untergrenze ist R-22 (warning, nicht error: das Apply-Gate lehnt
|
|
60
|
-
* neu eingefuehrte Errors ab, jede FUNC muesste sonst mit ihrem Modul im selben
|
|
61
|
-
* Zug entstehen). Das Bein sperrt `passed`, nicht das Gate. R-23 (MOD ohne FUNC)
|
|
62
|
-
* ist bewusst KEIN Bein: ein leeres Modul laesst keine Funktion unverortet — es
|
|
63
|
-
* ist Ueberschuss, keine Luecke; die treibende Population ist FUNC, und dieselbe
|
|
64
|
-
* allocate-Kante wuerde nur ein zweites Mal von der anderen Seite gezaehlt.
|
|
65
|
-
* CDR: FLOW→SCHEMA = R-18 (seit CR-SM-271: die Untergrenze ist Grammatik,
|
|
66
|
-
* `FLOW -relation-> SCHEMA [1..1]` — SC-04 existiert nicht mehr; ein
|
|
67
|
-
* R-18-Befund am FLOW heisst "Kanten ungrammatisch", inkl. fehlendem
|
|
68
|
-
* oder doppeltem SCHEMA).
|
|
69
|
-
* TRR: TEST.testRefs = R-19, FUNC.realRef = R-20.
|
|
70
|
-
*/
|
|
71
|
-
export declare const COMPLETENESS_SLICES: Record<string, CompletenessLeg[]>;
|
|
72
|
-
/**
|
|
73
|
-
* Score one phase gate's structural completeness over its driving population.
|
|
74
|
-
* `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
|
|
75
|
-
* `violations` is the full rule-stream output (e.g. `harness.evaluateRules()`);
|
|
76
|
-
* only the leg's own `ruleId` is consulted per node.
|
|
77
|
-
*/
|
|
78
|
-
export declare function scoreCompleteness(gateId: string, graph: CGraph, violations: RuleViolation[]): GateCompleteness;
|
|
@@ -1,109 +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
|
-
* FUNC→MOD = R-22 (CR-SM-389: jede FUNC wohnt in einem Modul). Der Pattern ist
|
|
45
|
-
* `0..1` — seine Untergrenze ist R-22 (warning, nicht error: das Apply-Gate lehnt
|
|
46
|
-
* neu eingefuehrte Errors ab, jede FUNC muesste sonst mit ihrem Modul im selben
|
|
47
|
-
* Zug entstehen). Das Bein sperrt `passed`, nicht das Gate. R-23 (MOD ohne FUNC)
|
|
48
|
-
* ist bewusst KEIN Bein: ein leeres Modul laesst keine Funktion unverortet — es
|
|
49
|
-
* ist Ueberschuss, keine Luecke; die treibende Population ist FUNC, und dieselbe
|
|
50
|
-
* allocate-Kante wuerde nur ein zweites Mal von der anderen Seite gezaehlt.
|
|
51
|
-
* CDR: FLOW→SCHEMA = R-18 (seit CR-SM-271: die Untergrenze ist Grammatik,
|
|
52
|
-
* `FLOW -relation-> SCHEMA [1..1]` — SC-04 existiert nicht mehr; ein
|
|
53
|
-
* R-18-Befund am FLOW heisst "Kanten ungrammatisch", inkl. fehlendem
|
|
54
|
-
* oder doppeltem SCHEMA).
|
|
55
|
-
* TRR: TEST.testRefs = R-19, FUNC.realRef = R-20.
|
|
56
|
-
*/
|
|
57
|
-
export const COMPLETENESS_SLICES = {
|
|
58
|
-
// Behavioral spine exists: system has use cases, every UC derives a chain AND requirements.
|
|
59
|
-
SRR: [
|
|
60
|
-
{ id: 'SYS→UC', sourceType: 'SYS', ruleId: 'R-17' },
|
|
61
|
-
{ id: 'UC→FCHAIN', sourceType: 'UC', ruleId: 'UC-03' },
|
|
62
|
-
{ id: 'UC→REQ', sourceType: 'UC', ruleId: 'UC-01' },
|
|
63
|
-
],
|
|
64
|
-
// Functional architecture: every chain has functions AND is actor-bounded (trigger + consumer),
|
|
65
|
-
// and every function is allocated to a module (CR-SM-389).
|
|
66
|
-
PDR: [
|
|
67
|
-
{ id: 'FCHAIN→FUNC', sourceType: 'FCHAIN', ruleId: 'R-15' },
|
|
68
|
-
{ id: 'FCHAIN-actor-bounded', sourceType: 'FCHAIN', ruleId: 'FC-04' },
|
|
69
|
-
{ id: 'FUNC→MOD', sourceType: 'FUNC', ruleId: 'R-22' },
|
|
70
|
-
],
|
|
71
|
-
// Interfaces carry a data contract: every FLOW has a SCHEMA (CR-GC-106 schema-before-code).
|
|
72
|
-
// CR-SM-271: SC-04 entfiel — der Fall meldet als R-18 (Untergrenzen-Bein) am FLOW.
|
|
73
|
-
CDR: [
|
|
74
|
-
{ id: 'FLOW→SCHEMA', sourceType: 'FLOW', ruleId: 'R-18' },
|
|
75
|
-
],
|
|
76
|
-
// Binding: tests are runnable and functions carry code — concept/external exemptions
|
|
77
|
-
// are R-19/R-20's own business now (single source of truth), not re-decided here.
|
|
78
|
-
TRR: [
|
|
79
|
-
{ id: 'TEST.testRefs', sourceType: 'TEST', ruleId: 'R-19' },
|
|
80
|
-
{ id: 'FUNC.realRef', sourceType: 'FUNC', ruleId: 'R-20' },
|
|
81
|
-
],
|
|
82
|
-
};
|
|
83
|
-
/**
|
|
84
|
-
* Score one phase gate's structural completeness over its driving population.
|
|
85
|
-
* `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
|
|
86
|
-
* `violations` is the full rule-stream output (e.g. `harness.evaluateRules()`);
|
|
87
|
-
* only the leg's own `ruleId` is consulted per node.
|
|
88
|
-
*/
|
|
89
|
-
export function scoreCompleteness(gateId, graph, violations) {
|
|
90
|
-
const legs = COMPLETENESS_SLICES[gateId] ?? [];
|
|
91
|
-
let covered = 0;
|
|
92
|
-
let total = 0;
|
|
93
|
-
const missing = [];
|
|
94
|
-
for (const leg of legs) {
|
|
95
|
-
const violatingIds = new Set(violations
|
|
96
|
-
.filter((v) => v.ruleId === leg.ruleId && v.elementId !== undefined)
|
|
97
|
-
.map((v) => v.elementId));
|
|
98
|
-
for (const node of graph.nodes) {
|
|
99
|
-
if (node.type !== leg.sourceType)
|
|
100
|
-
continue;
|
|
101
|
-
total += 1;
|
|
102
|
-
if (violatingIds.has(node.uid))
|
|
103
|
-
missing.push(`${leg.id}: ${node.uid}`);
|
|
104
|
-
else
|
|
105
|
-
covered += 1;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return GateCompleteness.parse({ covered, total, missing }); // CR-SM-361: der Erzeuger prueft seinen Vertrag
|
|
109
|
-
}
|