merge: T69 bot_reset purges orphaned 'you' activity rows
This commit is contained in:
+11
-2
@@ -48,6 +48,17 @@ def _apply_bot_reset(conn: Connection, e: Event) -> None:
|
|||||||
"SELECT id FROM chats WHERE host_bot_id = ?", (bot_id,)
|
"SELECT id FROM chats WHERE host_bot_id = ?", (bot_id,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
]
|
]
|
||||||
|
# T69: purge orphaned "you" activity rows pointing at containers in this
|
||||||
|
# bot's chats BEFORE the containers/chats themselves are deleted, otherwise
|
||||||
|
# the subqueries find nothing and the FK constraint on activity.container_id
|
||||||
|
# blocks the container delete.
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM activity WHERE entity_id = 'you' "
|
||||||
|
"AND container_id IN (SELECT id FROM containers WHERE chat_id IN ("
|
||||||
|
" SELECT id FROM chats WHERE host_bot_id = ?"
|
||||||
|
"))",
|
||||||
|
(bot_id,),
|
||||||
|
)
|
||||||
for chat_id in chat_ids:
|
for chat_id in chat_ids:
|
||||||
conn.execute("DELETE FROM scenes WHERE chat_id = ?", (chat_id,))
|
conn.execute("DELETE FROM scenes WHERE chat_id = ?", (chat_id,))
|
||||||
conn.execute("DELETE FROM containers WHERE chat_id = ?", (chat_id,))
|
conn.execute("DELETE FROM containers WHERE chat_id = ?", (chat_id,))
|
||||||
@@ -74,8 +85,6 @@ def _apply_bot_reset(conn: Connection, e: Event) -> None:
|
|||||||
(bot_id,),
|
(bot_id,),
|
||||||
)
|
)
|
||||||
# NOTE: bots row itself is preserved (identity, kickoff_prose intact).
|
# NOTE: bots row itself is preserved (identity, kickoff_prose intact).
|
||||||
# NOTE: "you" activity (entity_id="you") may linger from a deleted chat;
|
|
||||||
# acceptable for v1 — Phase 1.5 cleanup if needed.
|
|
||||||
|
|
||||||
|
|
||||||
def get_bot(conn: Connection, bot_id: str) -> dict | None:
|
def get_bot(conn: Connection, bot_id: str) -> dict | None:
|
||||||
|
|||||||
@@ -292,6 +292,189 @@ def test_reset_clears_guest_reference_in_other_chats(client, tmp_path):
|
|||||||
).fetchone()[0] == 1
|
).fetchone()[0] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_reset_purges_orphaned_you_activity_rows(client, tmp_path):
|
||||||
|
"""T69: when a bot's chats are deleted, "you" activity rows tied to those
|
||||||
|
chats' containers should also be purged (otherwise they linger orphaned)."""
|
||||||
|
db = tmp_path / "test.db"
|
||||||
|
with open_db(db) as conn:
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="bot_authored",
|
||||||
|
payload={
|
||||||
|
"id": "bot_a",
|
||||||
|
"name": "BotA",
|
||||||
|
"persona": "thoughtful",
|
||||||
|
"voice_samples": [],
|
||||||
|
"traits": [],
|
||||||
|
"backstory": "",
|
||||||
|
"initial_relationship_to_you": "coworker",
|
||||||
|
"kickoff_prose": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="chat_created",
|
||||||
|
payload={
|
||||||
|
"id": "chat_bot_a",
|
||||||
|
"host_bot_id": "bot_a",
|
||||||
|
"initial_time": "2026-04-26T20:00:00+00:00",
|
||||||
|
"narrative_anchor": "Day 1",
|
||||||
|
"weather": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="container_created",
|
||||||
|
payload={
|
||||||
|
"chat_id": "chat_bot_a",
|
||||||
|
"name": "office",
|
||||||
|
"type": "workplace",
|
||||||
|
"properties": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="activity_change",
|
||||||
|
payload={
|
||||||
|
"entity_id": "you",
|
||||||
|
"container_id": 1,
|
||||||
|
"posture": "standing",
|
||||||
|
"action": {"verb": "watching"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
project(conn)
|
||||||
|
# Sanity: the "you" activity row exists and points at the container.
|
||||||
|
assert conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM activity WHERE entity_id = 'you'"
|
||||||
|
).fetchone()[0] == 1
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
"/bots/bot_a/reset",
|
||||||
|
data={"confirm_name": "BotA"},
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
assert response.status_code == 303
|
||||||
|
|
||||||
|
with open_db(db) as conn:
|
||||||
|
# The orphaned "you" activity row tied to bot_a's purged container is gone.
|
||||||
|
assert conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM activity WHERE entity_id = 'you'"
|
||||||
|
).fetchone()[0] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_reset_does_not_purge_you_activity_in_other_chats(client, tmp_path):
|
||||||
|
"""T69: resetting bot_a must leave a "you" activity row pointing at
|
||||||
|
bot_b's container intact — only orphans from the reset bot's chats go."""
|
||||||
|
db = tmp_path / "test.db"
|
||||||
|
with open_db(db) as conn:
|
||||||
|
# bot_a + its chat + container.
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="bot_authored",
|
||||||
|
payload={
|
||||||
|
"id": "bot_a",
|
||||||
|
"name": "BotA",
|
||||||
|
"persona": "thoughtful",
|
||||||
|
"voice_samples": [],
|
||||||
|
"traits": [],
|
||||||
|
"backstory": "",
|
||||||
|
"initial_relationship_to_you": "coworker",
|
||||||
|
"kickoff_prose": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="chat_created",
|
||||||
|
payload={
|
||||||
|
"id": "chat_bot_a",
|
||||||
|
"host_bot_id": "bot_a",
|
||||||
|
"initial_time": "2026-04-26T20:00:00+00:00",
|
||||||
|
"narrative_anchor": "Day 1",
|
||||||
|
"weather": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="container_created",
|
||||||
|
payload={
|
||||||
|
"chat_id": "chat_bot_a",
|
||||||
|
"name": "office",
|
||||||
|
"type": "workplace",
|
||||||
|
"properties": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# bot_b + its chat + container.
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="bot_authored",
|
||||||
|
payload={
|
||||||
|
"id": "bot_b",
|
||||||
|
"name": "BotB",
|
||||||
|
"persona": "curious",
|
||||||
|
"voice_samples": [],
|
||||||
|
"traits": [],
|
||||||
|
"backstory": "",
|
||||||
|
"initial_relationship_to_you": "friend",
|
||||||
|
"kickoff_prose": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="chat_created",
|
||||||
|
payload={
|
||||||
|
"id": "chat_bot_b",
|
||||||
|
"host_bot_id": "bot_b",
|
||||||
|
"initial_time": "2026-04-26T20:00:00+00:00",
|
||||||
|
"narrative_anchor": "Day 1",
|
||||||
|
"weather": "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="container_created",
|
||||||
|
payload={
|
||||||
|
"chat_id": "chat_bot_b",
|
||||||
|
"name": "kitchen",
|
||||||
|
"type": "home",
|
||||||
|
"properties": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# The activity table is keyed on entity_id (PRIMARY KEY), so only one
|
||||||
|
# "you" row exists at a time. Point it at bot_b's container so reset of
|
||||||
|
# bot_a should NOT touch it.
|
||||||
|
append_event(
|
||||||
|
conn,
|
||||||
|
kind="activity_change",
|
||||||
|
payload={
|
||||||
|
"entity_id": "you",
|
||||||
|
"container_id": 2, # kitchen, in chat_bot_b
|
||||||
|
"posture": "sitting",
|
||||||
|
"action": {"verb": "reading"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
project(conn)
|
||||||
|
# Sanity: the "you" activity row is in bot_b's container.
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT container_id FROM activity WHERE entity_id = 'you'"
|
||||||
|
).fetchone()
|
||||||
|
assert row is not None and row[0] == 2
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
"/bots/bot_a/reset",
|
||||||
|
data={"confirm_name": "BotA"},
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
assert response.status_code == 303
|
||||||
|
|
||||||
|
with open_db(db) as conn:
|
||||||
|
# The "you" activity in bot_b's container is preserved.
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT container_id FROM activity WHERE entity_id = 'you'"
|
||||||
|
).fetchone()
|
||||||
|
assert row is not None
|
||||||
|
assert row[0] == 2
|
||||||
|
|
||||||
|
|
||||||
def test_reset_purges_guest_memories_from_other_chats(client, tmp_path):
|
def test_reset_purges_guest_memories_from_other_chats(client, tmp_path):
|
||||||
db = tmp_path / "test.db"
|
db = tmp_path / "test.db"
|
||||||
_seed_two_bots_with_guest_link(
|
_seed_two_bots_with_guest_link(
|
||||||
|
|||||||
Reference in New Issue
Block a user