@sigloch/graph-api-core 0.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/LICENSE +21 -0
- package/dist/audit.d.ts +103 -0
- package/dist/audit.js +145 -0
- package/dist/browser.d.ts +21 -0
- package/dist/browser.js +17 -0
- package/dist/edge-ops.d.ts +42 -0
- package/dist/edge-ops.js +66 -0
- package/dist/factory.d.ts +26 -0
- package/dist/factory.js +23 -0
- package/dist/find-root.d.ts +34 -0
- package/dist/find-root.js +10 -0
- package/dist/format-e-codec.d.ts +23 -0
- package/dist/format-e-codec.js +296 -0
- package/dist/graph-service.d.ts +37 -0
- package/dist/graph-service.js +597 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +27 -0
- package/dist/memory-adapter.d.ts +28 -0
- package/dist/memory-adapter.js +72 -0
- package/dist/rule-engine.d.ts +33 -0
- package/dist/rule-engine.js +17 -0
- package/dist/schemas.d.ts +97 -0
- package/dist/schemas.js +58 -0
- package/dist/se-descriptor.d.ts +24 -0
- package/dist/se-descriptor.js +84 -0
- package/dist/storage-adapter.d.ts +32 -0
- package/dist/storage-adapter.js +1 -0
- package/dist/test-fixtures.d.ts +42 -0
- package/dist/test-fixtures.js +151 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/storage-contract-tests.d.ts +2 -0
- package/dist/testing/storage-contract-tests.js +113 -0
- package/dist/transport-adapter.d.ts +13 -0
- package/dist/transport-adapter.js +1 -0
- package/dist/types.d.ts +147 -0
- package/dist/types.js +39 -0
- package/package.json +45 -0
- package/test-fixtures/cr-007-features.format-e.md +22 -0
- package/test-fixtures/empty.format-e.md +6 -0
- package/test-fixtures/rasentraktor.format-e.md +46 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export class MemoryAdapter {
|
|
2
|
+
name = 'memory';
|
|
3
|
+
nodes = new Map();
|
|
4
|
+
edges = [];
|
|
5
|
+
async initialize() { }
|
|
6
|
+
async shutdown() {
|
|
7
|
+
this.nodes.clear();
|
|
8
|
+
this.edges = [];
|
|
9
|
+
}
|
|
10
|
+
async loadGraph(_scope) {
|
|
11
|
+
return {
|
|
12
|
+
nodes: Array.from(this.nodes.values()),
|
|
13
|
+
edges: [...this.edges],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async saveNodes(nodes) {
|
|
17
|
+
for (const node of nodes) {
|
|
18
|
+
this.nodes.set(node.uid, node);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async deleteNodes(uids) {
|
|
22
|
+
const uidSet = new Set(uids);
|
|
23
|
+
for (const uid of uids) {
|
|
24
|
+
this.nodes.delete(uid);
|
|
25
|
+
}
|
|
26
|
+
this.edges = this.edges.filter(e => !uidSet.has(e.sourceId) && !uidSet.has(e.targetId));
|
|
27
|
+
}
|
|
28
|
+
async saveEdges(edges) {
|
|
29
|
+
for (const edge of edges) {
|
|
30
|
+
// Upsert: remove existing with same key, then add
|
|
31
|
+
this.edges = this.edges.filter(e => !(e.sourceId === edge.sourceId && e.targetId === edge.targetId && e.edgeType === edge.edgeType));
|
|
32
|
+
this.edges.push(edge);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async deleteEdges(keys) {
|
|
36
|
+
const keySet = new Set(keys.map(k => `${k.sourceId}:${k.targetId}:${k.edgeType}`));
|
|
37
|
+
this.edges = this.edges.filter(e => !keySet.has(`${e.sourceId}:${e.targetId}:${e.edgeType}`));
|
|
38
|
+
}
|
|
39
|
+
async getNode(uid) {
|
|
40
|
+
return this.nodes.get(uid) ?? null;
|
|
41
|
+
}
|
|
42
|
+
async getSubgraph(root, depth) {
|
|
43
|
+
const visited = new Set();
|
|
44
|
+
const resultNodes = [];
|
|
45
|
+
const resultEdges = [];
|
|
46
|
+
const queue = [{ uid: root, d: 0 }];
|
|
47
|
+
while (queue.length > 0) {
|
|
48
|
+
const { uid, d } = queue.shift();
|
|
49
|
+
if (visited.has(uid))
|
|
50
|
+
continue;
|
|
51
|
+
visited.add(uid);
|
|
52
|
+
const node = this.nodes.get(uid);
|
|
53
|
+
if (node)
|
|
54
|
+
resultNodes.push(node);
|
|
55
|
+
if (d < depth) {
|
|
56
|
+
for (const edge of this.edges) {
|
|
57
|
+
if (edge.sourceId === uid && !visited.has(edge.targetId)) {
|
|
58
|
+
resultEdges.push(edge);
|
|
59
|
+
queue.push({ uid: edge.targetId, d: d + 1 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { nodes: resultNodes, edges: resultEdges };
|
|
65
|
+
}
|
|
66
|
+
async stats() {
|
|
67
|
+
return { nodeCount: this.nodes.size, edgeCount: this.edges.length };
|
|
68
|
+
}
|
|
69
|
+
async isHealthy() {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable rule engine for graph validation.
|
|
3
|
+
*/
|
|
4
|
+
import type { Graph } from './types.js';
|
|
5
|
+
export interface RuleViolation {
|
|
6
|
+
ruleId: string;
|
|
7
|
+
ruleName: string;
|
|
8
|
+
severity: 'error' | 'warning' | 'info';
|
|
9
|
+
message: string;
|
|
10
|
+
elementId?: string;
|
|
11
|
+
/** Carried from the contracts rule (CR-GC-203 item 1): how to fix this violation. */
|
|
12
|
+
fixHint?: string;
|
|
13
|
+
/** Carried from the contracts rule: candidate_targets / existing_traces context for fix automation. */
|
|
14
|
+
context?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface Rule {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
severity: 'error' | 'warning' | 'info';
|
|
20
|
+
evaluate: (graph: Graph) => RuleViolation[];
|
|
21
|
+
}
|
|
22
|
+
export interface RuleEngine {
|
|
23
|
+
readonly version: string;
|
|
24
|
+
register(rules: Rule[]): void;
|
|
25
|
+
evaluate(graph: Graph): RuleViolation[];
|
|
26
|
+
}
|
|
27
|
+
export declare class DefaultRuleEngine implements RuleEngine {
|
|
28
|
+
readonly version: string;
|
|
29
|
+
private rules;
|
|
30
|
+
constructor(version?: string);
|
|
31
|
+
register(rules: Rule[]): void;
|
|
32
|
+
evaluate(graph: Graph): RuleViolation[];
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class DefaultRuleEngine {
|
|
2
|
+
version;
|
|
3
|
+
rules = [];
|
|
4
|
+
constructor(version = '1.0.0') {
|
|
5
|
+
this.version = version;
|
|
6
|
+
}
|
|
7
|
+
register(rules) {
|
|
8
|
+
this.rules.push(...rules);
|
|
9
|
+
}
|
|
10
|
+
evaluate(graph) {
|
|
11
|
+
const violations = [];
|
|
12
|
+
for (const rule of this.rules) {
|
|
13
|
+
violations.push(...rule.evaluate(graph));
|
|
14
|
+
}
|
|
15
|
+
return violations;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for graph-api-core API types.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod/v4';
|
|
5
|
+
export declare const GraphNodeSchema: z.ZodObject<{
|
|
6
|
+
uid: z.ZodString;
|
|
7
|
+
type: z.ZodString;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
description: z.ZodOptional<z.ZodString>;
|
|
10
|
+
attributes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
11
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
12
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare const GraphEdgeSchema: z.ZodObject<{
|
|
15
|
+
sourceId: z.ZodString;
|
|
16
|
+
targetId: z.ZodString;
|
|
17
|
+
edgeType: z.ZodString;
|
|
18
|
+
attributes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
export declare const GraphSchema: z.ZodObject<{
|
|
21
|
+
nodes: z.ZodArray<z.ZodObject<{
|
|
22
|
+
uid: z.ZodString;
|
|
23
|
+
type: z.ZodString;
|
|
24
|
+
name: z.ZodString;
|
|
25
|
+
description: z.ZodOptional<z.ZodString>;
|
|
26
|
+
attributes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
27
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
28
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, z.core.$strip>>;
|
|
30
|
+
edges: z.ZodArray<z.ZodObject<{
|
|
31
|
+
sourceId: z.ZodString;
|
|
32
|
+
targetId: z.ZodString;
|
|
33
|
+
edgeType: z.ZodString;
|
|
34
|
+
attributes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
35
|
+
}, z.core.$strip>>;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
export declare const GraphScopeSchema: z.ZodObject<{
|
|
38
|
+
workspaceId: z.ZodString;
|
|
39
|
+
systemId: z.ZodString;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
export declare const GraphQuerySchema: z.ZodObject<{
|
|
42
|
+
type: z.ZodEnum<{
|
|
43
|
+
elements: "elements";
|
|
44
|
+
edges: "edges";
|
|
45
|
+
subgraph: "subgraph";
|
|
46
|
+
view: "view";
|
|
47
|
+
}>;
|
|
48
|
+
filter: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
49
|
+
view: z.ZodOptional<z.ZodString>;
|
|
50
|
+
root: z.ZodOptional<z.ZodString>;
|
|
51
|
+
depth: z.ZodDefault<z.ZodNumber>;
|
|
52
|
+
format: z.ZodDefault<z.ZodEnum<{
|
|
53
|
+
json: "json";
|
|
54
|
+
"format-e": "format-e";
|
|
55
|
+
}>>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export declare const RuleViolationSchema: z.ZodObject<{
|
|
58
|
+
ruleId: z.ZodString;
|
|
59
|
+
ruleName: z.ZodString;
|
|
60
|
+
severity: z.ZodEnum<{
|
|
61
|
+
error: "error";
|
|
62
|
+
warning: "warning";
|
|
63
|
+
info: "info";
|
|
64
|
+
}>;
|
|
65
|
+
message: z.ZodString;
|
|
66
|
+
elementId: z.ZodOptional<z.ZodString>;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
export declare const MutationResultSchema: z.ZodObject<{
|
|
69
|
+
applied: z.ZodNumber;
|
|
70
|
+
rejected: z.ZodNumber;
|
|
71
|
+
violations: z.ZodArray<z.ZodObject<{
|
|
72
|
+
ruleId: z.ZodString;
|
|
73
|
+
ruleName: z.ZodString;
|
|
74
|
+
severity: z.ZodEnum<{
|
|
75
|
+
error: "error";
|
|
76
|
+
warning: "warning";
|
|
77
|
+
info: "info";
|
|
78
|
+
}>;
|
|
79
|
+
message: z.ZodString;
|
|
80
|
+
elementId: z.ZodOptional<z.ZodString>;
|
|
81
|
+
}, z.core.$strip>>;
|
|
82
|
+
version: z.ZodNumber;
|
|
83
|
+
auditId: z.ZodString;
|
|
84
|
+
}, z.core.$strip>;
|
|
85
|
+
export declare const HealthStatusSchema: z.ZodObject<{
|
|
86
|
+
status: z.ZodEnum<{
|
|
87
|
+
ok: "ok";
|
|
88
|
+
degraded: "degraded";
|
|
89
|
+
down: "down";
|
|
90
|
+
}>;
|
|
91
|
+
storage: z.ZodString;
|
|
92
|
+
ontologyName: z.ZodString;
|
|
93
|
+
ontologyVersion: z.ZodString;
|
|
94
|
+
elementCount: z.ZodNumber;
|
|
95
|
+
edgeCount: z.ZodNumber;
|
|
96
|
+
violationCount: z.ZodNumber;
|
|
97
|
+
}, z.core.$strip>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for graph-api-core API types.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod/v4';
|
|
5
|
+
export const GraphNodeSchema = z.object({
|
|
6
|
+
uid: z.string(),
|
|
7
|
+
type: z.string(),
|
|
8
|
+
name: z.string(),
|
|
9
|
+
description: z.string().optional(),
|
|
10
|
+
attributes: z.record(z.string(), z.unknown()).default({}),
|
|
11
|
+
createdAt: z.string().optional(),
|
|
12
|
+
updatedAt: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
export const GraphEdgeSchema = z.object({
|
|
15
|
+
sourceId: z.string(),
|
|
16
|
+
targetId: z.string(),
|
|
17
|
+
edgeType: z.string(),
|
|
18
|
+
attributes: z.record(z.string(), z.unknown()).default({}),
|
|
19
|
+
});
|
|
20
|
+
export const GraphSchema = z.object({
|
|
21
|
+
nodes: z.array(GraphNodeSchema),
|
|
22
|
+
edges: z.array(GraphEdgeSchema),
|
|
23
|
+
});
|
|
24
|
+
export const GraphScopeSchema = z.object({
|
|
25
|
+
workspaceId: z.string(),
|
|
26
|
+
systemId: z.string(),
|
|
27
|
+
});
|
|
28
|
+
export const GraphQuerySchema = z.object({
|
|
29
|
+
type: z.enum(['elements', 'edges', 'subgraph', 'view']),
|
|
30
|
+
filter: z.record(z.string(), z.unknown()).optional(),
|
|
31
|
+
view: z.string().optional(),
|
|
32
|
+
root: z.string().optional(),
|
|
33
|
+
depth: z.number().int().min(1).max(10).default(3),
|
|
34
|
+
format: z.enum(['json', 'format-e']).default('json'),
|
|
35
|
+
});
|
|
36
|
+
export const RuleViolationSchema = z.object({
|
|
37
|
+
ruleId: z.string(),
|
|
38
|
+
ruleName: z.string(),
|
|
39
|
+
severity: z.enum(['error', 'warning', 'info']),
|
|
40
|
+
message: z.string(),
|
|
41
|
+
elementId: z.string().optional(),
|
|
42
|
+
});
|
|
43
|
+
export const MutationResultSchema = z.object({
|
|
44
|
+
applied: z.number(),
|
|
45
|
+
rejected: z.number(),
|
|
46
|
+
violations: z.array(RuleViolationSchema),
|
|
47
|
+
version: z.number(),
|
|
48
|
+
auditId: z.string(),
|
|
49
|
+
});
|
|
50
|
+
export const HealthStatusSchema = z.object({
|
|
51
|
+
status: z.enum(['ok', 'degraded', 'down']),
|
|
52
|
+
storage: z.string(),
|
|
53
|
+
ontologyName: z.string(),
|
|
54
|
+
ontologyVersion: z.string(),
|
|
55
|
+
elementCount: z.number(),
|
|
56
|
+
edgeCount: z.number(),
|
|
57
|
+
violationCount: z.number(),
|
|
58
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SE OntologyDescriptor — derived from @sigloch/contracts/se (SSOT). [CR-195a]
|
|
3
|
+
*
|
|
4
|
+
* graph-api-core is ontology-agnostic; this is the canonical SE instance so
|
|
5
|
+
* consumers (aimprove, moneyflow, graphengine) share one ElementType/TraceType/
|
|
6
|
+
* Rule wiring instead of each rebuilding it (Drift-Lock L1/L2).
|
|
7
|
+
*
|
|
8
|
+
* Two shape gaps vs contracts/se are bridged here (it's an adapter, not a copy):
|
|
9
|
+
* - Graph (nodes/edges) ⟷ OntologyGraph (elements/traces)
|
|
10
|
+
* - RuleViolation (ruleId/…) ⟷ contracts RuleViolation (rule_id/…)
|
|
11
|
+
*/
|
|
12
|
+
import { type OntologyGraph } from '@sigloch/contracts/se';
|
|
13
|
+
import type { Graph, OntologyDescriptor } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Project an ontology-agnostic Graph (nodes/edges) onto the SE OntologyGraph
|
|
16
|
+
* (elements/traces) that contracts/se rules evaluate against. Type-specific
|
|
17
|
+
* attributes (asil/method/kinds/status) are lifted out of `attributes`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function projectToOntologyGraph(graph: Graph): OntologyGraph;
|
|
20
|
+
/**
|
|
21
|
+
* Canonical SE OntologyDescriptor (ontology + 17 rules), version-pinned to
|
|
22
|
+
* contracts/se ONTOLOGY_VERSION. Plug into GraphService / FormatECodec.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SE_DESCRIPTOR: OntologyDescriptor;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SE OntologyDescriptor — derived from @sigloch/contracts/se (SSOT). [CR-195a]
|
|
3
|
+
*
|
|
4
|
+
* graph-api-core is ontology-agnostic; this is the canonical SE instance so
|
|
5
|
+
* consumers (aimprove, moneyflow, graphengine) share one ElementType/TraceType/
|
|
6
|
+
* Rule wiring instead of each rebuilding it (Drift-Lock L1/L2).
|
|
7
|
+
*
|
|
8
|
+
* Two shape gaps vs contracts/se are bridged here (it's an adapter, not a copy):
|
|
9
|
+
* - Graph (nodes/edges) ⟷ OntologyGraph (elements/traces)
|
|
10
|
+
* - RuleViolation (ruleId/…) ⟷ contracts RuleViolation (rule_id/…)
|
|
11
|
+
*/
|
|
12
|
+
import { ElementType, TraceType, TRACE_PATTERNS, V3_RULES, ONTOLOGY_VERSION, } from '@sigloch/contracts/se';
|
|
13
|
+
/**
|
|
14
|
+
* Project an ontology-agnostic Graph (nodes/edges) onto the SE OntologyGraph
|
|
15
|
+
* (elements/traces) that contracts/se rules evaluate against. Type-specific
|
|
16
|
+
* attributes (asil/method/kinds/status) are lifted out of `attributes`.
|
|
17
|
+
*/
|
|
18
|
+
export function projectToOntologyGraph(graph) {
|
|
19
|
+
const elements = graph.nodes.map((n) => ({
|
|
20
|
+
id: n.uid,
|
|
21
|
+
type: n.type,
|
|
22
|
+
name: n.name,
|
|
23
|
+
description: n.description ?? '',
|
|
24
|
+
status: n.attributes?.status ?? 'draft',
|
|
25
|
+
created_at: n.createdAt ?? '',
|
|
26
|
+
updated_at: n.updatedAt,
|
|
27
|
+
asil: n.attributes?.asil,
|
|
28
|
+
method: n.attributes?.method,
|
|
29
|
+
kinds: n.attributes?.kinds,
|
|
30
|
+
attributes: n.attributes,
|
|
31
|
+
}));
|
|
32
|
+
const traces = graph.edges.map((e) => ({
|
|
33
|
+
source: e.sourceId,
|
|
34
|
+
target: e.targetId,
|
|
35
|
+
type: e.edgeType,
|
|
36
|
+
category: e.attributes?.category,
|
|
37
|
+
label: e.attributes?.label,
|
|
38
|
+
weight: e.attributes?.weight ?? 1,
|
|
39
|
+
created_at: e.attributes?.created_at ?? '',
|
|
40
|
+
verified_at: e.attributes?.verified_at,
|
|
41
|
+
attributes: e.attributes,
|
|
42
|
+
}));
|
|
43
|
+
return { elements, traces };
|
|
44
|
+
}
|
|
45
|
+
/** The 17 contracts/se V3_RULES, adapted to graph-api-core's Rule shape. */
|
|
46
|
+
const SE_RULES = V3_RULES.map((def) => ({
|
|
47
|
+
id: def.id,
|
|
48
|
+
name: def.name,
|
|
49
|
+
severity: def.severity,
|
|
50
|
+
evaluate: (graph) => def.evaluate(projectToOntologyGraph(graph)).map((v) => ({
|
|
51
|
+
ruleId: v.rule_id,
|
|
52
|
+
ruleName: def.name,
|
|
53
|
+
severity: v.severity,
|
|
54
|
+
message: v.message,
|
|
55
|
+
elementId: v.element_id,
|
|
56
|
+
// CR-GC-203 item 1: carry the fix-context through instead of discarding it,
|
|
57
|
+
// so rules_get_violations hands the agent candidates + a hint, not just a message.
|
|
58
|
+
fixHint: v.fix_hint,
|
|
59
|
+
context: v.context,
|
|
60
|
+
})),
|
|
61
|
+
}));
|
|
62
|
+
// `label` is the node-table identifier (kuzu table name) — must be a valid
|
|
63
|
+
// identifier, so it's the ElementType key (SYS, UC, …), not the human
|
|
64
|
+
// ELEMENT_DESCRIPTIONS text which would break DDL.
|
|
65
|
+
const nodeTypes = Object.fromEntries(ElementType.options.map((t) => [t, { label: t, requiredAttrs: [] }]));
|
|
66
|
+
const edgeTypes = Object.fromEntries(TraceType.options.map((tt) => {
|
|
67
|
+
const pairs = TRACE_PATTERNS.filter((p) => p.type === tt).map((p) => [p.source, p.target]);
|
|
68
|
+
// Types without an explicit pattern (e.g. generic 'relation') stay open.
|
|
69
|
+
return [tt, { arrows: [tt], validPairs: pairs.length ? pairs : [['*', '*']] }];
|
|
70
|
+
}));
|
|
71
|
+
/**
|
|
72
|
+
* Canonical SE OntologyDescriptor (ontology + 17 rules), version-pinned to
|
|
73
|
+
* contracts/se ONTOLOGY_VERSION. Plug into GraphService / FormatECodec.
|
|
74
|
+
*/
|
|
75
|
+
export const SE_DESCRIPTOR = {
|
|
76
|
+
name: 'se',
|
|
77
|
+
version: ONTOLOGY_VERSION,
|
|
78
|
+
nodeTypes,
|
|
79
|
+
edgeTypes,
|
|
80
|
+
// CR-GC-247: TRACE_PATTERNS is the trace-legality SSOT; validPairs above is kept
|
|
81
|
+
// only for Format-E arrow/menu enumeration. isValidTrace validates against these.
|
|
82
|
+
patterns: TRACE_PATTERNS,
|
|
83
|
+
rules: SE_RULES,
|
|
84
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StorageAdapter interface — implemented by KuzuAdapter (@sigloch/graph-cypher-wasm), the one production store (Kuzu-only, 2yR-SSOT §Verriegelte Entscheidungen #3).
|
|
3
|
+
*/
|
|
4
|
+
import type { GraphNode, GraphEdge, Graph, GraphScope } from './types.js';
|
|
5
|
+
export interface StorageAdapter {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
initialize(): Promise<void>;
|
|
8
|
+
shutdown(): Promise<void>;
|
|
9
|
+
loadGraph(scope: GraphScope): Promise<Graph>;
|
|
10
|
+
saveNodes(nodes: GraphNode[]): Promise<void>;
|
|
11
|
+
deleteNodes(uids: string[]): Promise<void>;
|
|
12
|
+
saveEdges(edges: GraphEdge[]): Promise<void>;
|
|
13
|
+
deleteEdges(keys: Array<{
|
|
14
|
+
sourceId: string;
|
|
15
|
+
targetId: string;
|
|
16
|
+
edgeType: string;
|
|
17
|
+
}>): Promise<void>;
|
|
18
|
+
getNode(uid: string): Promise<GraphNode | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Direction-aware subgraph around `root`.
|
|
21
|
+
* - 'out' (default): root's dependencies — `(root)-[*1..depth]->(m)` (back-compat).
|
|
22
|
+
* - 'in' : root's dependents / blast-radius — `(m)-[*1..depth]->(root)`.
|
|
23
|
+
* - 'both' : union of in + out.
|
|
24
|
+
* Adapters that do not implement direction MUST behave as 'out'.
|
|
25
|
+
*/
|
|
26
|
+
getSubgraph(root: string, depth: number, direction?: 'out' | 'in' | 'both'): Promise<Graph>;
|
|
27
|
+
stats(): Promise<{
|
|
28
|
+
nodeCount: number;
|
|
29
|
+
edgeCount: number;
|
|
30
|
+
}>;
|
|
31
|
+
isHealthy(): Promise<boolean>;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format-E test fixtures shipped with @sigloch/graph-api-core.
|
|
3
|
+
*
|
|
4
|
+
* Purpose: every consumer package (graph-renderer, graph-cypher-wasm,
|
|
5
|
+
* ontoagent-app, aise, flowground, …) loads these same fixtures in its test
|
|
6
|
+
* setup. Round-trip equality across consumers fences off Format-E drift sigloch-wide.
|
|
7
|
+
*
|
|
8
|
+
* Import:
|
|
9
|
+
* import {
|
|
10
|
+
* TEST_ONTOLOGY,
|
|
11
|
+
* loadFixture,
|
|
12
|
+
* FIXTURES,
|
|
13
|
+
* } from '@sigloch/graph-api-core/test-fixtures';
|
|
14
|
+
*/
|
|
15
|
+
import type { OntologyDescriptor } from './types.js';
|
|
16
|
+
import { FormatECodec } from './format-e-codec.js';
|
|
17
|
+
export declare const TEST_ONTOLOGY: OntologyDescriptor;
|
|
18
|
+
export declare const FIXTURES: {
|
|
19
|
+
readonly empty: "empty.format-e.md";
|
|
20
|
+
readonly rasentraktor: "rasentraktor.format-e.md";
|
|
21
|
+
readonly cr007Features: "cr-007-features.format-e.md";
|
|
22
|
+
};
|
|
23
|
+
export type FixtureName = keyof typeof FIXTURES;
|
|
24
|
+
/**
|
|
25
|
+
* Read a fixture file from disk. Returns the raw markdown.
|
|
26
|
+
*
|
|
27
|
+
* For browser-only consumers, prefer `import.meta.glob` or a build-time
|
|
28
|
+
* inliner instead of this helper (Node fs is not available in the browser).
|
|
29
|
+
*/
|
|
30
|
+
export declare function readFixture(name: FixtureName): string;
|
|
31
|
+
/**
|
|
32
|
+
* Read a fixture, extract the ```format-e fence, and parse it via the
|
|
33
|
+
* shipped FormatECodec + TEST_ONTOLOGY.
|
|
34
|
+
*
|
|
35
|
+
* Throws if the parse produces validation errors.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadFixture(name: FixtureName, codec?: FormatECodec): import("./types.js").FormatEDiff;
|
|
38
|
+
/**
|
|
39
|
+
* Absolute path to the fixtures directory — useful for consumers using
|
|
40
|
+
* Vite's `?raw` import, esbuild's loader, or their own glob.
|
|
41
|
+
*/
|
|
42
|
+
export declare const FIXTURE_DIR_PATH: string;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format-E test fixtures shipped with @sigloch/graph-api-core.
|
|
3
|
+
*
|
|
4
|
+
* Purpose: every consumer package (graph-renderer, graph-cypher-wasm,
|
|
5
|
+
* ontoagent-app, aise, flowground, …) loads these same fixtures in its test
|
|
6
|
+
* setup. Round-trip equality across consumers fences off Format-E drift sigloch-wide.
|
|
7
|
+
*
|
|
8
|
+
* Import:
|
|
9
|
+
* import {
|
|
10
|
+
* TEST_ONTOLOGY,
|
|
11
|
+
* loadFixture,
|
|
12
|
+
* FIXTURES,
|
|
13
|
+
* } from '@sigloch/graph-api-core/test-fixtures';
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { FormatECodec } from './format-e-codec.js';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Minimal test ontology
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Mirrors the node/edge taxonomy used by the ontoagent workshop assistant
|
|
23
|
+
// (OU, R, PR, VAR, MT, SL, C, F, FR, CTRL, TC, TL …) — enough to parse the
|
|
24
|
+
// shipped fixtures without forcing consumers to define their own descriptor.
|
|
25
|
+
// Consumers with a richer ontology should compose this with their own.
|
|
26
|
+
export const TEST_ONTOLOGY = {
|
|
27
|
+
name: 'sigloch-test-ontology',
|
|
28
|
+
version: '0.1.0',
|
|
29
|
+
nodeTypes: {
|
|
30
|
+
OU: { label: 'OrgUnit', requiredAttrs: [] },
|
|
31
|
+
R: { label: 'Role', requiredAttrs: [] },
|
|
32
|
+
PR: { label: 'Product', requiredAttrs: [] },
|
|
33
|
+
VAR: { label: 'Variant', requiredAttrs: [] },
|
|
34
|
+
C: { label: 'Component', requiredAttrs: [] },
|
|
35
|
+
F: { label: 'Feature', requiredAttrs: [] },
|
|
36
|
+
FR: { label: 'FmeaRow', requiredAttrs: [] },
|
|
37
|
+
CTRL: { label: 'Control', requiredAttrs: [] },
|
|
38
|
+
TC: { label: 'TestCase', requiredAttrs: [] },
|
|
39
|
+
TL: { label: 'Tool', requiredAttrs: [] },
|
|
40
|
+
MT: { label: 'MilestoneType', requiredAttrs: [] },
|
|
41
|
+
SL: { label: 'StorageLocation', requiredAttrs: [] },
|
|
42
|
+
},
|
|
43
|
+
edgeTypes: {
|
|
44
|
+
compose: {
|
|
45
|
+
arrows: ['compose'],
|
|
46
|
+
validPairs: [
|
|
47
|
+
['PR', 'C'],
|
|
48
|
+
['C', 'C'],
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
has_variant: {
|
|
52
|
+
arrows: ['has_variant'],
|
|
53
|
+
validPairs: [
|
|
54
|
+
['PR', 'VAR'],
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
realized_by: {
|
|
58
|
+
arrows: ['realized_by'],
|
|
59
|
+
validPairs: [
|
|
60
|
+
['F', 'C'],
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
addressed_by: {
|
|
64
|
+
arrows: ['addressed_by'],
|
|
65
|
+
validPairs: [
|
|
66
|
+
['C', 'FR'],
|
|
67
|
+
['FR', 'CTRL'],
|
|
68
|
+
['C', 'C'],
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
verified_by: {
|
|
72
|
+
arrows: ['verified_by'],
|
|
73
|
+
validPairs: [
|
|
74
|
+
['CTRL', 'TC'],
|
|
75
|
+
['FR', 'TC'],
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
responsible_for: {
|
|
79
|
+
arrows: ['responsible_for'],
|
|
80
|
+
validPairs: [
|
|
81
|
+
['OU', 'C'],
|
|
82
|
+
['OU', 'TL'],
|
|
83
|
+
['R', 'C'],
|
|
84
|
+
['R', 'TL'],
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
within: {
|
|
88
|
+
arrows: ['within'],
|
|
89
|
+
validPairs: [
|
|
90
|
+
['R', 'OU'],
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
requires_document_of_type: {
|
|
94
|
+
arrows: ['requires_document_of_type'],
|
|
95
|
+
validPairs: [
|
|
96
|
+
['MT', 'FR'],
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
stored_in: {
|
|
100
|
+
arrows: ['stored_in'],
|
|
101
|
+
validPairs: [
|
|
102
|
+
['C', 'SL'],
|
|
103
|
+
['FR', 'SL'],
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Fixture loader
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Resolves to <package-root>/test-fixtures whether loaded from src/ (vitest) or dist/ (consumers).
|
|
112
|
+
const FIXTURE_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'test-fixtures');
|
|
113
|
+
export const FIXTURES = {
|
|
114
|
+
empty: 'empty.format-e.md',
|
|
115
|
+
rasentraktor: 'rasentraktor.format-e.md',
|
|
116
|
+
cr007Features: 'cr-007-features.format-e.md',
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Read a fixture file from disk. Returns the raw markdown.
|
|
120
|
+
*
|
|
121
|
+
* For browser-only consumers, prefer `import.meta.glob` or a build-time
|
|
122
|
+
* inliner instead of this helper (Node fs is not available in the browser).
|
|
123
|
+
*/
|
|
124
|
+
export function readFixture(name) {
|
|
125
|
+
return readFileSync(join(FIXTURE_DIR, FIXTURES[name]), 'utf8');
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Read a fixture, extract the ```format-e fence, and parse it via the
|
|
129
|
+
* shipped FormatECodec + TEST_ONTOLOGY.
|
|
130
|
+
*
|
|
131
|
+
* Throws if the parse produces validation errors.
|
|
132
|
+
*/
|
|
133
|
+
export function loadFixture(name, codec) {
|
|
134
|
+
const raw = readFixture(name);
|
|
135
|
+
const c = codec ?? new FormatECodec(TEST_ONTOLOGY);
|
|
136
|
+
const fence = c.extractFromLlm(raw);
|
|
137
|
+
if (fence === null && name !== 'empty') {
|
|
138
|
+
throw new Error(`Fixture ${name} has no \`\`\`format-e fence`);
|
|
139
|
+
}
|
|
140
|
+
const body = fence ?? '';
|
|
141
|
+
const diff = c.parse(body);
|
|
142
|
+
if (diff.errors.length > 0) {
|
|
143
|
+
throw new Error(`Fixture ${name} parse errors:\n - ${diff.errors.join('\n - ')}`);
|
|
144
|
+
}
|
|
145
|
+
return diff;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Absolute path to the fixtures directory — useful for consumers using
|
|
149
|
+
* Vite's `?raw` import, esbuild's loader, or their own glob.
|
|
150
|
+
*/
|
|
151
|
+
export const FIXTURE_DIR_PATH = FIXTURE_DIR;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contract test suites for StorageAdapter and TransportAdapter.
|
|
3
|
+
* Import from '@sigloch/graph-api-core/testing'
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { runStorageContractTests } from '@sigloch/graph-api-core/testing'
|
|
8
|
+
* runStorageContractTests(() => new MyAdapter())
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export { runStorageContractTests } from './storage-contract-tests.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contract test suites for StorageAdapter and TransportAdapter.
|
|
3
|
+
* Import from '@sigloch/graph-api-core/testing'
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { runStorageContractTests } from '@sigloch/graph-api-core/testing'
|
|
8
|
+
* runStorageContractTests(() => new MyAdapter())
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export { runStorageContractTests } from './storage-contract-tests.js';
|