Phase: Plan #6 — SPEC-020 Tri-Graph Knowledge Representation · Status:
closed · Filed 2026-04-20What Worked
- Wave-by-wave incremental shipping with concrete exit criteria — each wave independently deployable, zero blocking between waves
- Non-breaking parallel install pattern — new tri-graph alongside old :Memory/:Concept/:MENTIONS; zero user-visible regression across 6 waves
- Synthetic test fixtures via valid_from_override on prism_entity + prism_transition — enabled smoke 14-18 historical scenarios without backdating real data
- Fail-soft annotation layer — safe_annotate_recall wraps tri-graph resolution so Neo4j outage or empty graph returns null state_flag instead of breaking existing recall
- Deploy mechanism built alongside feature code (bin/prism-deploy-server1.sh solved during Wave A) — reused on 5 subsequent waves with zero friction
- Pragmatic deferrals with documented gates (Phase 5 probe-set, Phase 8 release-cycle soak) — prevents scope creep while preserving clear re-entry points
- Idempotent migration (Wave E) — runs twice cleanly, existing entities skip, new ones seed; caught-and-fixed limit=10000 silent-failure bug
- Vibe Coder protocol held throughout — wave-boundary language (no day-estimates), preserved TODOs #77/#78/#79 as considered alternatives, kept pace aligned with ‘whatever it takes’
- Neo4j constraint/index prototyping in local before Wave A caught syntax gotchas early (composite uniqueness + range indexes on 5.18.1)
What Didn’t
- Neo4j MERGE refused null property values in :Type uniqueness key — fix: empty-string sentinel for system-zone ptype
- ON CREATE SET ordering in MERGE statement — must precede unconditional SET; one-line fix, noisy failure mode
- rsync -a failed for non-owning-user deploys (donna can’t chtimes dirs frank owns) — fix: —omit-dir-times —no-perms —no-group + tolerate exit 23
- /entities endpoint limit=10000 silently returned 422 when max was 1000, making existing_map empty and re-creating all entities as duplicates — bumped max to 10000; caught by eyeballing entity counts vs DB
- Nested dict/list props in Postgres not Neo4j-compatible (Retro.lessons = list[dict]) — added _coerce_for_neo4j JSON-encoding helper at service boundary
- neo4j.time.DateTime not Pydantic-serializable — added _coerce_jsonable converting to ISO strings at service boundary
- Initial annotation logic used hit timestamp; SPEC-020 §10 test 18 required surface-form-based decision — rewrote to longest-surface-form-match with alias surfaces treated as historical-by-construction
- ADR numbering collision: Lola’s tri-graph ‘ADR-22’ (in-memory delta 966082d0 via field-collapse workaround) vs Donna’s formal ADR-22 (CORS) — promoted tri-graph to ADR-23 as retro cleanup
- Delta 10-row cap on /context/ only surfaced during migration — added delta_limit query param
Lessons
- [global] Non-breaking parallel install is the default posture for schema changes. New layer coexists with old during transition; deprecation is a separate time-gated change after soak. Shipped Plan #6 zero-regression across 6 waves via this pattern.
- [global] Deferrals with documented gates are valid outputs. When exit criteria require measurement infrastructure that doesn’t exist yet (Phase 5 probe-set), scaffold-plus-wait beats ship-plus-hope. Name the gate in the deferral so it’s actionable when met.
- [global] Feature-flag scaffolding without measurement is worse than deferral. Half-shipped code adds maintenance surface without validation. Either land full implementation with validation, or defer explicitly with a gate.
- [project] Synthetic test fixtures belong in the service layer (valid_from_override on prism_entity/prism_transition), not hand-crafted Cypher per test — same mechanism unlocks Wave E migration tooling + smoke 14-18.
- [project] Coerce non-primitive props at write-time for Neo4j. Retro.lessons list[dict], DateTime objects, nested dicts — every non-primitive surfaces as a Cypher error if not coerced. Add _coerce_for_neo4j at the service boundary; coerce DateTime → ISO string at read boundary for Pydantic.
- [project] When an agent uses the ADR-in-memory workaround (per feedback_adr_field_collapse), next session must reconcile with the formal ADR table. Numbering coordination is a methodology responsibility, not to be punted.
- [project] Vibe Coder protocol is load-bearing for solo-operator AI-native workflows. Wave-boundary language + preserved alternatives + no sprint-metaphors kept Plan #6 shipping six waves in one focused session.
- [global] Deploy mechanism built during the first deploy pays off every subsequent deploy. Front-load boring-deploy work into wave 1; hiccups surface under low blast-radius before higher-stakes waves.

