Files
clawmates/migrations/0028_backfill_on_delete.sql
T
Omar Sobh e5e049921f migrations: 0028 — backfill ON DELETE clauses on legacy FKs
Retrofits the ON DELETE pattern learned after v1 shipped (CASCADE for
tenant-scoped children, SET NULL for historical references, RESTRICT where
the domain type is non-Option) onto tables from 0001-0006 and 0026. Two
deliberate exceptions kept as NO ACTION: audit_log.workspace_id (audit is
append-only and must outlive workspace deletes) and thread_messages.from_agent
(history stays attributable via agents.deleted_at). Two stay NOT NULL as
RESTRICT (agents.managed_by, installed_skills.installed_by) because the
Rust domain type is UserId, not Option<UserId>.

Also includes the previously-orphan .sqlx cache for the usage_events query
in cm-runtime/tests/run_loop.rs, which needed re-recording after the FK
changes touched the metadata.
2026-07-05 18:48:48 -07:00

202 lines
9.7 KiB
SQL

-- Backfill ON DELETE clauses on FKs from 0001-0006 and 0026. The pattern
-- (CASCADE for tenant-scoped children, SET NULL for historical references)
-- was learned after v1 shipped and applied consistently from 0010 onward;
-- this migration retrofits the earlier tables so DELETEs succeed instead of
-- either failing with a FK violation or leaving orphans.
--
-- Two FKs stay as NO ACTION deliberately:
-- * audit_log.workspace_id — audit is append-only and must outlive any
-- workspace delete (the trigger blocks UPDATE/DELETE on audit rows).
-- * thread_messages.from_agent — history stays attributable via the
-- agents.deleted_at soft-delete already in schema.
--
-- Two FKs stay NOT NULL and become explicit RESTRICT rather than SET NULL:
-- * agents.managed_by — the Rust domain type is `UserId` (not Option).
-- Making it nullable would ripple through cm-domain/cm-db/cm-api/tests.
-- RESTRICT preserves current behavior (delete blocked) but documents it.
-- * installed_skills.installed_by — same reasoning.
-- To hard-delete a user in either case, reassign or hard-delete the
-- dependent rows first — which matches real org-ownership semantics.
-- ---- 0001_init: workspaces cascade -----------------------------------------
ALTER TABLE users
DROP CONSTRAINT IF EXISTS users_workspace_id_fkey,
ADD CONSTRAINT users_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE;
ALTER TABLE agents
DROP CONSTRAINT IF EXISTS agents_workspace_id_fkey,
ADD CONSTRAINT agents_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE;
ALTER TABLE agents
DROP CONSTRAINT IF EXISTS agents_managed_by_fkey,
ADD CONSTRAINT agents_managed_by_fkey
FOREIGN KEY (managed_by) REFERENCES users (id) ON DELETE RESTRICT;
ALTER TABLE sessions
DROP CONSTRAINT IF EXISTS sessions_agent_id_fkey,
ADD CONSTRAINT sessions_agent_id_fkey
FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS sessions_workspace_id_fkey,
ADD CONSTRAINT sessions_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE;
ALTER TABLE messages
DROP CONSTRAINT IF EXISTS messages_session_id_fkey,
ADD CONSTRAINT messages_session_id_fkey
FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE;
ALTER TABLE steps
DROP CONSTRAINT IF EXISTS steps_message_id_fkey,
ADD CONSTRAINT steps_message_id_fkey
FOREIGN KEY (message_id) REFERENCES messages (id) ON DELETE CASCADE;
ALTER TABLE agent_runs
DROP CONSTRAINT IF EXISTS agent_runs_session_id_fkey,
ADD CONSTRAINT agent_runs_session_id_fkey
FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE;
ALTER TABLE approvals
DROP CONSTRAINT IF EXISTS approvals_workspace_id_fkey,
ADD CONSTRAINT approvals_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS approvals_run_id_fkey,
ADD CONSTRAINT approvals_run_id_fkey
FOREIGN KEY (run_id) REFERENCES agent_runs (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS approvals_requested_by_agent_fkey,
ADD CONSTRAINT approvals_requested_by_agent_fkey
FOREIGN KEY (requested_by_agent) REFERENCES agents (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS approvals_decided_by_fkey,
ADD CONSTRAINT approvals_decided_by_fkey
FOREIGN KEY (decided_by) REFERENCES users (id) ON DELETE SET NULL;
-- Single-use execution grants: deleting the approval should drop the grant.
ALTER TABLE execution_grants
DROP CONSTRAINT IF EXISTS execution_grants_approval_id_fkey,
ADD CONSTRAINT execution_grants_approval_id_fkey
FOREIGN KEY (approval_id) REFERENCES approvals (id) ON DELETE CASCADE;
-- audit_log.workspace_id — intentionally left as NO ACTION.
ALTER TABLE skills
DROP CONSTRAINT IF EXISTS skills_workspace_id_fkey,
ADD CONSTRAINT skills_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE;
ALTER TABLE installed_skills
DROP CONSTRAINT IF EXISTS installed_skills_skill_id_fkey,
ADD CONSTRAINT installed_skills_skill_id_fkey
FOREIGN KEY (skill_id) REFERENCES skills (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS installed_skills_installed_by_fkey,
ADD CONSTRAINT installed_skills_installed_by_fkey
FOREIGN KEY (installed_by) REFERENCES users (id) ON DELETE RESTRICT;
ALTER TABLE secrets
DROP CONSTRAINT IF EXISTS secrets_workspace_id_fkey,
ADD CONSTRAINT secrets_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE;
ALTER TABLE app_connections
DROP CONSTRAINT IF EXISTS app_connections_workspace_id_fkey,
ADD CONSTRAINT app_connections_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS app_connections_agent_id_fkey,
ADD CONSTRAINT app_connections_agent_id_fkey
FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS app_connections_secret_ref_fkey,
ADD CONSTRAINT app_connections_secret_ref_fkey
FOREIGN KEY (secret_ref) REFERENCES secrets (id) ON DELETE SET NULL;
ALTER TABLE threads
DROP CONSTRAINT IF EXISTS threads_workspace_id_fkey,
ADD CONSTRAINT threads_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE;
ALTER TABLE thread_participants
DROP CONSTRAINT IF EXISTS thread_participants_agent_id_fkey,
ADD CONSTRAINT thread_participants_agent_id_fkey
FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE CASCADE;
-- thread_messages.thread_id → CASCADE; from_agent stays NO ACTION.
ALTER TABLE thread_messages
DROP CONSTRAINT IF EXISTS thread_messages_thread_id_fkey,
ADD CONSTRAINT thread_messages_thread_id_fkey
FOREIGN KEY (thread_id) REFERENCES threads (id) ON DELETE CASCADE;
ALTER TABLE file_nodes
DROP CONSTRAINT IF EXISTS file_nodes_workspace_id_fkey,
ADD CONSTRAINT file_nodes_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS file_nodes_agent_id_fkey,
ADD CONSTRAINT file_nodes_agent_id_fkey
FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE CASCADE;
ALTER TABLE credit_lots
DROP CONSTRAINT IF EXISTS credit_lots_workspace_id_fkey,
ADD CONSTRAINT credit_lots_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE;
ALTER TABLE usage_events
DROP CONSTRAINT IF EXISTS usage_events_workspace_id_fkey,
ADD CONSTRAINT usage_events_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS usage_events_agent_id_fkey,
ADD CONSTRAINT usage_events_agent_id_fkey
FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE SET NULL,
DROP CONSTRAINT IF EXISTS usage_events_run_id_fkey,
ADD CONSTRAINT usage_events_run_id_fkey
FOREIGN KEY (run_id) REFERENCES agent_runs (id) ON DELETE SET NULL;
-- ---- 0002_auth_sessions ---------------------------------------------------
ALTER TABLE auth_sessions
DROP CONSTRAINT IF EXISTS auth_sessions_user_id_fkey,
ADD CONSTRAINT auth_sessions_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE;
-- ---- 0003_run_events ------------------------------------------------------
ALTER TABLE run_events
DROP CONSTRAINT IF EXISTS run_events_run_id_fkey,
ADD CONSTRAINT run_events_run_id_fkey
FOREIGN KEY (run_id) REFERENCES agent_runs (id) ON DELETE CASCADE;
-- ---- 0004_outbox ----------------------------------------------------------
ALTER TABLE outbox
DROP CONSTRAINT IF EXISTS outbox_workspace_id_fkey,
ADD CONSTRAINT outbox_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS outbox_agent_id_fkey,
ADD CONSTRAINT outbox_agent_id_fkey
FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE CASCADE;
-- ---- 0005_oauth_states ----------------------------------------------------
ALTER TABLE oauth_states
DROP CONSTRAINT IF EXISTS oauth_states_workspace_id_fkey,
ADD CONSTRAINT oauth_states_workspace_id_fkey
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS oauth_states_user_id_fkey,
ADD CONSTRAINT oauth_states_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
DROP CONSTRAINT IF EXISTS oauth_states_agent_id_fkey,
ADD CONSTRAINT oauth_states_agent_id_fkey
FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE CASCADE;
-- ---- 0006_promo_codes -----------------------------------------------------
ALTER TABLE promo_codes
DROP CONSTRAINT IF EXISTS promo_codes_redeemed_by_fkey,
ADD CONSTRAINT promo_codes_redeemed_by_fkey
FOREIGN KEY (redeemed_by) REFERENCES workspaces (id) ON DELETE SET NULL;
-- ---- 0026_group_rooms -----------------------------------------------------
-- The threads.created_by / thread_participants.added_by columns were added
-- nullable by 0026, so SET NULL is legal.
ALTER TABLE threads
DROP CONSTRAINT IF EXISTS threads_created_by_fkey,
ADD CONSTRAINT threads_created_by_fkey
FOREIGN KEY (created_by) REFERENCES agents (id) ON DELETE SET NULL;
ALTER TABLE thread_participants
DROP CONSTRAINT IF EXISTS thread_participants_added_by_fkey,
ADD CONSTRAINT thread_participants_added_by_fkey
FOREIGN KEY (added_by) REFERENCES agents (id) ON DELETE SET NULL;