Spaces:
Sleeping
Sleeping
File size: 5,549 Bytes
3493993 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | -- AI Copilot: bounded durable plans and action results.
-- Apply after projects/0004_ai_studio.sql. Production migration remains explicit.
begin;
create table if not exists copilot_runs (
id text primary key default gen_random_uuid()::text,
workspace_id text not null references workspaces(id) on delete restrict,
user_id text not null references users(id) on delete restrict,
project_id text references projects(id) on delete restrict,
idempotency_key text not null check (char_length(idempotency_key) between 1 and 255),
request_fingerprint text not null check (char_length(request_fingerprint) = 64),
request_text text not null check (char_length(request_text) between 1 and 4000),
context jsonb not null check (jsonb_typeof(context) = 'object'),
plan jsonb not null check (jsonb_typeof(plan) = 'object'),
status text not null check (
status in ('plan_ready','blocked','executing','completed','partial','failed','cancelled')
),
current_action_id text,
results jsonb not null default '[]'::jsonb check (jsonb_typeof(results) = 'array'),
summary text,
error_code text,
error_message text,
confirmed_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
completed_at timestamptz,
constraint uq_copilot_run_workspace_idempotency unique (workspace_id, idempotency_key)
);
create index if not exists ix_copilot_runs_workspace_created
on copilot_runs(workspace_id, created_at desc);
create index if not exists ix_copilot_runs_workspace_status
on copilot_runs(workspace_id, status);
create index if not exists ix_copilot_runs_project_created
on copilot_runs(project_id, created_at desc);
drop trigger if exists mediarouter_copilot_touch_updated_at on copilot_runs;
create trigger mediarouter_copilot_touch_updated_at
before update on copilot_runs
for each row execute function mediarouter_tenant_touch_updated_at();
create or replace function mediarouter_assert_copilot_run_ownership()
returns trigger language plpgsql as $$
declare project_workspace text;
begin
if tg_op = 'UPDATE' and (
new.workspace_id is distinct from old.workspace_id
or new.user_id is distinct from old.user_id
or new.project_id is distinct from old.project_id
or new.idempotency_key is distinct from old.idempotency_key
or new.request_fingerprint is distinct from old.request_fingerprint
or new.request_text is distinct from old.request_text
or new.context is distinct from old.context
or new.plan is distinct from old.plan
or new.created_at is distinct from old.created_at
) then
raise exception 'copilot run identity fields are immutable' using errcode = '23514';
end if;
if new.context->>'workspace_id' is distinct from new.workspace_id then
raise exception 'copilot context workspace does not match its run' using errcode = '23514';
end if;
if new.project_id is not null then
select workspace_id into project_workspace from projects where id = new.project_id;
if project_workspace is null or project_workspace is distinct from new.workspace_id then
raise exception 'copilot project must belong to its workspace' using errcode = '23503';
end if;
end if;
if not exists (
select 1 from workspace_memberships m
where m.workspace_id = new.workspace_id
and m.user_id = new.user_id
and m.status = 'active'
) then
raise exception 'copilot run requires active workspace membership' using errcode = '23503';
end if;
if new.status in ('completed','partial','failed','cancelled') and new.completed_at is null then
raise exception 'terminal copilot runs require completed_at' using errcode = '23514';
end if;
return new;
end;
$$;
drop trigger if exists mediarouter_copilot_run_ownership on copilot_runs;
create trigger mediarouter_copilot_run_ownership
before insert or update on copilot_runs
for each row execute function mediarouter_assert_copilot_run_ownership();
alter table copilot_runs enable row level security;
alter table copilot_runs force row level security;
drop policy if exists copilot_runs_select on copilot_runs;
create policy copilot_runs_select on copilot_runs for select using (
workspace_id = current_setting('app.workspace_id', true)
and exists (
select 1 from workspace_memberships m
where m.workspace_id = copilot_runs.workspace_id
and m.user_id = current_setting('app.user_id', true)
and m.status = 'active'
)
);
drop policy if exists copilot_runs_insert on copilot_runs;
create policy copilot_runs_insert on copilot_runs for insert with check (
workspace_id = current_setting('app.workspace_id', true)
and user_id = current_setting('app.user_id', true)
and exists (
select 1 from workspace_memberships m
where m.workspace_id = copilot_runs.workspace_id
and m.user_id = current_setting('app.user_id', true)
and m.status = 'active'
and m.role <> 'viewer'
)
);
drop policy if exists copilot_runs_update on copilot_runs;
create policy copilot_runs_update on copilot_runs for update using (
workspace_id = current_setting('app.workspace_id', true)
and user_id = current_setting('app.user_id', true)
and exists (
select 1 from workspace_memberships m
where m.workspace_id = copilot_runs.workspace_id
and m.user_id = current_setting('app.user_id', true)
and m.status = 'active'
and m.role <> 'viewer'
)
) with check (
workspace_id = current_setting('app.workspace_id', true)
and user_id = current_setting('app.user_id', true)
);
commit;
|