> ## 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.

# Plan #6: SPEC-020 Tri-Graph Execution Plan

> SPEC-020 Tri-Graph Execution Plan

<Info>**Status:** `complete` · **Version** `1.1` · Filed 2026-04-19</Info>

# SPEC-020 Tri-Graph Execution Plan

**Owner:** Donna (Claude Code) · **Reviewer:** Frank · **Authoritative sources:** SPEC-020 memory body (56e279ac), v1.2 addendum (4c910dd8), v1.3 addendum (0ddd894b), Q1-Q6 resolutions (5cae8334), ADR-22 (966082d0)

**No time estimates.** The plan defines wave boundaries and exit criteria. Time taken is whatever it takes. Frank is running single-operator AI-native workflow with no coordination overhead; corporate-software timelines don't apply.

## Read-set for session open

Before starting, pull these via `semantic_recall`:

1. `"SPEC-020 tri-graph Canonical Semantic Temporal"` — main spec body
2. `"SPEC-020 v1.2 git tags migration"` — migration strategy addendum
3. `"SPEC-020 v1.3 WIP intent"` — WIP verbs and intent triggers
4. `"SPEC-020 Q1 Q2 Q3 Q4 Q5 Q6 resolved"` — settled decisions
5. `"ADR-22 tri-graph adopt"` — the accepting decision

Then run `prism_start` to pick up project context and any rules\_reminders.

## On prereqs

