Skip to main content
Phase: Plan #6 — SPEC-020 Tri-Graph Knowledge Representation · Status: closed · Filed 2026-04-20

What 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.
Last modified on April 20, 2026