@sigloch/graphcode-client 0.1.0 → 0.2.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 +9 -2
- package/dist/index.js +9 -2
- package/dist/panels.d.ts +151 -0
- package/dist/panels.js +121 -0
- package/dist/readiness-completeness.d.ts +64 -0
- package/dist/readiness-completeness.js +79 -0
- package/dist/readiness.d.ts +218 -0
- package/dist/readiness.js +269 -0
- package/package.json +5 -1
package/dist/index.d.ts
CHANGED
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
* no harness. The write path is unchanged — `callHost` forwards to the host's
|
|
7
7
|
* ONE `mutate()` gate; this package never opens a second one.
|
|
8
8
|
*
|
|
9
|
-
* Scope split (CR-GC-264):
|
|
10
|
-
* - here
|
|
9
|
+
* Scope split (CR-GC-264/265):
|
|
10
|
+
* - here : host-socket client, view catalog, readiness projection, dashboard panels
|
|
11
11
|
* - @sigloch/graphcode : store, gate, MCP server, exporters, host election
|
|
12
12
|
*
|
|
13
|
+
* The rule of thumb for what belongs here: it takes plain data in and returns
|
|
14
|
+
* plain data out. Anything that needs the store, the gate or a process stays in
|
|
15
|
+
* the substrate.
|
|
16
|
+
*
|
|
13
17
|
* @author andreas@siglochconsulting
|
|
14
18
|
*/
|
|
15
19
|
export { HOST_SOCK_BASENAME, callHost, type ShimRequest, type ShimResponse, } from './host-client.js';
|
|
16
20
|
export { MARKDOWN_VIEWS, VIEW_FILENAMES, type MarkdownView } from './view-catalog.js';
|
|
21
|
+
export { GRAPHCODE_INCOSE_SCOPE, PHASE_GATE_RULES, PHASE_GATE_LABELS, PHASE_GATE_CREATIONS, IMPL_GATE_PHASE, IMPL_GATE_MILESTONES, IMPL_GATE_RULES, ABSENT_CREATION_PROVIDER, creationBlockingMsg, summarizeReadiness, computeReadiness, scoreReadiness, getFamilyRuleIds, type IncoseScope, type CreationCurrency, type CreationCurrencyProvider, type ReadinessDimension, type ReadinessGate, type ReadinessReport, } from './readiness.js';
|
|
22
|
+
export { NEUTRAL_COMPLETENESS, COMPLETENESS_SLICES, scoreCompleteness, typeOf, hasOutTo, refFile, hasTestRef, hasCodeRef, isLeafFunc, fchainActorBounded, type CGraph, type CNode, type GateCompleteness, type CompletenessLeg, } from './readiness-completeness.js';
|
|
23
|
+
export * from './panels.js';
|
package/dist/index.js
CHANGED
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
* no harness. The write path is unchanged — `callHost` forwards to the host's
|
|
7
7
|
* ONE `mutate()` gate; this package never opens a second one.
|
|
8
8
|
*
|
|
9
|
-
* Scope split (CR-GC-264):
|
|
10
|
-
* - here
|
|
9
|
+
* Scope split (CR-GC-264/265):
|
|
10
|
+
* - here : host-socket client, view catalog, readiness projection, dashboard panels
|
|
11
11
|
* - @sigloch/graphcode : store, gate, MCP server, exporters, host election
|
|
12
12
|
*
|
|
13
|
+
* The rule of thumb for what belongs here: it takes plain data in and returns
|
|
14
|
+
* plain data out. Anything that needs the store, the gate or a process stays in
|
|
15
|
+
* the substrate.
|
|
16
|
+
*
|
|
13
17
|
* @author andreas@siglochconsulting
|
|
14
18
|
*/
|
|
15
19
|
export { HOST_SOCK_BASENAME, callHost, } from './host-client.js';
|
|
16
20
|
export { MARKDOWN_VIEWS, VIEW_FILENAMES } from './view-catalog.js';
|
|
21
|
+
export { GRAPHCODE_INCOSE_SCOPE, PHASE_GATE_RULES, PHASE_GATE_LABELS, PHASE_GATE_CREATIONS, IMPL_GATE_PHASE, IMPL_GATE_MILESTONES, IMPL_GATE_RULES, ABSENT_CREATION_PROVIDER, creationBlockingMsg, summarizeReadiness, computeReadiness, scoreReadiness, getFamilyRuleIds, } from './readiness.js';
|
|
22
|
+
export { NEUTRAL_COMPLETENESS, COMPLETENESS_SLICES, scoreCompleteness, typeOf, hasOutTo, refFile, hasTestRef, hasCodeRef, isLeafFunc, fchainActorBounded, } from './readiness-completeness.js';
|
|
23
|
+
export * from './panels.js';
|
package/dist/panels.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MOD-dashboard — headless panel data-layer (CR-GC-115, moved here CR-GC-265).
|
|
3
|
+
*
|
|
4
|
+
* The READ-ONLY data layer behind every dashboard panel: pure functions that
|
|
5
|
+
* shape tool outputs (graph_readiness / graph_impact / rules_get_violations /
|
|
6
|
+
* host /health) into view-models an external renderer mounts. No HTTP, no DOM,
|
|
7
|
+
* no mutation — REQ-dashboard-readonly is structural (these are pure projections
|
|
8
|
+
* of read-only inputs).
|
|
9
|
+
*
|
|
10
|
+
* Lives in graphcode-client, not graphcode: a renderer needs these functions and
|
|
11
|
+
* nothing else from the substrate. Importing them from `@sigloch/graphcode` also
|
|
12
|
+
* dragged in kuzu-wasm and the MCP SDK — a viewer that is forbidden from opening
|
|
13
|
+
* a store was installing an embedded graph database to draw a panel.
|
|
14
|
+
*
|
|
15
|
+
* @author andreas@siglochconsulting
|
|
16
|
+
*/
|
|
17
|
+
import type { ReadinessReport, ReadinessGate, CreationCurrency, CreationCurrencyProvider } from './readiness.js';
|
|
18
|
+
import type { RuleViolation, LiveUpdateEvent } from '@sigloch/contracts/harness';
|
|
19
|
+
export interface GatePanel {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
passed: boolean;
|
|
23
|
+
score: number;
|
|
24
|
+
/** The blocking elements — the drill-down behind the traffic light. */
|
|
25
|
+
blocking: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Structural completeness (CR-GC-250): ONE value per gate (covered/total) for
|
|
28
|
+
* the panel; `missing[]` is the on-click per-leg drill-down, same mechanic as
|
|
29
|
+
* `blocking`. REQ-completeness-single-value.
|
|
30
|
+
*/
|
|
31
|
+
completeness: ReadinessGate['completeness'];
|
|
32
|
+
}
|
|
33
|
+
export interface ReadinessPanel {
|
|
34
|
+
compliancePct: number;
|
|
35
|
+
totalElements: number;
|
|
36
|
+
elementsWithErrors: number;
|
|
37
|
+
phaseGates: GatePanel[];
|
|
38
|
+
implGates: GatePanel[];
|
|
39
|
+
}
|
|
40
|
+
export declare function readinessPanel(report: ReadinessReport): ReadinessPanel;
|
|
41
|
+
export interface RecommendationItem {
|
|
42
|
+
ruleId: string;
|
|
43
|
+
severity: 'error' | 'warning' | 'info';
|
|
44
|
+
elementId?: string;
|
|
45
|
+
message: string;
|
|
46
|
+
fixHint?: string;
|
|
47
|
+
/** The single top-ranked candidate to link (CR-GC-203 items 1+3), if any. */
|
|
48
|
+
topCandidate?: {
|
|
49
|
+
id: string;
|
|
50
|
+
type: string;
|
|
51
|
+
name: string;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export interface RecommendationsPanel {
|
|
55
|
+
items: RecommendationItem[];
|
|
56
|
+
total: number;
|
|
57
|
+
}
|
|
58
|
+
export declare function recommendationsPanel(violations: RuleViolation[], limit?: number): RecommendationsPanel;
|
|
59
|
+
export type Freshness = 'live' | 'stale' | 'absent';
|
|
60
|
+
/**
|
|
61
|
+
* Two artifact kinds with TWO distinct staleness mechanisms (CR-GC-222):
|
|
62
|
+
* - `render` — a deterministic `docs/views/*.md` projection; stale via file mtime vs graph.
|
|
63
|
+
* - `analysis` — judgment work (FMEA/ConOps/Trade/…); stale when the analyzed SCOPE moved,
|
|
64
|
+
* NEVER via mtime. The single `staleVsGraph` boolean was the defect (it applied
|
|
65
|
+
* an mtime check to analyses that don't refresh on mtime).
|
|
66
|
+
*/
|
|
67
|
+
export type ArtifactKind = 'render' | 'analysis';
|
|
68
|
+
/** INCOSE/SE-standard vs graphcode-specific — so non-INCOSE rows are not mislabeled as INCOSE. */
|
|
69
|
+
export type ArtifactGroup = 'incose' | 'graphcode';
|
|
70
|
+
/** Catalog entry: the SSOT for an artifact's kind/group/label (labels are NAMES, never `id==label`). */
|
|
71
|
+
export interface ArtifactCatalogEntry {
|
|
72
|
+
id: string;
|
|
73
|
+
label: string;
|
|
74
|
+
kind: ArtifactKind;
|
|
75
|
+
group: ArtifactGroup;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The canonical INCOSE-tab artifact catalog (readiness-artifact-model.md §6). Renders are the
|
|
79
|
+
* deterministic CR-GC-220 views; the 5 analysis ids are exactly CR-GC-221's creation keys
|
|
80
|
+
* (`conops`/`assumption-review`/`fmea`/`trade`/`implplan`). `irr` is renamed to "Assumption
|
|
81
|
+
* Review" and grouped graphcode-specific (it is not a standard INCOSE artifact).
|
|
82
|
+
*/
|
|
83
|
+
export declare const ARTIFACT_CATALOG: readonly ArtifactCatalogEntry[];
|
|
84
|
+
/** Classify a RENDER artifact. `exists` = doc present; `staleVsGraph` = graph changed since render. */
|
|
85
|
+
export declare function artifactFreshness(exists: boolean, staleVsGraph: boolean): Freshness;
|
|
86
|
+
/** Classify an ANALYSIS artifact from its scope-currency — 🟢 current / 🟡 stale / 🔴 absent. */
|
|
87
|
+
export declare function analysisFreshness(currency: CreationCurrency): Freshness;
|
|
88
|
+
export interface ArtifactStatus {
|
|
89
|
+
id: string;
|
|
90
|
+
label: string;
|
|
91
|
+
kind: ArtifactKind;
|
|
92
|
+
group: ArtifactGroup;
|
|
93
|
+
freshness: Freshness;
|
|
94
|
+
}
|
|
95
|
+
/** One artifact's freshness signal: render rows carry exists/staleVsGraph, analysis rows carry currency. */
|
|
96
|
+
export interface ArtifactSignal {
|
|
97
|
+
id: string;
|
|
98
|
+
/** RENDER: a materialized doc is present. */
|
|
99
|
+
exists?: boolean;
|
|
100
|
+
/** RENDER: the graph changed since the doc was generated (mtime). */
|
|
101
|
+
staleVsGraph?: boolean;
|
|
102
|
+
/** ANALYSIS: scope-currency of the judgment work (the ONLY signal read for analysis rows). */
|
|
103
|
+
currency?: CreationCurrency;
|
|
104
|
+
}
|
|
105
|
+
export interface ArtifactsPanel {
|
|
106
|
+
artifacts: ArtifactStatus[];
|
|
107
|
+
liveCount: number;
|
|
108
|
+
staleCount: number;
|
|
109
|
+
absentCount: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Shape artifact freshness signals into the tab view-model. The catalog decides each row's
|
|
113
|
+
* kind/group/label; the kind decides WHICH mechanism classifies it — render rows go through
|
|
114
|
+
* the mtime path, analysis rows through the scope-currency path (so a creation is NEVER
|
|
115
|
+
* classified by mtime, even if a caller passes `staleVsGraph` — it is ignored). Renderer groups
|
|
116
|
+
* by `group` to show "INCOSE / SE-standard" and "graphcode-specific" separately.
|
|
117
|
+
*/
|
|
118
|
+
export declare function artifactsPanel(signals: ArtifactSignal[]): ArtifactsPanel;
|
|
119
|
+
/**
|
|
120
|
+
* Build the CreationCurrencyProvider that CR-GC-221's readiness scorer consumes (CR-GC-222
|
|
121
|
+
* delivers the `analysis`-currency to 221). Maps each creation id → its scope-currency; an
|
|
122
|
+
* unknown/never-analyzed creation reads as 🔴 absent.
|
|
123
|
+
*/
|
|
124
|
+
export declare function creationCurrencyProvider(analysis: Array<{
|
|
125
|
+
id: string;
|
|
126
|
+
currency: CreationCurrency;
|
|
127
|
+
}>): CreationCurrencyProvider;
|
|
128
|
+
export interface ImpactPanel {
|
|
129
|
+
root: string;
|
|
130
|
+
blastRadiusNodes: number;
|
|
131
|
+
blastRadiusEdges: number;
|
|
132
|
+
}
|
|
133
|
+
export declare function impactPanel(impact: {
|
|
134
|
+
rootId: string;
|
|
135
|
+
nodeCount: number;
|
|
136
|
+
edgeCount: number;
|
|
137
|
+
}): ImpactPanel;
|
|
138
|
+
export interface HealthPanel {
|
|
139
|
+
ok: boolean;
|
|
140
|
+
store: string;
|
|
141
|
+
gate: string;
|
|
142
|
+
versions: Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
export declare function healthPanel(health: {
|
|
145
|
+
status?: string;
|
|
146
|
+
store?: string;
|
|
147
|
+
gate?: string;
|
|
148
|
+
versions?: Record<string, unknown>;
|
|
149
|
+
}): HealthPanel;
|
|
150
|
+
/** Which panel ids a viewer should refresh for this invalidation event. */
|
|
151
|
+
export declare function panelsForEvent(event: LiveUpdateEvent): string[];
|
package/dist/panels.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
function toGatePanel(g) {
|
|
2
|
+
return { id: g.id, label: g.label, passed: g.passed, score: g.score, blocking: g.blocking, completeness: g.completeness };
|
|
3
|
+
}
|
|
4
|
+
export function readinessPanel(report) {
|
|
5
|
+
return {
|
|
6
|
+
compliancePct: Math.round(report.compliance.score * 1000) / 10,
|
|
7
|
+
totalElements: report.compliance.totalElements,
|
|
8
|
+
elementsWithErrors: report.compliance.elementsWithErrors,
|
|
9
|
+
phaseGates: report.phaseGates.map(toGatePanel),
|
|
10
|
+
implGates: report.implGates.map(toGatePanel),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const SEVERITY_RANK = { error: 0, warning: 1, info: 2 };
|
|
14
|
+
export function recommendationsPanel(violations, limit = 5) {
|
|
15
|
+
const ranked = [...violations].sort((a, b) => (SEVERITY_RANK[a.severity] ?? 9) - (SEVERITY_RANK[b.severity] ?? 9));
|
|
16
|
+
const items = ranked.slice(0, limit).map((v) => {
|
|
17
|
+
const ctx = v.context;
|
|
18
|
+
return {
|
|
19
|
+
ruleId: v.ruleId,
|
|
20
|
+
severity: v.severity,
|
|
21
|
+
elementId: v.elementId,
|
|
22
|
+
message: v.message,
|
|
23
|
+
fixHint: v.fixHint,
|
|
24
|
+
topCandidate: ctx?.candidate_targets?.[0],
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
return { items, total: violations.length };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The canonical INCOSE-tab artifact catalog (readiness-artifact-model.md §6). Renders are the
|
|
31
|
+
* deterministic CR-GC-220 views; the 5 analysis ids are exactly CR-GC-221's creation keys
|
|
32
|
+
* (`conops`/`assumption-review`/`fmea`/`trade`/`implplan`). `irr` is renamed to "Assumption
|
|
33
|
+
* Review" and grouped graphcode-specific (it is not a standard INCOSE artifact).
|
|
34
|
+
*/
|
|
35
|
+
export const ARTIFACT_CATALOG = [
|
|
36
|
+
// Renders — INCOSE/SE-standard deterministic views (mtime-classified).
|
|
37
|
+
{ id: 'srs', label: 'Requirements Spec (SRS)', kind: 'render', group: 'incose' },
|
|
38
|
+
{ id: 'architecture', label: 'Architecture (SDD)', kind: 'render', group: 'incose' },
|
|
39
|
+
{ id: 'rtm', label: 'Req-Test Traceability Matrix', kind: 'render', group: 'incose' },
|
|
40
|
+
{ id: 'nfr', label: 'NFR Register', kind: 'render', group: 'incose' },
|
|
41
|
+
{ id: 'icd', label: 'Interface Control Document', kind: 'render', group: 'incose' },
|
|
42
|
+
{ id: 'testconcept', label: 'Test Concept', kind: 'render', group: 'incose' },
|
|
43
|
+
{ id: 'testmatrix', label: 'Test Matrix (VCRM)', kind: 'render', group: 'incose' },
|
|
44
|
+
{ id: 'intplan', label: 'Integration & Test Plan', kind: 'render', group: 'incose' },
|
|
45
|
+
{ id: 'changelog', label: 'Change Log', kind: 'render', group: 'incose' },
|
|
46
|
+
{ id: 'references', label: 'Requirements Traceability', kind: 'render', group: 'incose' },
|
|
47
|
+
// Creations — judgment work (scope-classified, NEVER mtime).
|
|
48
|
+
{ id: 'conops', label: 'Concept of Operations', kind: 'analysis', group: 'incose' },
|
|
49
|
+
{ id: 'fmea', label: 'FMEA', kind: 'analysis', group: 'incose' },
|
|
50
|
+
{ id: 'trade', label: 'Trade Study', kind: 'analysis', group: 'incose' },
|
|
51
|
+
{ id: 'implplan', label: 'Implementation Plan', kind: 'analysis', group: 'incose' },
|
|
52
|
+
{ id: 'assumption-review', label: 'Assumption Review', kind: 'analysis', group: 'graphcode' },
|
|
53
|
+
];
|
|
54
|
+
/** Classify a RENDER artifact. `exists` = doc present; `staleVsGraph` = graph changed since render. */
|
|
55
|
+
export function artifactFreshness(exists, staleVsGraph) {
|
|
56
|
+
if (!exists)
|
|
57
|
+
return 'absent';
|
|
58
|
+
return staleVsGraph ? 'stale' : 'live';
|
|
59
|
+
}
|
|
60
|
+
/** Classify an ANALYSIS artifact from its scope-currency — 🟢 current / 🟡 stale / 🔴 absent. */
|
|
61
|
+
export function analysisFreshness(currency) {
|
|
62
|
+
return currency === 'current' ? 'live' : currency === 'stale' ? 'stale' : 'absent';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Shape artifact freshness signals into the tab view-model. The catalog decides each row's
|
|
66
|
+
* kind/group/label; the kind decides WHICH mechanism classifies it — render rows go through
|
|
67
|
+
* the mtime path, analysis rows through the scope-currency path (so a creation is NEVER
|
|
68
|
+
* classified by mtime, even if a caller passes `staleVsGraph` — it is ignored). Renderer groups
|
|
69
|
+
* by `group` to show "INCOSE / SE-standard" and "graphcode-specific" separately.
|
|
70
|
+
*/
|
|
71
|
+
export function artifactsPanel(signals) {
|
|
72
|
+
const byId = new Map(ARTIFACT_CATALOG.map((e) => [e.id, e]));
|
|
73
|
+
const statuses = signals.map((s) => {
|
|
74
|
+
const entry = byId.get(s.id);
|
|
75
|
+
const kind = entry?.kind ?? 'render';
|
|
76
|
+
const freshness = kind === 'analysis'
|
|
77
|
+
? analysisFreshness(s.currency ?? 'absent')
|
|
78
|
+
: artifactFreshness(s.exists ?? false, s.staleVsGraph ?? false);
|
|
79
|
+
return { id: s.id, label: entry?.label ?? s.id, kind, group: entry?.group ?? 'graphcode', freshness };
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
artifacts: statuses,
|
|
83
|
+
liveCount: statuses.filter((s) => s.freshness === 'live').length,
|
|
84
|
+
staleCount: statuses.filter((s) => s.freshness === 'stale').length,
|
|
85
|
+
absentCount: statuses.filter((s) => s.freshness === 'absent').length,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build the CreationCurrencyProvider that CR-GC-221's readiness scorer consumes (CR-GC-222
|
|
90
|
+
* delivers the `analysis`-currency to 221). Maps each creation id → its scope-currency; an
|
|
91
|
+
* unknown/never-analyzed creation reads as 🔴 absent.
|
|
92
|
+
*/
|
|
93
|
+
export function creationCurrencyProvider(analysis) {
|
|
94
|
+
const m = new Map(analysis.map((a) => [a.id, a.currency]));
|
|
95
|
+
return (creation) => m.get(creation) ?? 'absent';
|
|
96
|
+
}
|
|
97
|
+
export function impactPanel(impact) {
|
|
98
|
+
return { root: impact.rootId, blastRadiusNodes: impact.nodeCount, blastRadiusEdges: impact.edgeCount };
|
|
99
|
+
}
|
|
100
|
+
export function healthPanel(health) {
|
|
101
|
+
return {
|
|
102
|
+
ok: health.status === 'ok',
|
|
103
|
+
store: health.store ?? 'unknown',
|
|
104
|
+
gate: health.gate ?? 'unknown',
|
|
105
|
+
versions: health.versions ?? {},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// FUNC-subscribe-updates — map a live-update event (CR-GC-114 SSE) to the panel
|
|
110
|
+
// ids that must refresh. The viewer subscribes once and re-fetches only those.
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
const DOMAIN_TO_PANELS = {
|
|
113
|
+
graph: ['graph', 'impact', 'artifacts'],
|
|
114
|
+
rules: ['recommendations', 'readiness'],
|
|
115
|
+
readiness: ['readiness', 'implGates', 'artifacts'],
|
|
116
|
+
suggestions: ['recommendations'],
|
|
117
|
+
};
|
|
118
|
+
/** Which panel ids a viewer should refresh for this invalidation event. */
|
|
119
|
+
export function panelsForEvent(event) {
|
|
120
|
+
return [...new Set(event.domains.flatMap((d) => DOMAIN_TO_PANELS[d] ?? []))];
|
|
121
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* readiness-completeness.ts — the COMPLETENESS dimension of readiness (CR-GC-250,
|
|
3
|
+
* split out of readiness.ts by CR-GC-260).
|
|
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
|
+
* BROWSER-SAFE — no `node:*` import may enter this file: the panels layer builds on it.
|
|
11
|
+
*
|
|
12
|
+
* @author andreas@siglochconsulting
|
|
13
|
+
*/
|
|
14
|
+
import type { Graph } from '@sigloch/graph-api-core';
|
|
15
|
+
export type CGraph = Pick<Graph, 'nodes' | 'edges'>;
|
|
16
|
+
export type CNode = Graph['nodes'][number];
|
|
17
|
+
/** A gate's structural completeness over its driving population. */
|
|
18
|
+
export interface GateCompleteness {
|
|
19
|
+
/** Source elements whose chain leg is complete. */
|
|
20
|
+
covered: number;
|
|
21
|
+
/** Total source elements evaluated across the gate's legs (the denominator). */
|
|
22
|
+
total: number;
|
|
23
|
+
/** Per-leg misses as `"<leg>: <uid>"` — the on-click drill-down detail. */
|
|
24
|
+
missing: string[];
|
|
25
|
+
}
|
|
26
|
+
/** Neutral completeness for gates that carry no chain slice (impl gates). */
|
|
27
|
+
export declare const NEUTRAL_COMPLETENESS: GateCompleteness;
|
|
28
|
+
/**
|
|
29
|
+
* One completeness leg: every node of `sourceType` (the driving population)
|
|
30
|
+
* must satisfy `holds`, else it is a `missing` source for the owning gate.
|
|
31
|
+
* Cardinality legs use the id `"<SRC>→<TGT>"` so the drift test can match them
|
|
32
|
+
* against the meta-model's `1..*` patterns.
|
|
33
|
+
*/
|
|
34
|
+
export interface CompletenessLeg {
|
|
35
|
+
id: string;
|
|
36
|
+
sourceType: string;
|
|
37
|
+
holds: (node: CNode, graph: CGraph, byId: Map<string, CNode>) => boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare const typeOf: (byId: Map<string, CNode>, id: string) => string | undefined;
|
|
40
|
+
/** True iff `src` has ≥1 out-edge of `edgeType` to a node of `targetType`. */
|
|
41
|
+
export declare function hasOutTo(g: CGraph, byId: Map<string, CNode>, src: string, edgeType: string, targetType: string): boolean;
|
|
42
|
+
export declare const refFile: (ref: unknown) => string | undefined;
|
|
43
|
+
export declare const hasTestRef: (n: CNode) => boolean;
|
|
44
|
+
export declare const hasCodeRef: (n: CNode) => boolean;
|
|
45
|
+
/** A leaf FUNC has no `compose→FUNC` children (parents are realized by children — meta-model §28). */
|
|
46
|
+
export declare const isLeafFunc: (n: CNode, g: CGraph, byId: Map<string, CNode>) => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* A FCHAIN is actor-bounded iff its function set has a trigger AND a consumer:
|
|
49
|
+
* `ACTOR —io→ FLOW —io→ FUNC∈chain` (entry) and `FUNC∈chain —io→ FLOW —io→ ACTOR`
|
|
50
|
+
* (exit). Catches the "hollow chain" — functions with no who-triggers / who-consumes.
|
|
51
|
+
*/
|
|
52
|
+
export declare function fchainActorBounded(fchain: CNode, g: CGraph, byId: Map<string, CNode>): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Phase-gate → chain legs that must be complete AT THAT PHASE. `concept:true`
|
|
55
|
+
* stays valid through CDR (spec allows unbound tests/funcs); only TRR demands
|
|
56
|
+
* real bindings. The `1..*` cardinality legs (SYS→UC, UC→FCHAIN, UC→REQ,
|
|
57
|
+
* FCHAIN→FUNC) are drift-locked against the meta-model by TEST-readiness-completeness.
|
|
58
|
+
*/
|
|
59
|
+
export declare const COMPLETENESS_SLICES: Record<string, CompletenessLeg[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Score one phase gate's structural completeness over its driving population.
|
|
62
|
+
* `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
|
|
63
|
+
*/
|
|
64
|
+
export declare function scoreCompleteness(gateId: string, graph: CGraph): GateCompleteness;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Neutral completeness for gates that carry no chain slice (impl gates). */
|
|
2
|
+
export const NEUTRAL_COMPLETENESS = { covered: 0, total: 0, missing: [] };
|
|
3
|
+
export const typeOf = (byId, id) => byId.get(id)?.type;
|
|
4
|
+
/** True iff `src` has ≥1 out-edge of `edgeType` to a node of `targetType`. */
|
|
5
|
+
export function hasOutTo(g, byId, src, edgeType, targetType) {
|
|
6
|
+
return g.edges.some((e) => e.sourceId === src && e.edgeType === edgeType && typeOf(byId, e.targetId) === targetType);
|
|
7
|
+
}
|
|
8
|
+
export const refFile = (ref) => ref?.file;
|
|
9
|
+
export const hasTestRef = (n) => !!refFile(n.attributes?.testRef);
|
|
10
|
+
export const hasCodeRef = (n) => !!refFile(n.attributes?.realRef);
|
|
11
|
+
/** A leaf FUNC has no `compose→FUNC` children (parents are realized by children — meta-model §28). */
|
|
12
|
+
export const isLeafFunc = (n, g, byId) => !hasOutTo(g, byId, n.uid, 'compose', 'FUNC');
|
|
13
|
+
/**
|
|
14
|
+
* A FCHAIN is actor-bounded iff its function set has a trigger AND a consumer:
|
|
15
|
+
* `ACTOR —io→ FLOW —io→ FUNC∈chain` (entry) and `FUNC∈chain —io→ FLOW —io→ ACTOR`
|
|
16
|
+
* (exit). Catches the "hollow chain" — functions with no who-triggers / who-consumes.
|
|
17
|
+
*/
|
|
18
|
+
export function fchainActorBounded(fchain, g, byId) {
|
|
19
|
+
const funcs = new Set(g.edges.filter((e) => e.sourceId === fchain.uid && e.edgeType === 'compose' && typeOf(byId, e.targetId) === 'FUNC').map((e) => e.targetId));
|
|
20
|
+
if (funcs.size === 0)
|
|
21
|
+
return false;
|
|
22
|
+
const io = g.edges.filter((e) => e.edgeType === 'io');
|
|
23
|
+
const entry = io.some((e) => funcs.has(e.targetId) && typeOf(byId, e.sourceId) === 'FLOW' &&
|
|
24
|
+
io.some((a) => a.targetId === e.sourceId && typeOf(byId, a.sourceId) === 'ACTOR'));
|
|
25
|
+
const exit = io.some((e) => funcs.has(e.sourceId) && typeOf(byId, e.targetId) === 'FLOW' &&
|
|
26
|
+
io.some((a) => a.sourceId === e.targetId && typeOf(byId, a.targetId) === 'ACTOR'));
|
|
27
|
+
return entry && exit;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Phase-gate → chain legs that must be complete AT THAT PHASE. `concept:true`
|
|
31
|
+
* stays valid through CDR (spec allows unbound tests/funcs); only TRR demands
|
|
32
|
+
* real bindings. The `1..*` cardinality legs (SYS→UC, UC→FCHAIN, UC→REQ,
|
|
33
|
+
* FCHAIN→FUNC) are drift-locked against the meta-model by TEST-readiness-completeness.
|
|
34
|
+
*/
|
|
35
|
+
export const COMPLETENESS_SLICES = {
|
|
36
|
+
// Behavioral spine exists: system has use cases, every UC derives a chain AND requirements.
|
|
37
|
+
SRR: [
|
|
38
|
+
{ id: 'SYS→UC', sourceType: 'SYS', holds: (n, g, b) => hasOutTo(g, b, n.uid, 'compose', 'UC') },
|
|
39
|
+
{ id: 'UC→FCHAIN', sourceType: 'UC', holds: (n, g, b) => hasOutTo(g, b, n.uid, 'compose', 'FCHAIN') },
|
|
40
|
+
{ id: 'UC→REQ', sourceType: 'UC', holds: (n, g, b) => hasOutTo(g, b, n.uid, 'compose', 'REQ') },
|
|
41
|
+
],
|
|
42
|
+
// Functional architecture: every chain has functions AND is actor-bounded (trigger + consumer).
|
|
43
|
+
PDR: [
|
|
44
|
+
{ id: 'FCHAIN→FUNC', sourceType: 'FCHAIN', holds: (n, g, b) => hasOutTo(g, b, n.uid, 'compose', 'FUNC') },
|
|
45
|
+
{ id: 'FCHAIN-actor-bounded', sourceType: 'FCHAIN', holds: (n, g, b) => fchainActorBounded(n, g, b) },
|
|
46
|
+
],
|
|
47
|
+
// Interfaces carry a data contract: every FLOW has a SCHEMA (CR-GC-106 schema-before-code).
|
|
48
|
+
CDR: [
|
|
49
|
+
{ id: 'FLOW→SCHEMA', sourceType: 'FLOW', holds: (n, g, b) => hasOutTo(g, b, n.uid, 'relation', 'SCHEMA') },
|
|
50
|
+
],
|
|
51
|
+
// Binding: tests are runnable and leaf functions carry code — concept no longer counts here.
|
|
52
|
+
TRR: [
|
|
53
|
+
{ id: 'TEST.testRef', sourceType: 'TEST', holds: (n) => hasTestRef(n) },
|
|
54
|
+
{ id: 'FUNC.realRef', sourceType: 'FUNC', holds: (n, g, b) => !isLeafFunc(n, g, b) || hasCodeRef(n) },
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Score one phase gate's structural completeness over its driving population.
|
|
59
|
+
* `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
|
|
60
|
+
*/
|
|
61
|
+
export function scoreCompleteness(gateId, graph) {
|
|
62
|
+
const legs = COMPLETENESS_SLICES[gateId] ?? [];
|
|
63
|
+
const byId = new Map(graph.nodes.map((n) => [n.uid, n]));
|
|
64
|
+
let covered = 0;
|
|
65
|
+
let total = 0;
|
|
66
|
+
const missing = [];
|
|
67
|
+
for (const leg of legs) {
|
|
68
|
+
for (const node of graph.nodes) {
|
|
69
|
+
if (node.type !== leg.sourceType)
|
|
70
|
+
continue;
|
|
71
|
+
total += 1;
|
|
72
|
+
if (leg.holds(node, graph, byId))
|
|
73
|
+
covered += 1;
|
|
74
|
+
else
|
|
75
|
+
missing.push(`${leg.id}: ${node.uid}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { covered, total, missing };
|
|
79
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
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';
|
|
44
|
+
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
|
+
/**
|
|
61
|
+
* INCOSE artifact scope for the system under governance.
|
|
62
|
+
* `lean` — graph = SSOT; renders are deterministic projections, creations are
|
|
63
|
+
* lightweight judgment inputs + gate preconditions (graphcode's stance,
|
|
64
|
+
* refined CR-GC-226). NOT "no artifacts".
|
|
65
|
+
* `full` — ASIL-D-grade evidence (full FMEA S/O/D rigor, ASIL isolation) (not graphcode).
|
|
66
|
+
*/
|
|
67
|
+
export type IncoseScope = 'lean' | 'full';
|
|
68
|
+
/** graphcode's declared scope — see the module header for the rationale. */
|
|
69
|
+
export declare const GRAPHCODE_INCOSE_SCOPE: IncoseScope;
|
|
70
|
+
/**
|
|
71
|
+
* Phase-Readiness gates (INCOSE technical reviews) → the element-level V3_RULES
|
|
72
|
+
* each gate owns. DISJOINT + EXHAUSTIVE over the 15 element rules; verified by
|
|
73
|
+
* TEST-readiness-model against SE_DESCRIPTOR at runtime (never hardcoded count).
|
|
74
|
+
*/
|
|
75
|
+
export declare const PHASE_GATE_RULES: Record<string, readonly string[]>;
|
|
76
|
+
/** Human labels for the phase gates. */
|
|
77
|
+
export declare const PHASE_GATE_LABELS: Record<string, string>;
|
|
78
|
+
/**
|
|
79
|
+
* Creation artifacts each phase gate requires (CR-GC-221): the judgment work
|
|
80
|
+
* ("rule-green ≠ analysis-done") a rule-clean structure cannot prove was ever
|
|
81
|
+
* done. A gate is only `passed` if its required creations are 🟢-current.
|
|
82
|
+
* TRR carries none (tests are inline in the graph, not a separate creation).
|
|
83
|
+
*/
|
|
84
|
+
export declare const PHASE_GATE_CREATIONS: Record<string, readonly string[]>;
|
|
85
|
+
/** Impl gate → the phase whose creations it inherits as an anti-vacuous-green precondition. */
|
|
86
|
+
export declare const IMPL_GATE_PHASE: Record<string, string>;
|
|
87
|
+
/** Currency of a creation artifact: 🟢 current / 🟡 stale / 🔴 absent. */
|
|
88
|
+
export type CreationCurrency = 'current' | 'stale' | 'absent';
|
|
89
|
+
/**
|
|
90
|
+
* Resolves whether a creation artifact is current/stale/absent (CR-GC-221 = the
|
|
91
|
+
* INTERFACE; CR-GC-222's classifier supplies the real implementation). When no
|
|
92
|
+
* provider is passed to `computeReadiness`, creation enforcement is OFF
|
|
93
|
+
* (back-compat) — the gates still report their `creationArtifacts`, but currency
|
|
94
|
+
* is not checked until 222 wires a provider in.
|
|
95
|
+
*/
|
|
96
|
+
export type CreationCurrencyProvider = (creation: string) => CreationCurrency;
|
|
97
|
+
/** Default stub provider (CR-GC-221, "Default 🔴"): everything absent until CR-GC-222 lands. */
|
|
98
|
+
export declare const ABSENT_CREATION_PROVIDER: CreationCurrencyProvider;
|
|
99
|
+
/** Format a "<Creation> not performed (<gate> creation)" blocking message.
|
|
100
|
+
* Exported so the help layer (CR-GC-228) can identify creation blockers in
|
|
101
|
+
* `ReadinessGate.blocking[]` without re-deriving the format (single source). */
|
|
102
|
+
export declare function creationBlockingMsg(creation: string, gateId: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Implementation-Readiness gates (program/build acceptance) → milestone tier.
|
|
105
|
+
* Ready iff every CR assigned to the MS is done + the MS scope is error-clean.
|
|
106
|
+
*/
|
|
107
|
+
export declare const IMPL_GATE_MILESTONES: Record<string, {
|
|
108
|
+
ms: string;
|
|
109
|
+
label: string;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* The 2 milestone-level V3_RULES the impl gates embody (MS-01 empty scope,
|
|
113
|
+
* MS-02 dangling dependency). Together with the phase-gate rules these span
|
|
114
|
+
* V3_RULES exhaustively — so the model covers it with nothing left over.
|
|
115
|
+
*/
|
|
116
|
+
export declare const IMPL_GATE_RULES: readonly string[];
|
|
117
|
+
/**
|
|
118
|
+
* A single readiness gate outcome.
|
|
119
|
+
* `passed` is the hard verdict (no blocking/error item); `score` ∈ [0,1] is a
|
|
120
|
+
* continuous maturity indicator; `open` items are advisory (warnings/CR debt).
|
|
121
|
+
*/
|
|
122
|
+
export interface ReadinessGate {
|
|
123
|
+
/** Gate id — SRR/PDR/CDR/TRR (phase) or SAR/FCA/SVR/FRR (impl). */
|
|
124
|
+
id: string;
|
|
125
|
+
/** Human-readable review name. */
|
|
126
|
+
label: string;
|
|
127
|
+
/** True iff no blocking (error-severity / not-done) item remains. */
|
|
128
|
+
passed: boolean;
|
|
129
|
+
/** Continuous maturity in [0, 1]. */
|
|
130
|
+
score: number;
|
|
131
|
+
/** Blocking items that hold the gate closed (errors / open CRs / scope errors). */
|
|
132
|
+
blocking: string[];
|
|
133
|
+
/** Advisory items surfaced but not blocking (warnings, info, scope notes). */
|
|
134
|
+
open: string[];
|
|
135
|
+
/** Creation artifacts this gate requires (CR-GC-221) — judgment work, not rules. */
|
|
136
|
+
creationArtifacts: string[];
|
|
137
|
+
/**
|
|
138
|
+
* Structural completeness of the derivation chain owned by this gate
|
|
139
|
+
* (CR-GC-250) — orthogonal to violation severity. `covered/total` over the
|
|
140
|
+
* DRIVING population, so absence counts (0 FCHAIN → all UCs incomplete → SRR
|
|
141
|
+
* red). One value per gate for the dashboard; `missing[]` is the on-click
|
|
142
|
+
* per-leg detail. A gate cannot pass while `covered < total`. Impl gates carry
|
|
143
|
+
* a neutral `{0,0,[]}` (completeness is a phase-gate concept).
|
|
144
|
+
*/
|
|
145
|
+
completeness: GateCompleteness;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Full readiness snapshot derived from V3_RULES evaluation + MS + status.
|
|
149
|
+
* All violation ruleIds are contracts rule-IDs (R-xx / RD-xx / MS-xx) — never BQ-*.
|
|
150
|
+
*/
|
|
151
|
+
export interface ReadinessReport {
|
|
152
|
+
/**
|
|
153
|
+
* Compliance dimension: fraction of graph elements with NO error-severity
|
|
154
|
+
* violation. 1.0 = all elements clean, 0.0 = all elements have ≥1 error.
|
|
155
|
+
*/
|
|
156
|
+
compliance: ReadinessDimension;
|
|
157
|
+
/** Declared INCOSE artifact scope for this system (graphcode = `lean`). */
|
|
158
|
+
incoseScope: IncoseScope;
|
|
159
|
+
/** Phase-Readiness gates (INCOSE technical reviews) in lifecycle order. */
|
|
160
|
+
phaseGates: ReadinessGate[];
|
|
161
|
+
/** Implementation-Readiness gates (program/build acceptance) in tier order. */
|
|
162
|
+
implGates: ReadinessGate[];
|
|
163
|
+
/**
|
|
164
|
+
* Per-contracts-rule-ID violation counts. Keys are exactly the
|
|
165
|
+
* `SE_DESCRIPTOR.rules` members that fired. Foreign BQ-* IDs cannot appear —
|
|
166
|
+
* the scorer delegates exclusively to the V3 rule engine.
|
|
167
|
+
*/
|
|
168
|
+
violationsByRule: Record<string, number>;
|
|
169
|
+
/** All raw violations, ordered by severity (error → warning → info). */
|
|
170
|
+
violations: RuleViolation[];
|
|
171
|
+
/**
|
|
172
|
+
* Whether creation-artifact currency was ENFORCED in this report (CR-GC-259).
|
|
173
|
+
* `off` means no `CreationCurrencyProvider` was supplied, so every gate's
|
|
174
|
+
* `creationArtifacts` was REPORTED but never checked — a gate can read
|
|
175
|
+
* `passed` with a never-performed FMEA/ConOps behind it. Made explicit because
|
|
176
|
+
* a silently-unenforced gate is the same false-green class CR-GC-250 closed:
|
|
177
|
+
* a consumer cannot distinguish "no creation blockers" from "not checked".
|
|
178
|
+
* graphcode itself has no classifier yet (the real provider is fed by the
|
|
179
|
+
* dashboard), so the product path reports `off` until one lands.
|
|
180
|
+
*/
|
|
181
|
+
creationEnforcement: 'on' | 'off';
|
|
182
|
+
/** Timestamp (ISO 8601) when this report was computed. */
|
|
183
|
+
computedAt: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Summary projection of a ReadinessReport (CR-GC-203 item 2): drops the heavy
|
|
187
|
+
* per-element lists — `violations` and every gate's `blocking`/`open` — while
|
|
188
|
+
* keeping scores, counts and `violationsByRule`. `graph_readiness` returns this
|
|
189
|
+
* by default; `detail:true` returns the full lists. On a fully-red graph the
|
|
190
|
+
* full report inlined every blocking element (86k+ chars, past the MCP tool
|
|
191
|
+
* result limit) forcing a file-spill; the summary stays small.
|
|
192
|
+
*/
|
|
193
|
+
export declare function summarizeReadiness(report: ReadinessReport): ReadinessReport;
|
|
194
|
+
/**
|
|
195
|
+
* Compute readiness from a violations array + the full graph (nodes + edges).
|
|
196
|
+
*
|
|
197
|
+
* @param violations - from `harness.evaluateRules()` (contracts V3 rule IDs)
|
|
198
|
+
* @param graph - from `harness.getGraph()` (elements + traces)
|
|
199
|
+
*/
|
|
200
|
+
export declare function computeReadiness(violations: RuleViolation[], graph: Pick<Graph, 'nodes' | 'edges'>, creationCurrency?: CreationCurrencyProvider): ReadinessReport;
|
|
201
|
+
/**
|
|
202
|
+
* Convenience wrapper: runs `evaluateRules()` + `getGraph()` on the harness
|
|
203
|
+
* and returns the full ReadinessReport.
|
|
204
|
+
*
|
|
205
|
+
* Accepts a minimal harness-duck-type so callers can pass `GraphCodeHarness`
|
|
206
|
+
* directly without importing its class (avoids circular dep risk in consumers).
|
|
207
|
+
*/
|
|
208
|
+
export declare function scoreReadiness(harness: {
|
|
209
|
+
evaluateRules(): RuleViolation[];
|
|
210
|
+
getGraph(): Pick<Graph, 'nodes' | 'edges'>;
|
|
211
|
+
}): ReadinessReport;
|
|
212
|
+
/**
|
|
213
|
+
* The authoritative set of family rule-IDs (derived at runtime from
|
|
214
|
+
* SE_DESCRIPTOR — the graph rules — plus the contracts RC conformance rules
|
|
215
|
+
* (CR-GC-253); never hardcoded here). Exposed for test assertions and
|
|
216
|
+
* dashboard consumers.
|
|
217
|
+
*/
|
|
218
|
+
export declare function getFamilyRuleIds(): Set<string>;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { SE_DESCRIPTOR } from '@sigloch/graph-api-core';
|
|
2
|
+
import { CODE_CONFORMANCE_RULES } 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
|
+
/**
|
|
7
|
+
* Phase-Readiness gates (INCOSE technical reviews) → the element-level V3_RULES
|
|
8
|
+
* each gate owns. DISJOINT + EXHAUSTIVE over the 15 element rules; verified by
|
|
9
|
+
* TEST-readiness-model against SE_DESCRIPTOR at runtime (never hardcoded count).
|
|
10
|
+
*/
|
|
11
|
+
export const PHASE_GATE_RULES = {
|
|
12
|
+
// System + use-case structure exists; every requirement is verifiable.
|
|
13
|
+
SRR: ['R-17', 'R-14', 'R-01'],
|
|
14
|
+
// Functional architecture: chains, actors, flows; functions trace to REQs.
|
|
15
|
+
PDR: ['R-15', 'R-16', 'R-10', 'R-02'],
|
|
16
|
+
// Detailed design: ASIL isolation, module size, no cycles, sound decomposition,
|
|
17
|
+
// valid trace patterns (R-18: every trace's element-type pair is legal — now also
|
|
18
|
+
// flags any residual REQ→MOD allocate edge, CR-228), module allocation (R-22
|
|
19
|
+
// FUNC→MOD, R-23 MOD→FUNC — CR-202/208), and FUNC→code binding — presence (R-20: every non-concept
|
|
20
|
+
// FUNC carries a realRef, CR-GC-205 Item 5) AND resolution (RC-01: the realRef
|
|
21
|
+
// points at a declared symbol on disk — CR-GC-253 conformance, with CodeFacts).
|
|
22
|
+
// SCHEMA realRef binding (R-26 presence, RC-03 resolution, RC-04 parsed-at-interface —
|
|
23
|
+
// CR-211) and physical-MOD realRef presence (R-27, CR-228) join the realRef family
|
|
24
|
+
// (R-20/RC-01) here: interfaces + parts are detailed design.
|
|
25
|
+
// RC-05 (cross-module import drift — CR-212) is module-coupling: also detailed design.
|
|
26
|
+
CDR: ['R-03', 'R-04', 'R-12', 'R-18', 'R-20', 'R-22', 'R-23', 'R-26', 'R-27', 'RC-01', 'RC-03', 'RC-04', 'RC-05', 'RD-01', 'RD-02', 'RD-03'],
|
|
27
|
+
// Verification wired (TEST→REQ) + referential trace consistency + runnable test
|
|
28
|
+
// bindings — presence (R-19) AND resolution (RC-02: file + case exist, CR-GC-253).
|
|
29
|
+
TRR: ['R-05', 'R-08', 'R-19', 'R-21', 'RC-02'],
|
|
30
|
+
};
|
|
31
|
+
/** Human labels for the phase gates. */
|
|
32
|
+
export const PHASE_GATE_LABELS = {
|
|
33
|
+
SRR: 'System Requirements Review',
|
|
34
|
+
PDR: 'Preliminary Design Review',
|
|
35
|
+
CDR: 'Critical Design Review',
|
|
36
|
+
TRR: 'Test Readiness Review',
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Creation artifacts each phase gate requires (CR-GC-221): the judgment work
|
|
40
|
+
* ("rule-green ≠ analysis-done") a rule-clean structure cannot prove was ever
|
|
41
|
+
* done. A gate is only `passed` if its required creations are 🟢-current.
|
|
42
|
+
* TRR carries none (tests are inline in the graph, not a separate creation).
|
|
43
|
+
*/
|
|
44
|
+
export const PHASE_GATE_CREATIONS = {
|
|
45
|
+
SRR: ['conops', 'assumption-review'],
|
|
46
|
+
PDR: ['fmea', 'trade'],
|
|
47
|
+
CDR: ['implplan'],
|
|
48
|
+
TRR: [],
|
|
49
|
+
};
|
|
50
|
+
/** Human labels for creation artifacts (used in blocking messages). */
|
|
51
|
+
const CREATION_LABELS = {
|
|
52
|
+
conops: 'ConOps',
|
|
53
|
+
'assumption-review': 'Assumption Review',
|
|
54
|
+
fmea: 'FMEA',
|
|
55
|
+
trade: 'Trade study',
|
|
56
|
+
implplan: 'Impl plan',
|
|
57
|
+
};
|
|
58
|
+
/** Impl gate → the phase whose creations it inherits as an anti-vacuous-green precondition. */
|
|
59
|
+
export const IMPL_GATE_PHASE = {
|
|
60
|
+
SAR: 'SRR',
|
|
61
|
+
FCA: 'PDR',
|
|
62
|
+
SVR: 'CDR',
|
|
63
|
+
FRR: 'TRR',
|
|
64
|
+
};
|
|
65
|
+
/** Default stub provider (CR-GC-221, "Default 🔴"): everything absent until CR-GC-222 lands. */
|
|
66
|
+
export const ABSENT_CREATION_PROVIDER = () => 'absent';
|
|
67
|
+
/** Format a "<Creation> not performed (<gate> creation)" blocking message.
|
|
68
|
+
* Exported so the help layer (CR-GC-228) can identify creation blockers in
|
|
69
|
+
* `ReadinessGate.blocking[]` without re-deriving the format (single source). */
|
|
70
|
+
export function creationBlockingMsg(creation, gateId) {
|
|
71
|
+
return `${CREATION_LABELS[creation] ?? creation} not performed (${gateId} creation)`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Implementation-Readiness gates (program/build acceptance) → milestone tier.
|
|
75
|
+
* Ready iff every CR assigned to the MS is done + the MS scope is error-clean.
|
|
76
|
+
*/
|
|
77
|
+
export const IMPL_GATE_MILESTONES = {
|
|
78
|
+
SAR: { ms: 'MS-1-specification', label: 'System Acceptance Review' },
|
|
79
|
+
FCA: { ms: 'MS-2-coding-vv', label: 'Functional Configuration Audit' },
|
|
80
|
+
SVR: { ms: 'MS-3-mvp-readiness', label: 'System Verification Review' },
|
|
81
|
+
FRR: { ms: 'MS-4-mvp2', label: 'Functional Readiness Review' },
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* The 2 milestone-level V3_RULES the impl gates embody (MS-01 empty scope,
|
|
85
|
+
* MS-02 dangling dependency). Together with the phase-gate rules these span
|
|
86
|
+
* V3_RULES exhaustively — so the model covers it with nothing left over.
|
|
87
|
+
*/
|
|
88
|
+
export const IMPL_GATE_RULES = ['MS-01', 'MS-02'];
|
|
89
|
+
/**
|
|
90
|
+
* Summary projection of a ReadinessReport (CR-GC-203 item 2): drops the heavy
|
|
91
|
+
* per-element lists — `violations` and every gate's `blocking`/`open` — while
|
|
92
|
+
* keeping scores, counts and `violationsByRule`. `graph_readiness` returns this
|
|
93
|
+
* by default; `detail:true` returns the full lists. On a fully-red graph the
|
|
94
|
+
* full report inlined every blocking element (86k+ chars, past the MCP tool
|
|
95
|
+
* result limit) forcing a file-spill; the summary stays small.
|
|
96
|
+
*/
|
|
97
|
+
export function summarizeReadiness(report) {
|
|
98
|
+
const stripGate = (g) => ({
|
|
99
|
+
...g,
|
|
100
|
+
blocking: [],
|
|
101
|
+
open: [],
|
|
102
|
+
// Keep the one-value covered/total for the dashboard; drop the per-leg drill-down.
|
|
103
|
+
completeness: { ...g.completeness, missing: [] },
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
...report,
|
|
107
|
+
phaseGates: report.phaseGates.map(stripGate),
|
|
108
|
+
implGates: report.implGates.map(stripGate),
|
|
109
|
+
violations: [],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Scorer
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/** Group violations by element-id for per-element error lookups. */
|
|
116
|
+
function elementsWithErrorSet(violations) {
|
|
117
|
+
return new Set(violations
|
|
118
|
+
.filter((v) => v.severity === 'error' && v.elementId !== undefined)
|
|
119
|
+
.map((v) => v.elementId));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Score one phase gate from the violations whose ruleId it owns, plus its
|
|
123
|
+
* required creation artifacts (CR-GC-221): a gate is only `passed` if it is
|
|
124
|
+
* rule-clean AND every required creation is 🟢-current. When `currency` is
|
|
125
|
+
* undefined, creation enforcement is OFF (back-compat) — `creationArtifacts`
|
|
126
|
+
* is still reported. Creations affect `passed`/`blocking`, not the rule `score`.
|
|
127
|
+
*/
|
|
128
|
+
function scorePhaseGate(id, ruleIds, violations, creations, graph, currency) {
|
|
129
|
+
const owned = violations.filter((v) => ruleIds.includes(v.ruleId));
|
|
130
|
+
const errors = owned.filter((v) => v.severity === 'error');
|
|
131
|
+
const rulesWithError = new Set(errors.map((v) => v.ruleId));
|
|
132
|
+
const ruleBlocking = errors.map((v) => `${v.ruleId}: ${v.message}`);
|
|
133
|
+
const open = owned.filter((v) => v.severity !== 'error').map((v) => `${v.ruleId}: ${v.message}`);
|
|
134
|
+
// Phase gate requires every creation 🟢-current — stale OR absent blocks.
|
|
135
|
+
const creationBlocking = currency
|
|
136
|
+
? creations.filter((c) => currency(c) !== 'current').map((c) => creationBlockingMsg(c, id))
|
|
137
|
+
: [];
|
|
138
|
+
// Structural completeness (CR-GC-250): one aggregated blocking line, not N warnings.
|
|
139
|
+
const completeness = scoreCompleteness(id, graph);
|
|
140
|
+
const complete = completeness.covered === completeness.total;
|
|
141
|
+
const completenessBlocking = complete
|
|
142
|
+
? []
|
|
143
|
+
: [`${id} completeness ${completeness.covered}/${completeness.total} — ${completeness.missing.join('; ')}`];
|
|
144
|
+
const blocking = [...ruleBlocking, ...creationBlocking, ...completenessBlocking];
|
|
145
|
+
// Maturity = share of the gate's hard checks with no error, floored by completeness coverage:
|
|
146
|
+
// a gate cannot read green while a mandated chain leg is empty.
|
|
147
|
+
const ruleScore = ruleIds.length > 0 ? (ruleIds.length - rulesWithError.size) / ruleIds.length : 1;
|
|
148
|
+
const completenessRatio = completeness.total > 0 ? completeness.covered / completeness.total : 1;
|
|
149
|
+
return {
|
|
150
|
+
id,
|
|
151
|
+
label: PHASE_GATE_LABELS[id] ?? id,
|
|
152
|
+
passed: rulesWithError.size === 0 && creationBlocking.length === 0 && complete,
|
|
153
|
+
score: Math.min(ruleScore, completenessRatio),
|
|
154
|
+
blocking,
|
|
155
|
+
open,
|
|
156
|
+
creationArtifacts: [...creations],
|
|
157
|
+
completeness,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Score one implementation gate from its milestone's CRs + scope (MS + status),
|
|
162
|
+
* plus an anti-vacuous-green creation check (CR-GC-221): the gate additionally
|
|
163
|
+
* blocks when a creation required by its phase is 🔴 **absent** — even with no
|
|
164
|
+
* representing CR (a never-done FMEA must not pass silently). Only `absent`
|
|
165
|
+
* blocks here (not `stale`); enforcement is off when `currency` is undefined.
|
|
166
|
+
*/
|
|
167
|
+
function scoreImplGate(id, ms, label, graph, elementsWithError, creations, currency) {
|
|
168
|
+
const msNode = graph.nodes.find((n) => n.uid === ms);
|
|
169
|
+
if (!msNode) {
|
|
170
|
+
return {
|
|
171
|
+
id,
|
|
172
|
+
label,
|
|
173
|
+
passed: false,
|
|
174
|
+
score: 0,
|
|
175
|
+
blocking: [`milestone ${ms} missing`],
|
|
176
|
+
open: [],
|
|
177
|
+
creationArtifacts: [...creations],
|
|
178
|
+
completeness: NEUTRAL_COMPLETENESS,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.uid, n]));
|
|
182
|
+
// CRs assigned to this milestone: CR -relation-> MS (the MS-01 scope semantics).
|
|
183
|
+
const crs = graph.edges
|
|
184
|
+
.filter((e) => e.edgeType === 'relation' && e.targetId === ms)
|
|
185
|
+
.map((e) => nodeById.get(e.sourceId))
|
|
186
|
+
.filter((n) => !!n && n.type === 'CR');
|
|
187
|
+
const openCrs = crs.filter((c) => c.attributes?.status !== 'done');
|
|
188
|
+
// Milestone scope: composed elements (MS -compose-> X) must be error-clean.
|
|
189
|
+
const scopeErrors = graph.edges
|
|
190
|
+
.filter((e) => e.sourceId === ms && e.edgeType === 'compose')
|
|
191
|
+
.map((e) => e.targetId)
|
|
192
|
+
.filter((id2) => elementsWithError.has(id2));
|
|
193
|
+
// Anti-vacuous-green: a phase creation that is 🔴 absent blocks the impl gate
|
|
194
|
+
// even when every CR is done and the scope is clean (the analysis was never done).
|
|
195
|
+
const creationBlocking = currency
|
|
196
|
+
? creations.filter((c) => currency(c) === 'absent').map((c) => creationBlockingMsg(c, id))
|
|
197
|
+
: [];
|
|
198
|
+
const blocking = [
|
|
199
|
+
...openCrs.map((c) => `${c.uid} not done`),
|
|
200
|
+
...scopeErrors.map((id2) => `${id2} has an error-severity violation`),
|
|
201
|
+
...creationBlocking,
|
|
202
|
+
];
|
|
203
|
+
const msStatus = msNode.attributes?.status;
|
|
204
|
+
const score = crs.length > 0
|
|
205
|
+
? (crs.length - openCrs.length) / crs.length
|
|
206
|
+
: msStatus === 'done' || msStatus === 'reviewed' ? 1 : 0;
|
|
207
|
+
return { id, label, passed: blocking.length === 0, score, blocking, open: [], creationArtifacts: [...creations], completeness: NEUTRAL_COMPLETENESS };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Compute readiness from a violations array + the full graph (nodes + edges).
|
|
211
|
+
*
|
|
212
|
+
* @param violations - from `harness.evaluateRules()` (contracts V3 rule IDs)
|
|
213
|
+
* @param graph - from `harness.getGraph()` (elements + traces)
|
|
214
|
+
*/
|
|
215
|
+
export function computeReadiness(violations, graph, creationCurrency) {
|
|
216
|
+
const totalElements = graph.nodes.length;
|
|
217
|
+
const elementsWithError = elementsWithErrorSet(violations);
|
|
218
|
+
const elementsWithErrors = elementsWithError.size;
|
|
219
|
+
const complianceScore = totalElements > 0 ? (totalElements - elementsWithErrors) / totalElements : 1;
|
|
220
|
+
// Violation counts keyed by contracts rule-ID (never BQ-*).
|
|
221
|
+
const violationsByRule = {};
|
|
222
|
+
for (const v of violations) {
|
|
223
|
+
violationsByRule[v.ruleId] = (violationsByRule[v.ruleId] ?? 0) + 1;
|
|
224
|
+
}
|
|
225
|
+
// Sort: error → warning → info.
|
|
226
|
+
const severityRank = { error: 0, warning: 1, info: 2 };
|
|
227
|
+
const sorted = [...violations].sort((a, b) => (severityRank[a.severity] ?? 3) - (severityRank[b.severity] ?? 3));
|
|
228
|
+
const phaseGates = Object.entries(PHASE_GATE_RULES).map(([id, ruleIds]) => scorePhaseGate(id, ruleIds, violations, PHASE_GATE_CREATIONS[id] ?? [], graph, creationCurrency));
|
|
229
|
+
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));
|
|
230
|
+
return {
|
|
231
|
+
compliance: {
|
|
232
|
+
score: complianceScore,
|
|
233
|
+
label: 'Compliance (V3_RULES, error-severity)',
|
|
234
|
+
totalElements,
|
|
235
|
+
elementsWithErrors,
|
|
236
|
+
},
|
|
237
|
+
incoseScope: GRAPHCODE_INCOSE_SCOPE,
|
|
238
|
+
phaseGates,
|
|
239
|
+
implGates,
|
|
240
|
+
violationsByRule,
|
|
241
|
+
violations: sorted,
|
|
242
|
+
creationEnforcement: creationCurrency ? 'on' : 'off',
|
|
243
|
+
computedAt: new Date().toISOString(),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Convenience wrapper: runs `evaluateRules()` + `getGraph()` on the harness
|
|
248
|
+
* and returns the full ReadinessReport.
|
|
249
|
+
*
|
|
250
|
+
* Accepts a minimal harness-duck-type so callers can pass `GraphCodeHarness`
|
|
251
|
+
* directly without importing its class (avoids circular dep risk in consumers).
|
|
252
|
+
*/
|
|
253
|
+
export function scoreReadiness(harness) {
|
|
254
|
+
const violations = harness.evaluateRules();
|
|
255
|
+
const graph = harness.getGraph();
|
|
256
|
+
return computeReadiness(violations, graph);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The authoritative set of family rule-IDs (derived at runtime from
|
|
260
|
+
* SE_DESCRIPTOR — the graph rules — plus the contracts RC conformance rules
|
|
261
|
+
* (CR-GC-253); never hardcoded here). Exposed for test assertions and
|
|
262
|
+
* dashboard consumers.
|
|
263
|
+
*/
|
|
264
|
+
export function getFamilyRuleIds() {
|
|
265
|
+
return new Set([
|
|
266
|
+
...(SE_DESCRIPTOR.rules ?? []).map((r) => r.id),
|
|
267
|
+
...CODE_CONFORMANCE_RULES.map((r) => r.id),
|
|
268
|
+
]);
|
|
269
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigloch/graphcode-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"test": "vitest run",
|
|
17
17
|
"prepublishOnly": "npm run build && npm run test"
|
|
18
18
|
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@sigloch/contracts": "^0.8.0",
|
|
21
|
+
"@sigloch/graph-api-core": "^0.4.1"
|
|
22
|
+
},
|
|
19
23
|
"license": "MIT",
|
|
20
24
|
"description": "Read-side client for a running @sigloch/graphcode host — host-socket calls, dashboard panels, readiness projection and the markdown view catalog. Deliberately free of Kuzu and the MCP SDK so a viewer can depend on it without pulling an embedded graph database.",
|
|
21
25
|
"author": "sigloch-consulting",
|