There are no hard prereqs. Existing TODOs (#67 delta timeout, #72 localhost audit, #76 Lola filesystem mount) are independent work and do not block the tri-graph. If #67 bites during Wave E migration, fix it inline. Don't clear the board before starting.

## Wave A — Foundation

**Phase 0: Parallel schema install**

* Alembic migration for `fact_candidates` table (review queue per Q2/Q4)
* Alembic migration for `alias_candidates` table (tier 3 per Q2)
* Neo4j schema additions: `:Type`, `:Entity`, `:EntityState`, `:INSTANCE_OF`, `:ALIAS_OF`, `:HAS_STATE`, `:SUPERSEDED_BY`, `:CAN_REFERENCE`, `:CAN_ALIAS`, `:CAN_SUPERSEDE` — all ADDITIONS alongside existing `:Memory`/`:Concept`/`:MENTIONS`
* Neo4j constraints: unique on `(:Entity {uuid})` per tenant+namespace; mandatory `:INSTANCE_OF` edge; composite index on `(:EntityState.entity_uuid, valid_until)` for computed current-state lookup (Q1)
* Nothing reads or writes new schema yet; phase is non-breaking

**Exit:** `docker compose up` brings backend up clean; Neo4j shows new constraints; existing retrieval still works unchanged.

## Wave B — Canonical layer

**Phase 1: Type seeding and Entity creation**

* Parse `templates/prism-base.md` and `templates/prism-application.md` for declared types
  * System zone (prism-base.md): `:Project`, `:Persona`, `:Session`
  * Application zone (prism-application.md): `:ADR`, `:SPEC`, `:Plan`, `:TODO`, `:Delta`, `:Journal`, `:Retro`, `:Component`, `:EnvVar`, `:Location`, `:Mode`
  * `:Location` replaces `:Host` per Q5; application's Location schema = {fqdn, bind_addr, tls_enabled}
* Template parsing produces `:Type` nodes with `zone`, `ptype`, `immutable_props`
* Implement `prism_entity(type_name, initial_props, source_memory_id)`:
  * Validates type exists in current project's ptype
  * Validates initial\_props against immutable\_props schema
  * Creates `:Entity` with UUID
  * Creates initial `:EntityState` with `valid_from=now`, `valid_until=NULL`, `commit_status="committed"` for application's initial seeding
  * `:INSTANCE_OF` and `:HAS_STATE` edges created atomically
* App-layer validation rejects `:Entity` writes carrying mutable props

**Exit:** `prism_entity` creates entities correctly; invariant violations surface; smoke tests 1-4 pass.

## Wave C — Semantic and Temporal (parallel)

Phases 2 and 3 are independent. Run them in parallel.

**Phase 2: Semantic layer**

* `prism_alias(surface_form, canonical_uuid, authority_tier=1|2|3)` — tier 1 and 2 direct, tier 3 to `alias_candidates`
* `:REFERENCES` / `:FORMALIZES` / `:DEPENDS_ON` / etc. edges constrained by `:CAN_REFERENCE` declarations
* Edge creation rejected when source/target type combination not permitted
* Every edge carries `source_memory_id` and `created_at`
* Extend ingestion concept extraction: type list as gazetteer (not just regex)
* Pattern detection for alias candidates → tier 3
* `prism_review_facts(action, candidate_id, edits)`:
  * Q4 gating: promotion changing immutable\_props → `frank-decision` tag + rules\_reminders
  * Otherwise agent-promotable

**Exit for Phase 2:** alias traversal works; reference type enforcement active; review queue populates; smoke tests 5-8 pass.

**Phase 3: Temporal layer**

* `:EntityState` + `:HAS_STATE`
* `prism_fact(subject_uuid, predicate, value, valid_from=now, source_memory_id)` — single-property convenience
* `prism_transition(subject_uuid, props={...}, event_type, cause_memory_id)` — multi-property event per Q3
  * Atomic: closes prior current state, opens new, creates `:SUPERSEDED_BY` with event\_type
  * One transaction; no NULL-overlaps
  * Rejects composite-of-composites event\_types
* `prism_wip(entity_uuid, props, source, source_ref, supersedes_commit)` per v1.3
  * One WIP per entity max
  * Update-in-place if WIP exists
* `prism_seal(wip_state_uuid, sealing_event, props_at_seal)` — closes WIP, opens sealed, `:SUPERSEDED_BY {event_type="wip_sealed", props_drift=<bool>}`
* State-change pattern extraction → `fact_candidates` tier 3
* Q4 identity-change routing: immutable\_prop changes → `frank-decision` + rules\_reminders

**Exit for Phase 3:** transitions ACID-correct; WIP+sealed coexist per v1.3; chains acyclic; smoke tests 9-13 + 21-25 pass.

## Wave D — Retrieval

**Phase 4: as\_of and three-phase retrieval**

* `semantic_recall` signature adds `as_of: datetime | None = None`
* Phase 1 (query resolution): entity-mention detection → Canonical+Semantic traversal → anchor UUIDs
* Phase 2 (temporal): computed traversal per Q1 — walk `:HAS_STATE`, filter `valid_until IS NULL` for current OR range-check for as\_of; WIP-aware per v1.3
* Phase 3 (memory recall): existing 3-leg RRF unchanged; post-process with truth anchors → boost current, tag `[historical]` for prior states, tag `[wip]` when truth anchor is WIP
* Add `wip_visibility` and `provenance` fields to response

**Exit for Phase 4:** `as_of=NULL` returns current (sealed or WIP); `as_of=<past>` returns state valid at that point; historical memories tagged not filtered; smoke tests 14-18 pass; p50 regression \< 10% of baseline 305ms.

**Phase 5: Graph leg rewiring**

* Replace `(:Memory)-[:MENTIONS]->(:Concept)` traversal with Canonical/Semantic/Temporal-aware walk
* Start from query entities → walk Semantic references → find relevant states → find memories via source\_memory\_id chain
* Backward compat: old graph leg behind feature flag during transition

**Exit for Phase 5:** graph leg score reflects full tri-graph relevance; retrieval quality measurably improved on known-answer probe set.

## Wave E — Migration

**Phase 6: Seed PID-PGR01 and register known aliases**

Migration runs with `dry_run=True` by default.

**Step 1 — Tier 0 (git tags, v1.2):**

* Parse annotated git tags
* For each consecutive tag pair, git-diff for changed files
* Map file paths to entity types per §9 mapping
* Write supersessions: `valid_from=T_n+1.date`, `event_type=classify(tag_message)`, `cause_commit=T_n+1.sha`
* Lightweight tags skipped, counted in report

**Step 2 — Tier 1 (between-tag commits):**

* For state changes falling between tags, commit is the event
* Lower priority than tag events

**Step 3 — Tier 2 (memory drift):**

* (subject, predicate) pairs with multiple memories → tier 3 candidates in `fact_candidates`
* Frank promotes selectively

**Step 4 — Known aliases:**

* "PrismGR" → Project (tier 1)
* "personal" → "local" mode (pending tier 1; activates with TODO #74)
* Other renames visible in ADR history

**Step 5 — Reference extraction from ADR/SPEC bodies:**

* One-time pass over ADR+SPEC bodies; create `:REFERENCES`, `:FORMALIZES` edges

**Report-first per Q6:** migration emits Tier counts + coverage estimate; operator reviews; re-runs with `dry_run=False`.

**Exit for Phase 6:** all PID-PGR01 entities have at least one sealed state; "PrismGR" alias resolves to Project; migration report looks reasonable.

## Wave F — Validation and deprecation

**Phase 7: Smoke battery**

* 25 tests in `mcp/smoke_trigraph.py` (20 from v1.0 §10 + 5 from v1.3)
* Run on PID-PGR01 post-migration
* Run on synthetic project for invariant testing
* Performance regression: existing perf-probe p50 ≤ 335ms

**Phase 8: Old schema deprecation**

* Only after Phase 7 green and stable through one release cycle
* Drop `:Memory`, `:Concept`, `:MENTIONS` from Neo4j
* Remove old graph-leg code
* Postgres memory\_embeddings untouched throughout

**Exit for Wave F:**

* 25 smoke tests green
* Live demo on PID-PGR01 per §13:
  * `semantic_recall("what is this project called")` → "Prism", no historical tag
  * `semantic_recall("PrismGR installation")` → memories tagged `[historical]`
  * `semantic_recall("server1 backend URL")` → current URL
  * New renames via `prism_fact` reflect immediately
* Provenance populated
* p50 within budget

## Methodology alignment

* Each wave opens with `prism_start`, closes with a `store_session_delta` call
* Non-trivial decisions → ADR if architectural, delta if tactical
* Phase completion → mark corresponding TODO status on TODO #80
* Implementation surprise contradicting spec → `spec-feedback` memory, don't pause
* Intent signals during implementation (e.g. "let's noodle on the Cypher query shape") → `prism_wip` once Temporal ships. Dog-food starting Wave D.

## Risks

| Risk                                      | Mitigation                                                                                                   |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Neo4j constraint syntax gotchas           | Prototype constraints in throwaway Neo4j before Wave A; pin Neo4j version                                    |
| Git-tag parsing misclassifies event types | Conservative classifier + manual override per Q6                                                             |
| Tier 0 coverage on PID-PGR01 \< 50%       | Acceptable; Tier 1 + review-queue cover gaps; document coverage in migration delta                           |
| WIP verb semantics surprise later users   | Ship Phase 3.5 methodology doc in prism-base.md before first non-PID-PGR01 project uses tri-graph            |
| Retrieval p50 regression > 10%            | Profile graph-leg traversal first (likeliest culprit); consider stored current-state pointer per Q1 fallback |

## Explicitly NOT in this plan

* Cross-ptype retrieval ("Paris" disambiguation)
* Multi-tenant fact conflict resolution
* SPEC-021 (git as continuous temporal feed via hooks) — follow-up
* Backfill of all 192 existing memory\_embeddings with extracted facts — scope creep
* Neo4j-to-Neo4j replication for LAN mode — orthogonal

## Completion trigger for TODO #80

TODO #80 closes when Wave F exits green. Also closes TODOs #77 (rejected), #78 (folded), #79 (superseded).

## Changelog

* **v1.1 (2026-04-19):** Removed all day-estimates. Removed prereq-clearing guidance — Frank flagged that #67/#72/#76 are independent work and don't block this plan. Wave boundaries and exit criteria unchanged.
* **v1.0 (2026-04-19):** Initial plan.
