merge: T109 schema 0014 — memories.event_id column

This commit is contained in:
Joseph Doherty
2026-04-27 05:01:17 -04:00
4 changed files with 91 additions and 4 deletions
@@ -0,0 +1,25 @@
-- 0014_phase45_schema.sql — Phase 4.5 Wave 2 schema bump (T109).
--
-- Two schema concerns are bundled into this migration:
--
-- 1. ``embeddings.memory_id`` FK should ideally carry ``ON DELETE CASCADE``
-- (T88 review nit). DEFERRED to Phase 5: ``embeddings`` rows are only ever
-- deleted when the parent ``memories`` row is deleted, and ``memories``
-- rows are never deleted today (memory hide is a soft flag; the surgical
-- ``deindex_event`` path operates on ``event_log`` and does NOT cascade
-- to projection rows). The CASCADE constraint therefore can't fire under
-- current usage — adding the SQLite table-rebuild dance (rename, recreate,
-- copy, drop, reindex) for a defensive constraint is unwarranted bloat
-- in a polish wave. Revisit during the broader Phase 5 migration cleanup
-- when other table reshapes make the rebuild worthwhile.
--
-- 2. Add ``memories.event_id`` (NULLABLE INTEGER, references ``event_log.id``)
-- so cross-chat search results can deep-link back to the originating
-- turn (foundation for T111). The column is nullable so historical
-- memory rows projected before 0014 ran continue to round-trip cleanly;
-- new rows are populated by the ``memory_written`` projector handler
-- from the projecting event's id. This is a pure additive change — no
-- backfill is performed. Older rows simply read NULL until/unless a
-- later migration backfills them; T111 surfaces are coded to accept
-- NULL gracefully (no deep-link rendered).
ALTER TABLE memories ADD COLUMN event_id INTEGER REFERENCES event_log(id);
+8 -2
View File
@@ -13,13 +13,18 @@ def _row_to_dict(conn: Connection, row: tuple) -> dict:
@on("memory_written")
def _apply_memory_written(conn: Connection, e: Event) -> None:
# T109 (schema 0014): persist the projecting event's id on the memory
# row so cross-chat search results can deep-link back to the
# originating turn (T111). Older memory rows projected before 0014
# ran read NULL here — the column is nullable for that reason.
p = e.payload
conn.execute(
"INSERT INTO memories ("
"owner_id, chat_id, scene_id, pov_summary, "
"witness_you, witness_host, witness_guest, "
"chat_clock_at, source, reliability, significance, pinned, auto_pinned"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
"chat_clock_at, source, reliability, significance, pinned, auto_pinned, "
"event_id"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
p["owner_id"],
p["chat_id"],
@@ -34,6 +39,7 @@ def _apply_memory_written(conn: Connection, e: Event) -> None:
int(p.get("significance", 1)),
int(p.get("pinned", 0)),
int(p.get("auto_pinned", 0)),
e.id,
),
)
+56
View File
@@ -586,3 +586,59 @@ def test_record_turn_memory_enqueues_embedding_job(tmp_path):
assert {job.memory_id for job in captured} == expected_ids
for job in captured:
assert job.text == "Both bots witness this beat."
# ---------------------------------------------------------------------------
# T109: memories.event_id deep-link column populated by the projector.
# ---------------------------------------------------------------------------
def test_memory_written_populates_event_id(tmp_path):
"""Schema 0014 added ``memories.event_id`` referencing ``event_log.id``.
The ``memory_written`` projector handler must populate the column with
the projecting event's id so T111 can deep-link cross-chat search hits
back to the originating turn.
"""
db = tmp_path / "t.db"
apply_migrations(db)
_seed_minimal(db)
with open_db(db) as conn:
result = record_turn_memory_for_present(
conn,
chat_id="chat_bot_a",
host_bot_id="bot_a",
guest_bot_id=None,
narrative_text="BotA shrugs.",
)
eid, mid = result["bot_a"]
assert eid > 0 and mid is not None
row = conn.execute(
"SELECT event_id FROM memories WHERE id = ?", (mid,)
).fetchone()
assert row is not None
assert row[0] == eid
def test_memory_event_id_column_is_nullable_for_backfill(tmp_path):
"""Backward compat: the ``event_id`` column is nullable so historical
memory rows projected before 0014 ran (or rows synthesised by tests
that bypass the projector) don't break the schema. A direct INSERT
omitting the column must succeed and read back NULL."""
db = tmp_path / "t.db"
apply_migrations(db)
_seed_minimal(db)
with open_db(db) as conn:
conn.execute(
"INSERT INTO memories ("
"owner_id, chat_id, pov_summary, "
"witness_you, witness_host, witness_guest"
") VALUES (?, ?, ?, ?, ?, ?)",
("bot_a", "chat_bot_a", "legacy row", 1, 1, 0),
)
row = conn.execute(
"SELECT event_id FROM memories WHERE pov_summary = 'legacy row'"
).fetchone()
assert row is not None
assert row[0] is None
+2 -2
View File
@@ -324,11 +324,11 @@ def test_get_scene_returns_none_for_missing(tmp_path):
assert active_scene(conn, "chat_missing") is None
def test_schema_version_after_migration_is_13(tmp_path):
def test_schema_version_after_migration_is_14(tmp_path):
db = tmp_path / "t.db"
apply_migrations(db)
with open_db(db) as conn:
row = conn.execute(
"SELECT value FROM meta WHERE key = 'schema_version'"
).fetchone()
assert int(row[0]) == 13
assert int(row[0]) == 14