74 lines
3.4 KiB
MySQL
74 lines
3.4 KiB
MySQL
|
|
-- 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;
|