@sigloch/graphcode-client 0.4.0 → 0.6.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 CHANGED
@@ -19,5 +19,5 @@
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
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';
22
+ export { NEUTRAL_COMPLETENESS, COMPLETENESS_SLICES, scoreCompleteness, typeOf, type CGraph, type CNode, type GateCompleteness, type CompletenessLeg, } from './readiness-completeness.js';
23
23
  export * from './panels.js';
package/dist/index.js CHANGED
@@ -19,5 +19,5 @@
19
19
  export { HOST_SOCK_BASENAME, callHost, } from './host-client.js';
20
20
  export { MARKDOWN_VIEWS, VIEW_FILENAMES } from './view-catalog.js';
21
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';
22
+ export { NEUTRAL_COMPLETENESS, COMPLETENESS_SLICES, scoreCompleteness, typeOf, } from './readiness-completeness.js';
23
23
  export * from './panels.js';
package/dist/panels.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import type { ReadinessReport, ReadinessGate, CreationCurrency, CreationCurrencyProvider } from './readiness.js';
18
18
  import type { RuleViolation, LiveUpdateEvent } from '@sigloch/contracts/harness';
19
+ import type { Graph } from '@sigloch/graph-api-core';
19
20
  export interface GatePanel {
20
21
  id: string;
21
22
  label: string;
@@ -125,6 +126,23 @@ export declare function creationCurrencyProvider(analysis: Array<{
125
126
  id: string;
126
127
  currency: CreationCurrency;
127
128
  }>): CreationCurrencyProvider;
129
+ /**
130
+ * Real scope-currency for the 5 analysis artifacts (CR-SM-227) — replaces the
131
+ * `ABSENT_CREATION_PROVIDER` placeholder (CR-GVE-225 F10: GVE showed `absent`
132
+ * correctly because no scope-currency check existed yet). Reads the
133
+ * `graphVersion` freshness stamp AF-01..05 (contracts) check for presence of
134
+ * (`SYS.attributes.analysisFreshness.<id>`) and classifies it against the
135
+ * live graphVersion: missing/invalid stamp → absent; stamp older than the
136
+ * current graphVersion → stale; stamp at (or ahead of) it → current.
137
+ * Grober Graph-Versions-Stempel only, no scope-hash — CR-SM-227's declared
138
+ * first-cut limitation ("Achtung" section), not full scope-precise staleness.
139
+ */
140
+ export declare function computeAnalysisCurrency(graph: Pick<Graph, 'nodes'>, currentGraphVersion: number): Array<{
141
+ id: string;
142
+ currency: CreationCurrency;
143
+ }>;
144
+ /** Convenience: build the real CreationCurrencyProvider straight off the graph + its live version. */
145
+ export declare function analysisCreationCurrencyProvider(graph: Pick<Graph, 'nodes'>, currentGraphVersion: number): CreationCurrencyProvider;
128
146
  export interface ImpactPanel {
129
147
  root: string;
130
148
  blastRadiusNodes: number;
package/dist/panels.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { AnalysisArtifactId, AnalysisFreshnessStampSchema } from '@sigloch/contracts/se';
1
2
  function toGatePanel(g) {
2
3
  return { id: g.id, label: g.label, passed: g.passed, score: g.score, blocking: g.blocking, completeness: g.completeness };
3
4
  }
@@ -94,6 +95,34 @@ export function creationCurrencyProvider(analysis) {
94
95
  const m = new Map(analysis.map((a) => [a.id, a.currency]));
95
96
  return (creation) => m.get(creation) ?? 'absent';
96
97
  }
98
+ /**
99
+ * Real scope-currency for the 5 analysis artifacts (CR-SM-227) — replaces the
100
+ * `ABSENT_CREATION_PROVIDER` placeholder (CR-GVE-225 F10: GVE showed `absent`
101
+ * correctly because no scope-currency check existed yet). Reads the
102
+ * `graphVersion` freshness stamp AF-01..05 (contracts) check for presence of
103
+ * (`SYS.attributes.analysisFreshness.<id>`) and classifies it against the
104
+ * live graphVersion: missing/invalid stamp → absent; stamp older than the
105
+ * current graphVersion → stale; stamp at (or ahead of) it → current.
106
+ * Grober Graph-Versions-Stempel only, no scope-hash — CR-SM-227's declared
107
+ * first-cut limitation ("Achtung" section), not full scope-precise staleness.
108
+ */
109
+ export function computeAnalysisCurrency(graph, currentGraphVersion) {
110
+ const sys = graph.nodes.find((n) => n.type === 'SYS');
111
+ const stamps = (sys?.attributes?.analysisFreshness ?? {});
112
+ return AnalysisArtifactId.options.map((id) => {
113
+ const parsed = AnalysisFreshnessStampSchema.safeParse(stamps[id]);
114
+ const currency = !parsed.success
115
+ ? 'absent'
116
+ : parsed.data.graphVersion >= currentGraphVersion
117
+ ? 'current'
118
+ : 'stale';
119
+ return { id, currency };
120
+ });
121
+ }
122
+ /** Convenience: build the real CreationCurrencyProvider straight off the graph + its live version. */
123
+ export function analysisCreationCurrencyProvider(graph, currentGraphVersion) {
124
+ return creationCurrencyProvider(computeAnalysisCurrency(graph, currentGraphVersion));
125
+ }
97
126
  export function impactPanel(impact) {
98
127
  return { root: impact.rootId, blastRadiusNodes: impact.nodeCount, blastRadiusEdges: impact.edgeCount };
99
128
  }
@@ -1,17 +1,28 @@
1
1
  /**
2
2
  * readiness-completeness.ts — the COMPLETENESS dimension of readiness (CR-GC-250,
3
- * split out of readiness.ts by CR-GC-260).
3
+ * split out of readiness.ts by CR-GC-260; rebuilt on the rule stream CR-SM-226).
4
4
  *
5
5
  * Its own module because it answers a different question than the rest of readiness:
6
6
  * compliance/gates ask "does any element violate a rule", completeness asks "is the
7
7
  * structural chain covered at all" — the absence case rules cannot see (0 FCHAIN → a
8
8
  * per-element warning rule fires 0 times and the gate reads green).
9
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/testRef/
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).
19
+ *
10
20
  * BROWSER-SAFE — no `node:*` import may enter this file: the panels layer builds on it.
11
21
  *
12
22
  * @author andreas@siglochconsulting
13
23
  */
14
24
  import type { Graph } from '@sigloch/graph-api-core';
25
+ import type { RuleViolation } from '@sigloch/contracts/harness';
15
26
  export type CGraph = Pick<Graph, 'nodes' | 'edges'>;
16
27
  export type CNode = Graph['nodes'][number];
17
28
  /** A gate's structural completeness over its driving population. */
@@ -25,40 +36,34 @@ export interface GateCompleteness {
25
36
  }
26
37
  /** Neutral completeness for gates that carry no chain slice (impl gates). */
27
38
  export declare const NEUTRAL_COMPLETENESS: GateCompleteness;
39
+ export declare const typeOf: (byId: Map<string, CNode>, id: string) => string | undefined;
28
40
  /**
29
41
  * 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.
42
+ * must carry NO violation of `ruleId` (its `@sigloch/contracts` rule twin),
43
+ * else it is a `missing` source for the owning gate. Cardinality legs use the
44
+ * id `"<SRC>→<TGT>"` so the drift test can match them against the
45
+ * meta-model's `1..*` patterns.
33
46
  */
34
47
  export interface CompletenessLeg {
35
48
  id: string;
36
49
  sourceType: string;
37
- holds: (node: CNode, graph: CGraph, byId: Map<string, CNode>) => boolean;
50
+ ruleId: string;
38
51
  }
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
52
  /**
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.
53
+ * Phase-gate → chain legs that must be complete AT THAT PHASE, each backed by
54
+ * its `@sigloch/contracts` rule twin (CR-SM-226):
55
+ * SRR: SYS→UC ≈ R-17 (SYS must have compose; not UC-typed specifically —
56
+ * no dedicated SYS→UC rule exists, R-17 is the closest twin),
57
+ * UC→FCHAIN = UC-03, UC→REQ = UC-01.
58
+ * PDR: FCHAIN→FUNC = R-15, FCHAIN-actor-bounded = FC-04 (trigger+consumer).
59
+ * CDR: FLOW→SCHEMA = SC-04 (sharp per-FLOW version of SC-02).
60
+ * TRR: TEST.testRef = R-19, FUNC.realRef = R-20.
58
61
  */
59
62
  export declare const COMPLETENESS_SLICES: Record<string, CompletenessLeg[]>;
60
63
  /**
61
64
  * Score one phase gate's structural completeness over its driving population.
62
65
  * `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
66
+ * `violations` is the full rule-stream output (e.g. `harness.evaluateRules()`);
67
+ * only the leg's own `ruleId` is consulted per node.
63
68
  */
64
- export declare function scoreCompleteness(gateId: string, graph: CGraph): GateCompleteness;
69
+ export declare function scoreCompleteness(gateId: string, graph: CGraph, violations: RuleViolation[]): GateCompleteness;
@@ -1,78 +1,62 @@
1
1
  /** Neutral completeness for gates that carry no chain slice (impl gates). */
2
2
  export const NEUTRAL_COMPLETENESS = { covered: 0, total: 0, missing: [] };
3
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
4
  /**
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.
5
+ * Phase-gate → chain legs that must be complete AT THAT PHASE, each backed by
6
+ * its `@sigloch/contracts` rule twin (CR-SM-226):
7
+ * SRR: SYS→UC ≈ R-17 (SYS must have compose; not UC-typed specifically —
8
+ * no dedicated SYS→UC rule exists, R-17 is the closest twin),
9
+ * UC→FCHAIN = UC-03, UC→REQ = UC-01.
10
+ * PDR: FCHAIN→FUNC = R-15, FCHAIN-actor-bounded = FC-04 (trigger+consumer).
11
+ * CDR: FLOW→SCHEMA = SC-04 (sharp per-FLOW version of SC-02).
12
+ * TRR: TEST.testRef = R-19, FUNC.realRef = R-20.
34
13
  */
35
14
  export const COMPLETENESS_SLICES = {
36
15
  // Behavioral spine exists: system has use cases, every UC derives a chain AND requirements.
37
16
  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') },
17
+ { id: 'SYS→UC', sourceType: 'SYS', ruleId: 'R-17' },
18
+ { id: 'UC→FCHAIN', sourceType: 'UC', ruleId: 'UC-03' },
19
+ { id: 'UC→REQ', sourceType: 'UC', ruleId: 'UC-01' },
41
20
  ],
42
21
  // Functional architecture: every chain has functions AND is actor-bounded (trigger + consumer).
43
22
  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) },
