> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC-020 v1.0: Tri-Graph Knowledge Representation (Canonical / Semantic / Temporal)

> Tri-Graph Knowledge Representation (Canonical / Semantic / Temporal)

<Info>**Status:** `accepted` · **Version** `1.0` · Filed 2026-04-21</Info>

# Title: Tri-Graph Knowledge Representation (Canonical / Semantic / Temporal)

## Status

accepted (shipped via Plan #6, 2026-04-19/20) — retroactive formal filing per governance discipline

## Context

This spec is being filed retroactively. SPEC-020 was drafted, reviewed, and shipped entirely through memory deltas (56e279ac body, 4c910dd8 v1.2 addendum, 0ddd894b v1.3 addendum, 5cae8334 Q1-Q6 resolutions) and an accepting ADR (ADR-23, id ccab2b8d). It was implemented across Plan #6's six waves and retroed in Retro #5. At no point was it filed through `prism_spec(action=create)` to the formal specs table, so it doesn't appear in `docs/specs/` or the Mintlify export.

This filing closes that gap. Authoritative source-of-truth for spec bodies is the memory delta chain above; this filing consolidates the key content for queryability via `prism_spec(action=list)` and for export pipeline discovery.

## Problem

Before the tri-graph, Prism's Neo4j graph was a single-layer `(:Memory)-[:MENTIONS]->(:Concept)` co-occurrence index. That structure:

* Conflated **identity** with **current-state** (a project "is" its name, so renaming breaks identity).
* Conflated **aliases** (surface-form variants: "PrismGR" = old name for "Prism") with **references** (typed relationships: ADR-19 FORMALIZES SPEC-019).
* Had no notion of **time** — no way to ask "what was true as of last month" or distinguish a stale fact from a current one.

Observable failure: `semantic_recall("PrismGR installation")` returned historical memories with the same confidence as current-state memories, with no way to tell which was which.

## Decision

Replace the single-layer graph with a three-layer architecture:

### Layer 1 — Canonical (identity + type ontology)

Stable facts about what kinds of things exist and who each thing is.

**Nodes:**

* `(:Type {zone, name, ptype, immutable_props, mutable_props})` — type ontology. System-zone types (`Project`, `Persona`, `Session`) apply to every Prism project. Project-zone types depend on ptype (`application` declares `ADR`, `SPEC`, `Plan`, `TODO`, `Delta`, `Journal`, `Retro`, `Component`, `EnvVar`, `Location`, `Mode`).
* `(:Entity {uuid, type_name, tenant_id, namespace, created_at, source_memory_id})` — identity registry. Carries only metadata. Domain properties live on `:EntityState`.

**Edges:**

* `(:Entity)-[:INSTANCE_OF]->(:Type)` — every entity has exactly one.

**Invariants:**

* `:Entity.uuid` unique per `(tenant_id, namespace)`.
* `:Entity` MUST NOT carry any domain property (enforced at application layer).

### Layer 2 — Semantic (aliases + typed references)

Which surface forms refer to the same entity, and how distinct entities relate.

**Edges:**

* `(:Entity)-[:ALIAS_OF {surface_form, authority_tier}]->(:Entity)` — alias resolution chain. Authority tiers: 1 (human-asserted), 2 (system-derived), 3 (pattern-extracted candidate, requires review).
* `(:Entity)-[:REFERENCES|:FORMALIZES|:DEPENDS_ON|etc.]->(:Entity)` — typed reference edges, constrained by `:CAN_REFERENCE` declarations on Types.

### Layer 3 — Temporal (state versions + supersession events)

Every change in state is a new `:EntityState` node linked to the prior one via a typed `:SUPERSEDED_BY` event.

**Nodes:**

* `(:EntityState {valid_from, valid_until, commit_status, props...})` — state version. `valid_until=NULL` means current. `commit_status ∈ {wip, committed}` per v1.3.

**Edges:**

* `(:Entity)-[:HAS_STATE]->(:EntityState)` — state pointer.
* `(:EntityState)-[:SUPERSEDED_BY {event_type, cause_memory_id, cause_commit}]->(:EntityState)` — supersession event with typed causality.

**Invariants:**

* Current-state pointer is computed by traversal (`valid_until IS NULL`), never stored.
* At most one WIP state per entity.
* Supersession chains are acyclic (enforced at write time).

### Verbs introduced

* `prism_entity(type_name, initial_props, source_memory_id)` — create canonical entity + initial state.
* `prism_alias(surface_form, canonical_uuid, authority_tier)` — register alias.
* `prism_reference(source_uuid, target_uuid, reference_type, source_memory_id)` — typed reference edge.
* `prism_fact(subject_uuid, predicate, value, valid_from, source_memory_id)` — single-property state change.
* `prism_transition(subject_uuid, props, event_type, cause_memory_id)` — multi-property ACID state transition.
* `prism_wip(entity_uuid, props, source, source_ref, supersedes_commit)` — declare WIP state.
* `prism_seal(wip_state_uuid, sealing_event, props_at_seal)` — close WIP, open sealed state.
* `prism_state_as_of(entity_uuid, as_of=None)` — query state at a point in time.
* `prism_review_fact_candidate(action, candidate_id, edits)` — promote / reject tier-3 candidate.
* `semantic_recall` gains `as_of: datetime | None` parameter.

## Rationale

Three distinct reasoning modes require three distinct graph structures. Conflating them corrupts each:

* Identity reasoning ("are these two references to the same thing?") wants stable UUIDs and alias chains — the Canonical + Semantic layers.
* Structural reasoning ("what formalizes what, what depends on what?") wants typed references — the Semantic layer.
* Temporal reasoning ("what was true as of T, what changed, why?") wants versioned state with typed supersession — the Temporal layer.

The separation rule: canonical identity never carries mutable props; mutable state lives on temporal version nodes; the pointer from canonical to "current state" is computed by traversal, never stored. This delivers entity identity surviving renames, aliases resolving at retrieval time, point-in-time queries as first-class, queryable causality, and a graph leg in hybrid RAG that finally earns its keep.

## Alternatives considered

* **Recency decay on RRF fusion.** Theater on short-horizon projects — discriminates nothing when all facts are within one week. Rejected (TODO #77).
* **Postgres facts table (bitemporal rows).** Works but confines event causality to joins, loses graph-native traversal. SQL-shaped solution to graph-shaped problem. Rejected (TODO #79).
* **Ad-hoc `:SUPERSEDES` edges on existing Concept graph.** Treats symptom (renames) not disease (no temporal model). Subset of tri-graph, not replacement. Rejected (TODO #78).
* **Per-ptype hardcoded ontologies.** Would work for application ptype but fail for research / travel. Tri-graph's ptype-declarative Canonical mechanism generalizes.
* **Keep current graph + smarter ingestion heuristics.** Does not address identity stability across renames, no point-in-time queries, no alias / reference separation.

## Migration and exit

Shipped 2026-04-19/20 across Plan #6 Waves A-F:

* **Wave A** — parallel schema install (non-breaking; old and new coexist).
* **Wave B** — Canonical verbs (`prism_entity`, Type seeding from templates).
* **Wave C** — Semantic (`prism_alias`, `prism_reference`) + Temporal (`prism_fact`, `prism_transition`, `prism_wip`, `prism_seal`).
* **Wave D** — Retrieval: `as_of` on `semantic_recall`, three-phase query resolution.
* **Wave E** — Migration: Tier 0 (git tags), Tier 1 (between-tag commits), Tier 2 (memory drift → candidates), known aliases ("PrismGR" → Project).
* **Wave F** — Validation (25/25 smoke tests green, zero aggregate perf regression) + old schema deprecation gated on one release-cycle soak.

Final state on PID-PGR01 server1: 199 `:Entity` nodes, 46 typed reference edges, all 25 smoke tests green, p50 within budget.

## Open follow-ups

* SPEC-018 (git as continuous temporal feed via hooks) — deferred follow-up.
* Backfill of pre-tri-graph `memory_embeddings` with extracted facts — explicit scope creep, deferred.
* Cross-ptype retrieval ("Paris" disambiguation across travel + research ptypes) — future work.
* Multi-tenant fact conflict resolution — future work.

## References

* ADR-23 (accepting decision, id `ccab2b8d-97d2-4c5e-8c05-e59a93c0ae85`)
* Plan #6 (execution plan, status `complete`)
* Retro #5 (Plan #6 retrospective, 8 global + 4 project lessons)
* Memory deltas: `56e279ac` (main body), `4c910dd8` (v1.2 migration strategy), `0ddd894b` (v1.3 WIP/intent), `5cae8334` (Q1-Q6 resolutions)
* `docs/TRIGRAPH.md` — external-facing architecture overview

Status: accepted
