@sigloch/graph-api-core 5.3.0 → 5.4.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/browser.d.ts +2 -0
- package/dist/browser.js +1 -0
- package/dist/format-e-codec.d.ts +14 -2
- package/dist/format-e-codec.js +44 -6
- package/dist/impact-slice.d.ts +57 -0
- package/dist/impact-slice.js +49 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/rule-engine.d.ts +18 -3
- package/dist/rule-engine.js +11 -2
- package/dist/se-descriptor.d.ts +8 -1
- package/dist/se-descriptor.js +20 -6
- package/package.json +3 -3
package/dist/browser.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export { findRoot } from './find-root.js';
|
|
15
15
|
export type { RootQueryGraph } from './find-root.js';
|
|
16
|
+
export { impactSlice } from './impact-slice.js';
|
|
17
|
+
export type { ImpactSlice, ImpactSliceNode, ImpactRole } from './impact-slice.js';
|
|
16
18
|
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
17
19
|
export { FormatECodec } from './format-e-codec.js';
|
|
18
20
|
export { isValidTrace, tracePatternsOf } from './types.js';
|
package/dist/browser.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @author andreas@siglochconsulting
|
|
13
13
|
*/
|
|
14
14
|
export { findRoot } from './find-root.js';
|
|
15
|
+
export { impactSlice } from './impact-slice.js';
|
|
15
16
|
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
16
17
|
export { FormatECodec } from './format-e-codec.js';
|
|
17
18
|
export { isValidTrace, tracePatternsOf } from './types.js';
|
package/dist/format-e-codec.d.ts
CHANGED
|
@@ -18,8 +18,20 @@ export declare class FormatECodec {
|
|
|
18
18
|
parse(input: string, options?: {
|
|
19
19
|
resolveType?: (uid: string) => string | undefined;
|
|
20
20
|
}): FormatEDiff;
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Serialize a Graph to Format E text.
|
|
23
|
+
*
|
|
24
|
+
* CR-GC-373: `options.omitProvenance` is the **agent view** — provenance attributes
|
|
25
|
+
* (`created_at`, `updated_at`, `ranAt` inside `testRefs` entries, `weight` at its
|
|
26
|
+
* default 1) are dropped; everything that is work instruction (`codeRef`, `realRef`,
|
|
27
|
+
* `testRefs` file/tool/level, `kinds`, `constraint`, `zodDefinition`, `status`) stays.
|
|
28
|
+
* NOT a second codec: the default stays complete — round-trip is the contract, the
|
|
29
|
+
* agent view the exception. Measured on the CR-GC-114 job slice: 18% of the text was
|
|
30
|
+
* pure timestamps.
|
|
31
|
+
*/
|
|
32
|
+
serialize(graph: Graph, options?: {
|
|
33
|
+
omitProvenance?: boolean;
|
|
34
|
+
}): string;
|
|
23
35
|
private parseNodeLine;
|
|
24
36
|
private parseEdgeLine;
|
|
25
37
|
private parseMerge;
|
package/dist/format-e-codec.js
CHANGED
|
@@ -149,8 +149,19 @@ export class FormatECodec {
|
|
|
149
149
|
}
|
|
150
150
|
return { operations, errors };
|
|
151
151
|
}
|
|
152
|
-
/**
|
|
153
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Serialize a Graph to Format E text.
|
|
154
|
+
*
|
|
155
|
+
* CR-GC-373: `options.omitProvenance` is the **agent view** — provenance attributes
|
|
156
|
+
* (`created_at`, `updated_at`, `ranAt` inside `testRefs` entries, `weight` at its
|
|
157
|
+
* default 1) are dropped; everything that is work instruction (`codeRef`, `realRef`,
|
|
158
|
+
* `testRefs` file/tool/level, `kinds`, `constraint`, `zodDefinition`, `status`) stays.
|
|
159
|
+
* NOT a second codec: the default stays complete — round-trip is the contract, the
|
|
160
|
+
* agent view the exception. Measured on the CR-GC-114 job slice: 18% of the text was
|
|
161
|
+
* pure timestamps.
|
|
162
|
+
*/
|
|
163
|
+
serialize(graph, options = {}) {
|
|
164
|
+
const omit = options.omitProvenance === true;
|
|
154
165
|
const lines = [];
|
|
155
166
|
if (graph.nodes.length > 0) {
|
|
156
167
|
lines.push('## Nodes');
|
|
@@ -169,18 +180,21 @@ export class FormatECodec {
|
|
|
169
180
|
lines.push(`### ${type}`);
|
|
170
181
|
for (const node of byType.get(type) ?? []) {
|
|
171
182
|
const descr = node.description ? `|${node.description}` : '';
|
|
172
|
-
const
|
|
173
|
-
lines.push(`+ ${node.uid}${descr}${
|
|
183
|
+
const nodeAttrs = omit ? stripNodeProvenance(node.attributes) : node.attributes;
|
|
184
|
+
lines.push(`+ ${node.uid}${descr}${this.serializeAttrs(nodeAttrs)}`);
|
|
174
185
|
// CR-GC-334: realRef/testRef & friends as @key {json} — the inline block above
|
|
175
186
|
// cannot carry them, and dropping them here is what made bindings vanish.
|
|
176
|
-
lines.push(...this.structuredAttrLines(
|
|
187
|
+
lines.push(...this.structuredAttrLines(nodeAttrs));
|
|
177
188
|
}
|
|
178
189
|
}
|
|
179
190
|
}
|
|
180
191
|
if (graph.edges.length > 0) {
|
|
181
192
|
lines.push('');
|
|
182
193
|
lines.push('## Edges');
|
|
183
|
-
|
|
194
|
+
const edges = omit
|
|
195
|
+
? graph.edges.map(e => ({ ...e, attributes: stripEdgeProvenance(e.attributes) }))
|
|
196
|
+
: graph.edges;
|
|
197
|
+
lines.push(...this.serializeEdges(edges));
|
|
184
198
|
}
|
|
185
199
|
return lines.join('\n');
|
|
186
200
|
}
|
|
@@ -408,3 +422,27 @@ export class FormatECodec {
|
|
|
408
422
|
return desc?.arrows[0] ?? edgeType.toLowerCase();
|
|
409
423
|
}
|
|
410
424
|
}
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
// CR-GC-373: agent-view provenance filters (pure — never mutate the input graph)
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
const PROVENANCE_KEYS = new Set(['created_at', 'updated_at']);
|
|
429
|
+
/** Drop created_at/updated_at; inside testRefs entries additionally ranAt. */
|
|
430
|
+
function stripNodeProvenance(attrs) {
|
|
431
|
+
const out = {};
|
|
432
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
433
|
+
if (PROVENANCE_KEYS.has(k))
|
|
434
|
+
continue;
|
|
435
|
+
if (k === 'testRefs' && Array.isArray(v)) {
|
|
436
|
+
out[k] = v.map(entry => entry != null && typeof entry === 'object' && !Array.isArray(entry)
|
|
437
|
+
? Object.fromEntries(Object.entries(entry).filter(([ek]) => ek !== 'ranAt'))
|
|
438
|
+
: entry);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
out[k] = v;
|
|
442
|
+
}
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
/** Drop created_at/updated_at and the default weight 1 (a non-default weight stays). */
|
|
446
|
+
function stripEdgeProvenance(attrs) {
|
|
447
|
+
return Object.fromEntries(Object.entries(attrs).filter(([k, v]) => !PROVENANCE_KEYS.has(k) && !(k === 'weight' && Number(v) === 1)));
|
|
448
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* impactSlice — THE directed impact traversal (CR-GC-365, Weg b).
|
|
3
|
+
*
|
|
4
|
+
* One definition of "blast radius", two readers: graphcode's `graph_impact`
|
|
5
|
+
* (MCP surface, agent) and graph-view-edit's ImpactMap (viewer). Before this,
|
|
6
|
+
* the MCP side ran a directed incoming Kuzu query while the viewer ran its own
|
|
7
|
+
* undirected frontier-BFS in the browser — what the human saw was a superset
|
|
8
|
+
* in direction and a subset in depth of what the agent got, computed from a
|
|
9
|
+
* different source.
|
|
10
|
+
*
|
|
11
|
+
* Semantics (node-equal to the former Kuzu query `(m)-[*1..depth]->(root)`,
|
|
12
|
+
* direction 'in' — conformance-tested against KuzuAdapter.getSubgraph):
|
|
13
|
+
*
|
|
14
|
+
* - Traversal is DIRECTED INCOMING: dependents, i.e. nodes with a directed
|
|
15
|
+
* path INTO a seed of length <= depth (who breaks if the seed changes).
|
|
16
|
+
* `distance` is the shortest such path length (0 = seed).
|
|
17
|
+
*
|
|
18
|
+
* - Roles per SPIKE-GC-minimal-whitebox §8 (`seed | whitebox | blackbox`):
|
|
19
|
+
* seed distance 0 — the changeset,
|
|
20
|
+
* whitebox distance 1..depth — full node (description, attributes),
|
|
21
|
+
* blackbox distance depth+1 — the frontier ONE step beyond the request:
|
|
22
|
+
* identity only (uid/type/name), description and attributes
|
|
23
|
+
* stripped. The cut is materialized in the artifact, not merely
|
|
24
|
+
* meant in a renderer; the ring is also the honest
|
|
25
|
+
* "+"-affordance: a bigger slice (same function, depth+1) opens
|
|
26
|
+
* exactly these nodes.
|
|
27
|
+
*
|
|
28
|
+
* - Edges only when BOTH ends lie in the slice (§8 — no edges into nothing).
|
|
29
|
+
*
|
|
30
|
+
* Pure function over a loaded Graph — no store handle, browser-safe (exported
|
|
31
|
+
* from `./browser.js` too). Seeds not present in the graph are dropped; no
|
|
32
|
+
* present seed yields an empty slice, never a full dump.
|
|
33
|
+
*
|
|
34
|
+
* @author andreas@siglochconsulting
|
|
35
|
+
*/
|
|
36
|
+
import type { Graph, GraphEdge, GraphNode } from './types.js';
|
|
37
|
+
export type ImpactRole = 'seed' | 'whitebox' | 'blackbox';
|
|
38
|
+
/** A slice node: the graph node plus its role and BFS distance from the changeset. */
|
|
39
|
+
export type ImpactSliceNode = GraphNode & {
|
|
40
|
+
role: ImpactRole;
|
|
41
|
+
distance: number;
|
|
42
|
+
};
|
|
43
|
+
export interface ImpactSlice {
|
|
44
|
+
/** The seeds actually present in the graph (order of first mention, deduped). */
|
|
45
|
+
seeds: string[];
|
|
46
|
+
/** The requested traversal depth (whitebox horizon; blackbox ring sits at depth+1). */
|
|
47
|
+
depth: number;
|
|
48
|
+
/** Slice nodes in the graph's node order — blackbox nodes carry identity only. */
|
|
49
|
+
nodes: ImpactSliceNode[];
|
|
50
|
+
/** Induced edges: kept iff both endpoints are in the slice. */
|
|
51
|
+
edges: GraphEdge[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Compute the impact slice of `seedIds` over a loaded graph: directed incoming
|
|
55
|
+
* BFS to `depth` (whitebox) plus the materialized blackbox ring at depth+1.
|
|
56
|
+
*/
|
|
57
|
+
export declare function impactSlice(graph: Graph, seedIds: readonly string[], depth: number): ImpactSlice;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the impact slice of `seedIds` over a loaded graph: directed incoming
|
|
3
|
+
* BFS to `depth` (whitebox) plus the materialized blackbox ring at depth+1.
|
|
4
|
+
*/
|
|
5
|
+
export function impactSlice(graph, seedIds, depth) {
|
|
6
|
+
const present = new Set(graph.nodes.map((n) => n.uid));
|
|
7
|
+
const seeds = [...new Set(seedIds)].filter((id) => present.has(id));
|
|
8
|
+
// Reverse adjacency: an edge source depends on its target, so traversal
|
|
9
|
+
// walks target -> source (incoming). Dangling edge ends are ignored.
|
|
10
|
+
const dependents = new Map();
|
|
11
|
+
for (const e of graph.edges) {
|
|
12
|
+
if (!present.has(e.sourceId) || !present.has(e.targetId))
|
|
13
|
+
continue;
|
|
14
|
+
const list = dependents.get(e.targetId);
|
|
15
|
+
if (list)
|
|
16
|
+
list.push(e.sourceId);
|
|
17
|
+
else
|
|
18
|
+
dependents.set(e.targetId, [e.sourceId]);
|
|
19
|
+
}
|
|
20
|
+
// BFS out to depth+1: shortest incoming-path distance per reached node.
|
|
21
|
+
const distance = new Map(seeds.map((s) => [s, 0]));
|
|
22
|
+
let frontier = seeds;
|
|
23
|
+
for (let d = 1; d <= depth + 1 && frontier.length > 0; d++) {
|
|
24
|
+
const next = [];
|
|
25
|
+
for (const uid of frontier) {
|
|
26
|
+
for (const dep of dependents.get(uid) ?? []) {
|
|
27
|
+
if (!distance.has(dep)) {
|
|
28
|
+
distance.set(dep, d);
|
|
29
|
+
next.push(dep);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
frontier = next;
|
|
34
|
+
}
|
|
35
|
+
const roleOf = (d) => (d === 0 ? 'seed' : d <= depth ? 'whitebox' : 'blackbox');
|
|
36
|
+
const nodes = [];
|
|
37
|
+
for (const n of graph.nodes) {
|
|
38
|
+
const d = distance.get(n.uid);
|
|
39
|
+
if (d === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
const role = roleOf(d);
|
|
42
|
+
nodes.push(role === 'blackbox'
|
|
43
|
+
? { ...n, description: undefined, attributes: {}, role, distance: d }
|
|
44
|
+
: { ...n, role, distance: d });
|
|
45
|
+
}
|
|
46
|
+
const inSlice = new Set(nodes.map((n) => n.uid));
|
|
47
|
+
const edges = graph.edges.filter((e) => inSlice.has(e.sourceId) && inSlice.has(e.targetId));
|
|
48
|
+
return { seeds, depth, nodes, edges };
|
|
49
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,5 +25,7 @@ export type { GraphApiConfig } from './factory.js';
|
|
|
25
25
|
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
26
26
|
export { findRoot } from './find-root.js';
|
|
27
27
|
export type { RootQueryGraph } from './find-root.js';
|
|
28
|
+
export { impactSlice } from './impact-slice.js';
|
|
29
|
+
export type { ImpactSlice, ImpactSliceNode, ImpactRole } from './impact-slice.js';
|
|
28
30
|
export { applyEdgeOps, updateEdge, mergeNodes } from './edge-ops.js';
|
|
29
31
|
export type { EdgeIdentity, UpdateEdgeSet, UpdateEdgeResult, MergeNodesResult, EdgeOp, } from './edge-ops.js';
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,9 @@ export { createGraphApi } from './factory.js';
|
|
|
22
22
|
export { SE_DESCRIPTOR, createSeDescriptor, projectToOntologyGraph, fromOntologyGraph } from './se-descriptor.js';
|
|
23
23
|
// Root-Suche — strukturelle Wurzel (SYS ohne eingehende compose), statt UID-Hardcode
|
|
24
24
|
export { findRoot } from './find-root.js';
|
|
25
|
+
// The shared directed impact traversal (CR-GC-365 b) — one definition for
|
|
26
|
+
// graphcode's graph_impact and graph-view-edit's ImpactMap.
|
|
27
|
+
export { impactSlice } from './impact-slice.js';
|
|
25
28
|
// Edge ops — update-edge (flip/retype) + merge-nodes, shared by GraphService.mutate()
|
|
26
29
|
// and the graphcode Apply-Gate (CR-198)
|
|
27
30
|
export { applyEdgeOps, updateEdge, mergeNodes } from './edge-ops.js';
|
package/dist/rule-engine.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pluggable rule engine for graph validation.
|
|
3
3
|
*/
|
|
4
|
+
import type { MetricPolicy } from '@sigloch/contracts/se';
|
|
4
5
|
import type { Graph } from './types.js';
|
|
5
6
|
export interface RuleViolation {
|
|
6
7
|
ruleId: string;
|
|
@@ -36,17 +37,31 @@ export interface Rule {
|
|
|
36
37
|
* everywhere it is displayed.
|
|
37
38
|
*/
|
|
38
39
|
gating?: boolean;
|
|
39
|
-
|
|
40
|
+
/**
|
|
41
|
+
* `policy` (CR-GC-402) overrides the threshold set this rule was BOUND with at
|
|
42
|
+
* descriptor construction, for this call only. Omitted = judge with the bound one,
|
|
43
|
+
* the pre-existing behaviour; a rule with no threshold ignores the argument.
|
|
44
|
+
*/
|
|
45
|
+
evaluate: (graph: Graph, policy?: MetricPolicy) => RuleViolation[];
|
|
40
46
|
}
|
|
41
47
|
export interface RuleEngine {
|
|
42
48
|
readonly version: string;
|
|
43
49
|
register(rules: Rule[]): void;
|
|
44
|
-
evaluate(graph: Graph): RuleViolation[];
|
|
50
|
+
evaluate(graph: Graph, policy?: MetricPolicy): RuleViolation[];
|
|
45
51
|
}
|
|
46
52
|
export declare class DefaultRuleEngine implements RuleEngine {
|
|
47
53
|
readonly version: string;
|
|
48
54
|
private rules;
|
|
49
55
|
constructor(version?: string);
|
|
50
56
|
register(rules: Rule[]): void;
|
|
51
|
-
|
|
57
|
+
/**
|
|
58
|
+
* CR-GC-402: `policy` is passed THROUGH to every rule instead of each rule judging
|
|
59
|
+
* with whatever policy the descriptor was built with. A consumer that registers the
|
|
60
|
+
* published `SE_DESCRIPTOR.rules` (which are bound to `DEFAULT_METRIC_POLICY`) but
|
|
61
|
+
* holds its own `graphcode.config.jsonc` could otherwise not judge with its own
|
|
62
|
+
* thresholds — the gve dashboard reported MT-01 on a repo whose config had switched
|
|
63
|
+
* it off, and disagreed with the host over the same graph. Omitting the argument
|
|
64
|
+
* keeps the bound policy, so no existing caller changes behaviour.
|
|
65
|
+
*/
|
|
66
|
+
evaluate(graph: Graph, policy?: MetricPolicy): RuleViolation[];
|
|
52
67
|
}
|
package/dist/rule-engine.js
CHANGED
|
@@ -7,14 +7,23 @@ export class DefaultRuleEngine {
|
|
|
7
7
|
register(rules) {
|
|
8
8
|
this.rules.push(...rules);
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* CR-GC-402: `policy` is passed THROUGH to every rule instead of each rule judging
|
|
12
|
+
* with whatever policy the descriptor was built with. A consumer that registers the
|
|
13
|
+
* published `SE_DESCRIPTOR.rules` (which are bound to `DEFAULT_METRIC_POLICY`) but
|
|
14
|
+
* holds its own `graphcode.config.jsonc` could otherwise not judge with its own
|
|
15
|
+
* thresholds — the gve dashboard reported MT-01 on a repo whose config had switched
|
|
16
|
+
* it off, and disagreed with the host over the same graph. Omitting the argument
|
|
17
|
+
* keeps the bound policy, so no existing caller changes behaviour.
|
|
18
|
+
*/
|
|
19
|
+
evaluate(graph, policy) {
|
|
11
20
|
const violations = [];
|
|
12
21
|
for (const rule of this.rules) {
|
|
13
22
|
// CR-GC-312: stamp `gating` from the rule so a consumer's gate can filter
|
|
14
23
|
// without a second lookup into the catalog. Only stamped when the rule opts
|
|
15
24
|
// OUT — an absent field means gating, the pre-existing behaviour.
|
|
16
25
|
const stamp = rule.gating === false ? { gating: false } : undefined;
|
|
17
|
-
for (const v of rule.evaluate(graph))
|
|
26
|
+
for (const v of rule.evaluate(graph, policy))
|
|
18
27
|
violations.push(stamp ? { ...v, ...stamp } : v);
|
|
19
28
|
}
|
|
20
29
|
return violations;
|
package/dist/se-descriptor.d.ts
CHANGED
|
@@ -14,7 +14,14 @@ import type { Graph, OntologyDescriptor } from './types.js';
|
|
|
14
14
|
/**
|
|
15
15
|
* Project an ontology-agnostic Graph (nodes/edges) onto the SE OntologyGraph
|
|
16
16
|
* (elements/traces) that contracts/se rules evaluate against. Type-specific
|
|
17
|
-
* attributes (
|
|
17
|
+
* attributes (method/kinds/status) are lifted out of `attributes`.
|
|
18
|
+
*
|
|
19
|
+
* CR-SM-294 hat `asil` aus der Ontologie entfernt (0 von 305 MOD trugen es, R-03 war sein
|
|
20
|
+
* einziger Leser). Die Spalte stand hier weiter und hob einen Schluessel heraus, den keine
|
|
21
|
+
* Regel mehr liest — gefunden erst vom Release-Zug, weil `npm test` gegen ein `dist/` von
|
|
22
|
+
* VOR der Streichung lief und nur ein frisches `tsc` darueber bricht. Ein Graph, der das
|
|
23
|
+
* Feld noch traegt, behaelt es im freien `attributes`-Sack; es ist nur keine typisierte
|
|
24
|
+
* Spalte mehr.
|
|
18
25
|
*/
|
|
19
26
|
export declare function projectToOntologyGraph(graph: Graph): OntologyGraph;
|
|
20
27
|
/**
|
package/dist/se-descriptor.js
CHANGED
|
@@ -13,7 +13,14 @@ import { ElementType, TraceType, TRACE_PATTERNS, V3_RULES, MT_RULES, UC_RULES, F
|
|
|
13
13
|
/**
|
|
14
14
|
* Project an ontology-agnostic Graph (nodes/edges) onto the SE OntologyGraph
|
|
15
15
|
* (elements/traces) that contracts/se rules evaluate against. Type-specific
|
|
16
|
-
* attributes (
|
|
16
|
+
* attributes (method/kinds/status) are lifted out of `attributes`.
|
|
17
|
+
*
|
|
18
|
+
* CR-SM-294 hat `asil` aus der Ontologie entfernt (0 von 305 MOD trugen es, R-03 war sein
|
|
19
|
+
* einziger Leser). Die Spalte stand hier weiter und hob einen Schluessel heraus, den keine
|
|
20
|
+
* Regel mehr liest — gefunden erst vom Release-Zug, weil `npm test` gegen ein `dist/` von
|
|
21
|
+
* VOR der Streichung lief und nur ein frisches `tsc` darueber bricht. Ein Graph, der das
|
|
22
|
+
* Feld noch traegt, behaelt es im freien `attributes`-Sack; es ist nur keine typisierte
|
|
23
|
+
* Spalte mehr.
|
|
17
24
|
*/
|
|
18
25
|
export function projectToOntologyGraph(graph) {
|
|
19
26
|
const elements = graph.nodes.map((n) => ({
|
|
@@ -24,7 +31,6 @@ export function projectToOntologyGraph(graph) {
|
|
|
24
31
|
status: n.attributes?.status ?? 'draft',
|
|
25
32
|
created_at: n.createdAt ?? '',
|
|
26
33
|
updated_at: n.updatedAt,
|
|
27
|
-
asil: n.attributes?.asil,
|
|
28
34
|
method: n.attributes?.method,
|
|
29
35
|
kinds: n.attributes?.kinds,
|
|
30
36
|
attributes: n.attributes,
|
|
@@ -140,14 +146,17 @@ const SE_RULE_DEFS = [
|
|
|
140
146
|
...VIEW_RULES,
|
|
141
147
|
...AF_RULES,
|
|
142
148
|
];
|
|
143
|
-
function adapt(def, evaluate) {
|
|
144
|
-
// (evaluate is already policy-bound by the caller — see seRules below)
|
|
149
|
+
function adapt(def, evaluate, boundPolicy) {
|
|
145
150
|
return {
|
|
146
151
|
id: def.id,
|
|
147
152
|
name: def.name,
|
|
148
153
|
severity: def.severity,
|
|
149
154
|
gating: GATING_PREFIXES.some((p) => def.id.startsWith(p)),
|
|
150
|
-
|
|
155
|
+
// CR-GC-402: the CALLER's policy wins for this evaluation, the descriptor's bound
|
|
156
|
+
// one is the fallback. Without this the threshold was frozen at construction, so a
|
|
157
|
+
// consumer of the published `SE_DESCRIPTOR` judged with `DEFAULT_METRIC_POLICY` no
|
|
158
|
+
// matter what its own config said (the MT-01 split between host and dashboard).
|
|
159
|
+
evaluate: (graph, policy) => evaluate(projectToOntologyGraph(graph), policy ?? boundPolicy).map((v) => ({
|
|
151
160
|
ruleId: v.rule_id,
|
|
152
161
|
ruleName: def.name,
|
|
153
162
|
severity: v.severity,
|
|
@@ -175,9 +184,14 @@ function adapt(def, evaluate) {
|
|
|
175
184
|
*
|
|
176
185
|
* A `null` threshold means measure, don't judge: the rule stays registered and simply
|
|
177
186
|
* reports nothing.
|
|
187
|
+
*
|
|
188
|
+
* CR-GC-402: `policy` is the BOUND value — the one that judges when a caller names none.
|
|
189
|
+
* `DefaultRuleEngine.evaluate(graph, policy)` may override it per call, which is what lets
|
|
190
|
+
* a consumer of the published descriptor judge with its own `graphcode.config.jsonc`
|
|
191
|
+
* instead of silently with the default.
|
|
178
192
|
*/
|
|
179
193
|
function seRules(policy) {
|
|
180
|
-
return [...SE_RULE_DEFS, ...MT_RULES].map((def) => adapt(def, (g) => def.evaluate(g, policy))
|
|
194
|
+
return [...SE_RULE_DEFS, ...MT_RULES].map((def) => adapt(def, (g, p) => def.evaluate(g, p), policy));
|
|
181
195
|
}
|
|
182
196
|
// `label` is the node-table identifier (kuzu table name) — must be a valid
|
|
183
197
|
// identifier, so it's the ElementType key (SYS, UC, …), not the human
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigloch/graph-api-core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@sigloch/contracts": ">=
|
|
46
|
+
"@sigloch/contracts": ">=10 <11",
|
|
47
47
|
"kuzu-wasm": "^0.11.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@sigloch/contracts": ">=
|
|
55
|
+
"@sigloch/contracts": ">=10 <11",
|
|
56
56
|
"kuzu-wasm": "^0.11.3"
|
|
57
57
|
}
|
|
58
58
|
}
|