23
+ { id: 'FCHAIN→FUNC', sourceType: 'FCHAIN', ruleId: 'R-15' },
24
+ { id: 'FCHAIN-actor-bounded', sourceType: 'FCHAIN', ruleId: 'FC-04' },
46
25
  ],
47
26
  // Interfaces carry a data contract: every FLOW has a SCHEMA (CR-GC-106 schema-before-code).
48
27
  CDR: [
49
- { id: 'FLOW→SCHEMA', sourceType: 'FLOW', holds: (n, g, b) => hasOutTo(g, b, n.uid, 'relation', 'SCHEMA') },
28
+ { id: 'FLOW→SCHEMA', sourceType: 'FLOW', ruleId: 'SC-04' },
50
29
  ],
51
- // Binding: tests are runnable and leaf functions carry code — concept no longer counts here.
30
+ // Binding: tests are runnable and functions carry code — concept/external exemptions
31
+ // are R-19/R-20's own business now (single source of truth), not re-decided here.
52
32
  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) },
33
+ { id: 'TEST.testRef', sourceType: 'TEST', ruleId: 'R-19' },
34
+ { id: 'FUNC.realRef', sourceType: 'FUNC', ruleId: 'R-20' },
55
35
  ],
56
36
  };
57
37
  /**
58
38
  * Score one phase gate's structural completeness over its driving population.
59
39
  * `total === 0` (no source elements to cover) → vacuously complete (ratio 1).
40
+ * `violations` is the full rule-stream output (e.g. `harness.evaluateRules()`);
41
+ * only the leg's own `ruleId` is consulted per node.
60
42
  */
