sds: id: SDS-003 title: Knowledge Graph Service bounded_context: semantic-core satisfies_prds: [PRD-006, PRD-007, PRD-008] satisfies_adrs: [ADR-004, ADR-006, ADR-040] version: 1.1.0 status: approved owners: [“sea-core-team”] created: 2025-12-01 —
✅ MVP
The Knowledge Graph Service provides an inferential projection of the Semantic Core, storing and managing enterprise knowledge as a graph of interconnected entities and relationships. It adopts a SPARQL-first approach, prioritizing explicit query patterns over implicit reasoning. While it supports RDF/OWL for semantic representation, heavyweight reasoning is optional and secondary to materialized projections.
| Direction | Description | Format |
|---|---|---|
| Input | Semantic data | RDF triples (Turtle/JSON-LD) |
| Input | Queries | SPARQL (Primary), Cypher (Secondary) |
| Input | Constraints | SHACL definitions |
| Output | Graph query results | JSON/RDF |
| Output | Inferred relationships | JSON |
| Output | SHACL validation reports | JSON |
Refer to SDS-001: Data Model Schemas for:
Entity schema (represented as nodes)Resource schema (represented as nodes)Flow schema (represented as relationships)Instance schema (represented as nodes)Policy schema (represented as nodes with rule links)| Dependency | Type | Version | Justification |
|---|---|---|---|
| Oxigraph | Rust Binary / Python | 0.4.x | Production-grade RDF triple store with SPARQL 1.1 engine |
| pyoxigraph | Python Library | 0.4.x | Python bindings for Oxigraph |
| RDFLib | Python Library | 7.1.x | Fallback RDF store for development/testing |
| Comunica | Node.js Library | 3.x | Execute SPARQL queries in Node.js for TypeScript adapters |
| pySHACL | Python Library | 0.29.x | Validate knowledge graph integrity against SHACL shapes |
| OWL-RL | Python Library | 7.1.x | Enable logical inference over OWL ontologies |
Runtime validation behavior is controlled by a single configuration source of truth and injected into runtime adapters:
| Setting | Type | Default | Purpose |
|---|---|---|---|
SHACL_ENFORCEMENT_ENABLED |
boolean | true |
Enable or disable enforcement |
SHACL_ENFORCEMENT_CONTEXTS |
CSV | empty | If empty, all contexts are active |
SHACL_MAINTENANCE_MODE |
boolean | false |
Log violations but do not raise |
SHACL_SHAPES_VERSION |
string | v1 |
Versioned shapes selection |
Rules
validate_with_shacl(...) returns a structured ValidationResult at all call sites:
| Field | Type | Description |
|---|---|---|
conforms |
boolean | SHACL conformance result |
violations |
array | Normalized violation details |
report_graph |
string | SHACL report as Turtle or JSON-LD |
requires_context |
array | Missing contexts from sea:requiresContext |
shape_version |
string | Shapes version used |
maintenance_mode |
boolean | Whether enforcement was muted |
enforcement_applied |
boolean | Whether an error would be raised |
cache_hit |
boolean | Whether validation used cached result |
Fail-Closed Policy
maintenance_mode is true: return conforms=false without raising.ShaclValidationError.conforms=false and emit a warning.ontologies/sea-shapes.{version}.shacl when present.ontologies/sea-shapes.shacl.ontologies/shared/core.shacl and are imported via owl:imports.sea:validationScope "full", validate against the merged full graph.sea:requiresContext "<context-name>".sea:legacyData true with sea:migrationDeadline.This service integrates with the following hexagonal ports:
| Port | Location | Purpose |
|---|---|---|
TripleStorePort |
libs/skeleton/graph/ports/src/lib/triple-store.port.ts | RDF triple storage and SPARQL query interface |
The TripleStorePort provides:
storeTriples(input: StoreTripleInput): Promise<void> — Store RDF triplesquery(sparql: string): Promise<SparqlSelectResult> — Execute SPARQL SELECTdeletePolicy(policyId: string): Promise<void> — Delete triples by policygetTriplesForPolicy(policyId: string): Promise<RDFTriple[]> — Retrieve triplesexists(policyId: string): Promise<boolean> — Check existencePrefix: sea-debt: <http://sea-forge.com/schema/debt#>
| Class/Property | Type | Description |
|---|---|---|
sea-debt:SemanticDebtItem |
Class | Represents a recorded instance of semantic debt (e.g., translation loss). |
sea-debt:Evidence |
Class | Proof or context explaining why the debt occurred. |
sea-debt:Decision |
Class | The architectural decision or waiver allowing this debt. |
sea-debt:MitigationPlan |
Class | Planned steps to resolve the debt. |
sea-debt:hasEvidence |
Property | Links DebtItem to Evidence. |
sea-debt:governedBy |
Property | Links DebtItem to a Decision. |
sea-debt:hasMitigation |
Property | Links DebtItem to a MitigationPlan. |
sea-debt:regardingConcept |
Property | Links DebtItem/Evidence to a specific Semantic Concept (URI). |
sea-debt:status |
Property | Status of the debt (e.g., “Accepted”, “Expired”, “Critical”). |
sea-debt:expiryDate |
Property | Date when the accepted debt expires. |
Prefix: sea-inc: <http://sea-forge.com/schema/incident#>
Groups multiple SemanticDebtItem instances under a common root cause. See SDS-016: Semantic Incidents for the complete specification.
| Class/Property | Type | Description |
|---|---|---|
sea-inc:SemanticIncident |
Class | Groups related debt items under common root cause |
sea-inc:RootCause |
Class | The underlying cause of the incident |
sea-inc:hasDebtItem |
Property | Links Incident to constituent DebtItems |
sea-inc:hasRootCause |
Property | Links Incident to RootCause |
sea-inc:affectedContext |
Property | Links Incident to affected BoundedContext |
sea-inc:owner |
Property | Links Incident to responsible Principal |
sea-inc:status |
Property | Current lifecycle state (open, triaging, contained, resolving, resolved, postmortem, closed) |
sea-inc:severity |
Property | Aggregate severity |
sea-inc:supersedes |
Property | Links DebtItem to superseded DebtItem |
1
2
3
4
5
6
7
8
9
10
11
12
13
PREFIX sea-inc: <http://sea-forge.com/schema/incident#>
SELECT ?incident ?title ?severity (COUNT(?debt) AS ?debtCount)
WHERE {
?incident a sea-inc:SemanticIncident ;
sea-inc:title ?title ;
sea-inc:severity ?severity ;
sea-inc:status ?status ;
sea-inc:hasDebtItem ?debt .
FILTER (?status IN ("open", "triaging", "contained", "resolving"))
}
GROUP BY ?incident ?title ?severity
ORDER BY DESC(?severity) DESC(?debtCount)
These rules define how SemanticDebtCreated events are projected into the graph.
Source: SemanticDebtCreated Event
Target: RDF Triples
1
2
3
4
<sea:debt:{eventId}> a sea-debt:SemanticDebtItem ;
rdfs:label "{payload.title}" ;
sea-debt:status "{payload.status}" ;
dct:created "{timestamp}" .
1
<sea:debt:{eventId}> sea-debt:regardingConcept <sea:concept:{payload.conceptId}> .
1
2
3
<sea:evidence:{evidenceId}> a sea-debt:Evidence ;
rdfs:comment "{payload.evidenceDescription}" .
<sea:debt:{eventId}> sea-debt:hasEvidence <sea:evidence:{evidenceId}> .
Find debt items that are “Blocking” within a specific context (e.g., PaymentProcessing).
1
2
3
4
5
6
7
8
9
10
11
PREFIX sea-debt: <http://sea-forge.com/schema/debt#>
PREFIX sea: <http://sea-forge.com/schema/core#>
SELECT ?debt ?label ?concept
WHERE {
?debt a sea-debt:SemanticDebtItem ;
sea-debt:status "Blocking" ;
rdfs:label ?label ;
sea-debt:regardingConcept ?concept .
?concept sea:inContext <http://sea-forge.com/context/PaymentProcessing> .
}
Trace debt on a concept and its upstream dependencies (2 levels deep).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SELECT DISTINCT ?debt ?affectedConcept ?upstreamConcept
WHERE {
# Debt directly on the concept
{
?debt sea-debt:regardingConcept <http://sea-forge.com/concept/TargetConcept> .
BIND(<http://sea-forge.com/concept/TargetConcept> AS ?affectedConcept)
}
UNION
# Debt on direct dependency (Level 1)
{
<http://sea-forge.com/concept/TargetConcept> sea:dependsOn ?l1 .
?debt sea-debt:regardingConcept ?l1 .
BIND(?l1 AS ?affectedConcept)
}
UNION
# Debt on dependency of dependency (Level 2)
{
<http://sea-forge.com/concept/TargetConcept> sea:dependsOn ?l1 .
?l1 sea:dependsOn ?l2 .
?debt sea-debt:regardingConcept ?l2 .
BIND(?l2 AS ?affectedConcept)
}
}
Concepts with the highest count of associated debt items.
1
2
3
4
5
6
7
8
9
SELECT ?policy (COUNT(?debt) AS ?debtCount)
WHERE {
?debt a sea-debt:SemanticDebtItem ;
sea-debt:regardingConcept ?policy .
?policy a sea:Policy .
}
GROUP BY ?policy
ORDER BY DESC(?debtCount)
LIMIT 10
Accepted debt that has passed its expiry date.
1
2
3
4
5
6
7
SELECT ?debt ?decision ?expiry
WHERE {
?debt sea-debt:status "Accepted" ;
sea-debt:governedBy ?decision .
?decision sea-debt:expiryDate ?expiry .
FILTER (?expiry < NOW())
}
Debt accepted under “Break-glass” emergency conditions.
1
2
3
4
5
6
SELECT ?debt ?reason
WHERE {
?debt sea-debt:governedBy ?decision .
?decision sea-debt:type "BreakGlass" ;
rdfs:comment ?reason .
}
Attributes where translation loss frequency is high.
1
2
3
4
5
6
7
8
SELECT ?concept (COUNT(?debt) as ?lossCount)
WHERE {
?debt a sea-debt:SemanticDebtItem ;
sea-debt:type "TranslationLoss" ;
sea-debt:regardingConcept ?concept .
}
GROUP BY ?concept
ORDER BY DESC(?lossCount)
A Snapshot represents an immutable, versioned state of the Knowledge Graph at a specific point in time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "KnowledgeGraphSnapshot",
"description": "Immutable, versioned state of the Knowledge Graph",
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^ifl:snap:[a-f0-9]{64}$",
"description": "Content-addressable snapshot identifier (SHA-256 hash)"
},
"version": {
"type": "string",
"description": "Semantic version (e.g., '1.0.0')"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp when snapshot was created"
},
"parentSnapshotId": {
"type": "string",
"pattern": "^ifl:snap:[a-f0-9]{64}$",
"description": "Parent snapshot ID (null for initial snapshot)"
},
"appliedChangeSets": {
"type": "array",
"items": {
"type": "string",
"pattern": "^ifl:cs:[a-f0-9]{64}$"
},
"description": "Ordered list of change set IDs applied to reach this snapshot"
},
"tripleCount": {
"type": "integer",
"minimum": 0,
"description": "Total number of RDF triples in this snapshot"
},
"contentHash": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 hash of canonical triple serialization (Turtle sorted)"
},
"metadata": {
"type": "object",
"properties": {
"createdBy": {
"type": "string",
"description": "Principal or system that created the snapshot"
},
"description": {
"type": "string",
"description": "Human-readable description of this snapshot"
},
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "Semantic tags (e.g., 'production', 'staging')"
}
},
"additionalProperties": true
}
},
"required": ["id", "version", "timestamp", "contentHash", "appliedChangeSets"]
}
A ChangeSet represents a set of graph operations (add/delete triples) that can be applied to a snapshot.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "KnowledgeGraphChangeSet",
"description": "Set of graph operations to transition between snapshots",
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^ifl:cs:[a-f0-9]{64}$",
"description": "Content-addressable change set identifier (SHA-256 hash)"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "UTC timestamp when change set was created"
},
"sourceSnapshotId": {
"type": "string",
"pattern": "^ifl:snap:[a-f0-9]{64}$",
"description": "Snapshot this change set is based on"
},
"operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["add", "delete"],
"description": "Operation type"
},
"triple": {
"type": "object",
"properties": {
"subject": {"type": "string", "format": "uri"},
"predicate": {"type": "string", "format": "uri"},
"object": {
"oneOf": [
{"type": "string", "format": "uri"},
{
"type": "object",
"properties": {
"value": {"type": "string"},
"datatype": {"type": "string", "format": "uri"},
"language": {"type": "string"}
},
"required": ["value"]
}
]
}
},
"required": ["subject", "predicate", "object"]
}
},
"required": ["type", "triple"]
},
"description": "Ordered list of add/delete operations"
},
"metadata": {
"type": "object",
"properties": {
"createdBy": {"type": "string"},
"description": {"type": "string"},
"source": {
"type": "string",
"description": "Origin of change (e.g., 'SemanticDebtCreated event')"
}
},
"additionalProperties": true
}
},
"required": ["id", "timestamp", "sourceSnapshotId", "operations"]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SnapshotQueryResult",
"description": "Result of executing a query against a snapshot",
"type": "object",
"properties": {
"snapshotId": {
"type": "string",
"pattern": "^ifl:snap:[a-f0-9]{64}$"
},
"query": {
"type": "object",
"properties": {
"language": {
"type": "string",
"enum": ["sparql", "cypher"]
},
"text": {"type": "string"}
},
"required": ["language", "text"]
},
"results": {
"type": "array",
"items": {"type": "object"},
"description": "Query result bindings"
},
"executionTimeMs": {
"type": "integer",
"minimum": 0,
"description": "Query execution time in milliseconds"
},
"resultHash": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 hash of canonical result serialization (for stability verification)"
}
},
"required": ["snapshotId", "query", "results", "executionTimeMs", "resultHash"]
}
| Endpoint | Method | Description |
|---|---|---|
/graph/query |
POST | Execute Cypher/SPARQL query |
/graph/shacl/validate |
POST | Run SHACL validation |
/graph/snapshots |
GET | List available snapshots |
/graph/snapshots |
POST | Create a new snapshot from current state |
/graph/snapshots/{id} |
GET | Retrieve snapshot metadata |
/graph/snapshots/{id}/query |
POST | Execute query against specific snapshot |
/graph/changesets |
GET | List available change sets |
/graph/changesets |
POST | Create a new change set |
/graph/changesets/{id} |
GET | Retrieve change set details |
/graph/changesets/{id}/apply |
POST | Apply change set to create new snapshot |
Create Snapshot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /graph/snapshots
{
"version": "1.0.0",
"description": "Initial production snapshot",
"tags": ["production", "release-1.0"],
"createdBy": "system"
}
// Response
{
"id": "ifl:snap:a3b8c9d2e1f4567890abcdef1234567890abcdef1234567890abcdef12345678",
"version": "1.0.0",
"timestamp": "2025-12-30T10:00:00Z",
"parentSnapshotId": null,
"appliedChangeSets": [],
"tripleCount": 1523,
"contentHash": "a3b8c9d2e1f4567890abcdef1234567890abcdef1234567890abcdef12345678",
"metadata": {
"createdBy": "system",
"description": "Initial production snapshot",
"tags": ["production", "release-1.0"]
}
}
List Snapshots
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET /graph/snapshots?tags=production&limit=10
// Response
{
"snapshots": [
{
"id": "ifl:snap:a3b8c9d2...",
"version": "1.0.0",
"timestamp": "2025-12-30T10:00:00Z",
"tripleCount": 1523,
"tags": ["production", "release-1.0"]
},
{
"id": "ifl:snap:b4c9d8e2...",
"version": "1.1.0",
"timestamp": "2025-12-31T14:30:00Z",
"tripleCount": 1687,
"tags": ["production", "release-1.1"]
}
],
"total": 2,
"limit": 10,
"offset": 0
}
Query Snapshot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
POST /graph/snapshots/ifl:snap:a3b8c9d2.../query
{
"language": "sparql",
"query": "PREFIX sea-debt: <http://sea-forge.com/schema/debt#> SELECT ?debt ?status WHERE { ?debt a sea-debt:SemanticDebtItem ; sea-debt:status ?status . } LIMIT 10"
}
// Response
{
"snapshotId": "ifl:snap:a3b8c9d2...",
"query": {
"language": "sparql",
"text": "PREFIX sea-debt: <http://sea-forge.com/schema/debt#> SELECT ?debt ?status WHERE { ?debt a sea-debt:SemanticDebtItem ; sea-debt:status ?status . } LIMIT 10"
},
"results": [
{
"debt": "http://sea-forge.com/debt/123",
"status": "Blocking"
},
{
"debt": "http://sea-forge.com/debt/456",
"status": "Accepted"
}
],
"executionTimeMs": 45,
"resultHash": "f1e2d3c4b5a6978901234567890abcdef1234567890abcdef1234567890abcdef"
}
Create ChangeSet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
POST /graph/changesets
{
"sourceSnapshotId": "ifl:snap:a3b8c9d2...",
"description": "Add new debt item from SemanticDebtCreated event",
"createdBy": "event-projector",
"source": "SemanticDebtCreated:evt-789",
"operations": [
{
"type": "add",
"triple": {
"subject": "http://sea-forge.com/debt/789",
"predicate": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
"object": "http://sea-forge.com/schema/debt#SemanticDebtItem"
}
},
{
"type": "add",
"triple": {
"subject": "http://sea-forge.com/debt/789",
"predicate": "http://www.w3.org/2000/01/rdf-schema#label",
"object": {
"value": "Missing validation for payment amount",
"datatype": "http://www.w3.org/2001/XMLSchema#string"
}
}
},
{
"type": "add",
"triple": {
"subject": "http://sea-forge.com/debt/789",
"predicate": "http://sea-forge.com/schema/debt#status",
"object": {
"value": "Blocking",
"datatype": "http://www.w3.org/2001/XMLSchema#string"
}
}
}
]
}
// Response
{
"id": "ifl:cs:c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7a8b9c0d1e2f3g4h5",
"timestamp": "2025-12-30T11:15:00Z",
"sourceSnapshotId": "ifl:snap:a3b8c9d2...",
"operations": [...],
"metadata": {
"createdBy": "event-projector",
"description": "Add new debt item from SemanticDebtCreated event",
"source": "SemanticDebtCreated:evt-789"
}
}
Apply ChangeSet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /graph/changesets/ifl:cs:c4d5e6f7.../apply
{
"version": "1.0.1",
"description": "Applied debt item creation",
"tags": ["development"]
}
// Response
{
"newSnapshotId": "ifl:snap:b4c9d8e2...",
"version": "1.0.1",
"timestamp": "2025-12-30T11:15:30Z",
"parentSnapshotId": "ifl:snap:a3b8c9d2...",
"appliedChangeSets": ["ifl:cs:c4d5e6f7..."],
"tripleCount": 1526,
"contentHash": "b4c9d8e2f1a3567890abcdef1234567890abcdef1234567890abcdef12345678",
"metadata": {
"description": "Applied debt item creation",
"tags": ["development"]
}
}
Graph Query
1
2
3
4
5
6
7
8
9
10
11
12
13
POST /graph/query
{
"language": "cypher",
"query": "MATCH (c:Customer)-[:PLACED]->(o:Order) RETURN c, o LIMIT 10"
}
// Response
{
"results": [
{"c": {...}, "o": {...}},
...
]
}
SHACL Validation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
POST /graph/shacl/validate
{
"targetGraph": "production",
"context": "semantic-core",
"shapesVersion": "v1",
"validationScope": "delta",
"dataGraph": "@prefix sea: <http://sea-forge.com/schema/core#> . ..."
}
// Response
{
"conforms": false,
"shapeVersion": "v1",
"maintenanceMode": false,
"enforcementApplied": true,
"violations": [
{
"shape": "http://sea-forge.com/shapes/EmailFormatShape",
"focusNode": "customer:123",
"path": "sea:email",
"message": "Invalid email format",
"severity": "Violation",
"requiresContext": []
}
],
"requiresContext": [],
"reportGraph": "@prefix sh: <http://www.w3.org/ns/shacl#> . ..."
}
| Code | Description |
|---|---|
400 Bad Request |
Invalid query/SHACL syntax |
404 Not Found |
Snapshot or change set not found |
409 Conflict |
Change set cannot be applied (conflict) |
422 Unprocessable Entity |
Invalid change set operations |
500 Internal Server Error |
Graph database error |
503 Service Unavailable |
Snapshot creation in progress (retry later) |
1
2
3
4
5
# Python dependencies (add to pyproject.toml)
pip install pyoxigraph rdflib pyshacl owlrl
# Node.js dependencies (add to package.json)
pnpm add @comunica/query-sparql
1
2
3
4
5
6
7
8
9
interface KnowledgeGraphPort {
querySparql(snapshotId: string, query: string): Promise<SparqlResult>;
validateShacl(snapshotId: string, shapeUri: string, dataGraph: string): Promise<ValidationReport>;
inferOwl(snapshotId: string, ontologyUri: string): Promise<InferenceResult>;
createSnapshot(metadata: SnapshotMetadata): Promise<Snapshot>;
applyChangeSet(changeSetId: string, targetMetadata: SnapshotMetadata): Promise<Snapshot>;
getSnapshot(snapshotId: string): Promise<Snapshot>;
listSnapshots(filters: SnapshotFilters): Promise<Snapshot[]>;
}
ValidationResult with actionable SHACL diagnosticsShaclValidationError only when enabled and active