From 84b32e35ff3eee25a7963d7f8964f4071c07d620 Mon Sep 17 00:00:00 2001 From: yushen Date: Mon, 15 Jun 2026 16:05:11 +0800 Subject: [PATCH] MUL-3284: drop DB FKs/cascade from runtime_profile migration (review fix) Per review (house rule: no new database foreign keys / cascades; relational integrity lives in the application layer): - runtime_profile.workspace_id: drop REFERENCES workspace ON DELETE CASCADE -> plain UUID NOT NULL. - runtime_profile.created_by: drop REFERENCES "user" ON DELETE SET NULL -> plain UUID. - agent_runtime.profile_id: drop REFERENCES runtime_profile ON DELETE CASCADE -> plain UUID. CHECK constraints, UNIQUE (workspace_id, display_name), the workspace index, and the partial unique index agent_runtime_workspace_daemon_profile_key are unchanged. The legacy UNIQUE (workspace_id, daemon_id, provider) constraint remains untouched. Behavioral consequence: the database no longer auto-removes a profile's agent_runtime instance rows on profile delete. That cleanup moves into PR2's profile-delete path. Up-migration comments document this; down-migration comment no longer references FKs/cascade. Re-verified on Postgres 17: migrate up applies 120; no FK constraints exist on the new columns; partial index still blocks dup (ws,daemon,profile_id); CHECK and display_name uniqueness still reject bad input; deleting a profile now leaves the runtime row orphaned (proving cascade is gone); down/up round-trip clean with the legacy constraint intact. Co-authored-by: multica-agent --- .../migrations/120_runtime_profile.down.sql | 7 ++++--- server/migrations/120_runtime_profile.up.sql | 21 +++++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/server/migrations/120_runtime_profile.down.sql b/server/migrations/120_runtime_profile.down.sql index 245f37ab5..f637a70aa 100644 --- a/server/migrations/120_runtime_profile.down.sql +++ b/server/migrations/120_runtime_profile.down.sql @@ -1,6 +1,7 @@ --- Reverse 120_runtime_profile.up.sql. Order matters: drop the partial index --- and the profile_id column (which carries the FK into runtime_profile) before --- dropping the table the FK points at. +-- Reverse 120_runtime_profile.up.sql. No DB foreign keys were added by the up +-- migration (relationships are enforced in the application layer), so ordering +-- here only needs to drop dependent index/column before the table they live +-- alongside. DROP INDEX IF EXISTS agent_runtime_workspace_daemon_profile_key; diff --git a/server/migrations/120_runtime_profile.up.sql b/server/migrations/120_runtime_profile.up.sql index bdf12b18d..8f24b403c 100644 --- a/server/migrations/120_runtime_profile.up.sql +++ b/server/migrations/120_runtime_profile.up.sql @@ -5,6 +5,14 @@ -- `agent_runtime` a stable `profile_id` so the same daemon can host multiple -- runtimes of the same protocol family. -- +-- Referential integrity policy (house rule): this migration does NOT add any +-- new database foreign keys or ON DELETE cascades. `workspace_id`, +-- `created_by` and `agent_runtime.profile_id` are plain UUID columns; the +-- relationships they model are enforced in the application layer, not by the +-- database. In particular, deleting a runtime_profile must clean up its +-- associated agent_runtime instance rows in application code (PR2's profile +-- delete path) — the database will no longer cascade that for us. +-- -- Scope is deliberately additive only: -- * The legacy `UNIQUE (workspace_id, daemon_id, provider)` constraint on -- agent_runtime is left INTACT so the existing registration upsert @@ -23,7 +31,9 @@ CREATE TABLE runtime_profile ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE, + -- Owning workspace. Plain UUID; integrity (and cleanup on workspace + -- delete) is enforced in the application layer, not by a DB FK. + workspace_id UUID NOT NULL, display_name TEXT NOT NULL, -- protocol_family must stay in lockstep with the agent.New() switch in -- server/pkg/agent/agent.go. A profile may only be based on a backend @@ -47,7 +57,8 @@ CREATE TABLE runtime_profile ( description TEXT, fixed_args JSONB NOT NULL DEFAULT '[]', visibility TEXT NOT NULL DEFAULT 'workspace' CHECK (visibility IN ('workspace', 'private')), - created_by UUID REFERENCES "user"(id) ON DELETE SET NULL, + -- Creating user. Plain UUID, nullable; no DB FK. + created_by UUID, enabled BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), @@ -58,9 +69,11 @@ CREATE INDEX idx_runtime_profile_workspace ON runtime_profile(workspace_id); -- Stable profile identity on the runtime instance row. NULL = built-in runtime -- (registered the legacy way); non-NULL = a registered instance of a custom --- profile. ON DELETE CASCADE: removing a profile tears down its runtime rows. +-- profile. Plain UUID with no DB FK: the link to runtime_profile, and the +-- cleanup of these rows when a profile is deleted, is the application layer's +-- responsibility (PR2). ALTER TABLE agent_runtime - ADD COLUMN profile_id UUID REFERENCES runtime_profile(id) ON DELETE CASCADE; + ADD COLUMN profile_id UUID; -- Custom-runtime uniqueness: one instance per (workspace, daemon, profile). -- Partial so it never touches built-in rows (profile_id IS NULL) and never