61
- export function scoreCompleteness(gateId, graph) {
43
+ export function scoreCompleteness(gateId, graph, violations) {
62
44
  const legs = COMPLETENESS_SLICES[gateId] ?? [];
63
- const byId = new Map(graph.nodes.map((n) => [n.uid, n]));
64
45
  let covered = 0;
65
46
  let total = 0;
66
47
  const missing = [];
67
48
  for (const leg of legs) {
49
+ const violatingIds = new Set(violations
50
+ .filter((v) => v.ruleId === leg.ruleId && v.elementId !== undefined)
51
+ .map((v) => v.elementId));
68
52
  for (const node of graph.nodes) {
69
53
  if (node.type !== leg.sourceType)
70
54
  continue;
71
55
  total += 1;
72
- if (leg.holds(node, graph, byId))
73
- covered += 1;
74
- else
56
+ if (violatingIds.has(node.uid))
75
57
  missing.push(`${leg.id}: ${node.uid}`);
58
+ else
59
+ covered += 1;
76
60
  }
77
61
  }
78
62
  return { covered, total, missing };
package/dist/readiness.js CHANGED
@@ -142,7 +142,8 @@ function scorePhaseGate(id, ruleIds, violations, creations, graph, currency) {
142
142
  ? creations.filter((c) => currency(c) !== 'current').map((c) => creationBlockingMsg(c, id))
143
143
  : [];
144
144
  // Structural completeness (CR-GC-250): one aggregated blocking line, not N warnings.
145
- const completeness = scoreCompleteness(id, graph);
145
+ // CR-SM-226: reads off the SAME violations array (the rule stream), no re-walk.
146
+ const completeness = scoreCompleteness(id, graph, violations);
146
147
  const complete = completeness.covered === completeness.total;
147
148
  const completenessBlocking = complete
148
149
  ? []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigloch/graphcode-client",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "prepublishOnly": "npm run build && npm run test"
18
18
  },
19
19
  "dependencies": {
20
- "@sigloch/contracts": "^2.0.0",
20
+ "@sigloch/contracts": "^3.0.0",
21
21
  "@sigloch/graph-api-core": "^2.0.0"
22
22
  },
23
23
  "license": "MIT",