lazyBoy/migrations/019_memory_scope_on_change.sql

74 lines
3.4 KiB
MySQL
Raw Normal View History

2026-09-09 06:41:09 +00:00
-- Deleting a conversation that holds a remembered message used to fail.
--
-- `DELETE FROM threads` cascades in two steps that Postgres runs one after the
-- other: the thread's messages are removed first, and only then is
-- memory_items.session_id set to NULL. That second step fires this guard while
-- source_message_id still points at a message that is already gone, so the
-- guard raised "memory message is outside agent scope" and the delete rolled
-- back — the human saw a conversation that could not be removed.
--
-- The guard now checks only the references that this write actually changes.
-- A referential action that clears one column leaves the others as they were,
-- and those were validated when they were written. An INSERT, or a move to
-- another agent/space/user, still validates everything; pointing the item at a
-- different session re-checks that its run and message belong to that session.
-- The same race exists for runs (also removed with the thread), so clearing
-- session_id never re-checks the run or message.
CREATE OR REPLACE FUNCTION validate_memory_item_scope() RETURNS trigger AS $$
DECLARE
moved BOOLEAN := TG_OP = 'INSERT'
OR NEW.bot_id IS DISTINCT FROM OLD.bot_id
OR NEW.space_id IS DISTINCT FROM OLD.space_id
OR NEW.user_id IS DISTINCT FROM OLD.user_id;
resessioned BOOLEAN := TG_OP = 'UPDATE'
AND NEW.session_id IS NOT NULL
AND NEW.session_id IS DISTINCT FROM OLD.session_id;
BEGIN
IF NEW.session_id IS NOT NULL
AND (moved OR resessioned)
AND NOT EXISTS (
SELECT 1 FROM threads t
WHERE t.id = NEW.session_id AND (
(t.room_id IS NULL AND t.bot_id = NEW.bot_id)
OR EXISTS (
SELECT 1 FROM rooms room JOIN room_members member ON member.room_id=room.id
WHERE room.id=t.room_id AND member.bot_id=NEW.bot_id
AND room.space_id=NEW.space_id AND room.user_id=NEW.user_id
)
)
AND t.space_id = NEW.space_id AND t.user_id = NEW.user_id
) THEN
RAISE EXCEPTION 'memory session is outside agent scope';
END IF;
IF NEW.source_run_id IS NOT NULL
AND (moved OR resessioned OR NEW.source_run_id IS DISTINCT FROM OLD.source_run_id)
AND NOT EXISTS (
SELECT 1 FROM runs r
WHERE r.id = NEW.source_run_id AND r.bot_id = NEW.bot_id
AND r.space_id = NEW.space_id AND r.user_id = NEW.user_id
AND (NEW.session_id IS NULL OR r.thread_id = NEW.session_id)
) THEN
RAISE EXCEPTION 'memory run is outside agent scope';
END IF;
IF NEW.source_message_id IS NOT NULL
AND (moved OR resessioned OR NEW.source_message_id IS DISTINCT FROM OLD.source_message_id)
AND NOT EXISTS (
SELECT 1 FROM messages m
JOIN threads t ON t.id = m.thread_id
WHERE m.id = NEW.source_message_id AND (
(t.room_id IS NULL AND t.bot_id = NEW.bot_id)
OR EXISTS (
SELECT 1 FROM rooms room JOIN room_members member ON member.room_id=room.id
WHERE room.id=t.room_id AND member.bot_id=NEW.bot_id
AND room.space_id=NEW.space_id AND room.user_id=NEW.user_id
)
)
AND t.space_id = NEW.space_id AND t.user_id = NEW.user_id
AND (NEW.session_id IS NULL OR t.id = NEW.session_id)
) THEN
RAISE EXCEPTION 'memory message is outside agent scope';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;