diff --git a/supabase/migrations/001_teams.sql b/supabase/migrations/001_teams.sql new file mode 100644 index 00000000..2cdea56b --- /dev/null +++ b/supabase/migrations/001_teams.sql @@ -0,0 +1,44 @@ +-- 001_teams.sql — Core team infrastructure. +-- +-- Creates teams and team_members tables with RLS policies. +-- Must be run first — other tables reference teams. + +-- Teams +create table if not exists teams ( + id uuid primary key default gen_random_uuid(), + name text not null, + slug text not null unique, + created_at timestamptz default now() +); + +-- Team membership +create table if not exists team_members ( + team_id uuid references teams(id) on delete cascade, + user_id uuid references auth.users(id) on delete cascade, + role text not null default 'member' check (role in ('owner', 'admin', 'member')), + primary key (team_id, user_id) +); + +-- RLS for teams +alter table teams enable row level security; + +create policy "team_members_read_team" on teams + for select using ( + id in (select team_id from team_members where user_id = auth.uid()) + ); + +-- RLS for team_members +alter table team_members enable row level security; + +create policy "members_read_own_team" on team_members + for select using ( + team_id in (select team_id from team_members where user_id = auth.uid()) + ); + +create policy "admins_manage_members" on team_members + for all using ( + team_id in ( + select team_id from team_members + where user_id = auth.uid() and role in ('owner', 'admin') + ) + ); diff --git a/supabase/migrations/002_eval_runs.sql b/supabase/migrations/002_eval_runs.sql new file mode 100644 index 00000000..e8ae8bf5 --- /dev/null +++ b/supabase/migrations/002_eval_runs.sql @@ -0,0 +1,71 @@ +-- 002_eval_runs.sql — Eval result storage. +-- +-- Mirrors EvalResult from test/helpers/eval-store.ts. +-- Supports both gstack's native eval format and the universal +-- adapter format (any language pushes JSON results). + +create table if not exists eval_runs ( + id uuid primary key default gen_random_uuid(), + team_id uuid references teams(id) not null, + user_id uuid references auth.users(id), + repo_slug text not null, + hostname text not null default '', + + -- Eval metadata + schema_version int not null default 1, + version text not null default '', + branch text not null default '', + git_sha text not null default '', + timestamp timestamptz not null default now(), + tier text not null default 'e2e', + + -- Summary stats + total_tests int not null default 0, + passed int not null default 0, + failed int not null default 0, + total_cost_usd numeric(10,4) not null default 0, + total_duration_ms int not null default 0, + + -- Universal format fields (adapter mode) + label text, -- e.g. "dev_fix-terseness_standard" + prompt_sha text, -- SHA of prompt source files + by_category jsonb, -- { "post_generation": { passed: 16, total: 17 } } + costs jsonb, -- [{ model, calls, input_tokens, output_tokens }] + + -- Full test results (transcripts stripped for team sync) + tests jsonb not null default '[]'::jsonb, + + created_at timestamptz default now() +); + +-- Indexes for common queries +create index if not exists idx_eval_runs_team on eval_runs(team_id); +create index if not exists idx_eval_runs_repo on eval_runs(team_id, repo_slug); +create index if not exists idx_eval_runs_branch on eval_runs(team_id, branch); +create index if not exists idx_eval_runs_timestamp on eval_runs(team_id, timestamp desc); +create index if not exists idx_eval_runs_label on eval_runs(team_id, label) where label is not null; + +-- Upsert natural key: timestamp + hostname + repo_slug (idempotent pushes) +create unique index if not exists idx_eval_runs_natural_key + on eval_runs(team_id, timestamp, hostname, repo_slug); + +-- RLS +alter table eval_runs enable row level security; + +create policy "team_read" on eval_runs + for select using ( + team_id in (select team_id from team_members where user_id = auth.uid()) + ); + +create policy "team_insert" on eval_runs + for insert with check ( + team_id in (select team_id from team_members where user_id = auth.uid()) + ); + +create policy "admin_delete" on eval_runs + for delete using ( + team_id in ( + select team_id from team_members + where user_id = auth.uid() and role in ('owner', 'admin') + ) + ); diff --git a/supabase/migrations/003_data_tables.sql b/supabase/migrations/003_data_tables.sql new file mode 100644 index 00000000..22dda92e --- /dev/null +++ b/supabase/migrations/003_data_tables.sql @@ -0,0 +1,156 @@ +-- 003_data_tables.sql — Retro, QA, ship, greptile, and transcript tables. + +-- Retro snapshots +create table if not exists retro_snapshots ( + id uuid primary key default gen_random_uuid(), + team_id uuid references teams(id) not null, + repo_slug text not null, + user_id uuid references auth.users(id), + date date not null, + window text not null default '7d', + metrics jsonb not null default '{}'::jsonb, + authors jsonb not null default '[]'::jsonb, + version_range jsonb, + streak_days int, + tweetable text, + greptile jsonb, + backlog jsonb, + created_at timestamptz default now() +); + +create index if not exists idx_retro_team on retro_snapshots(team_id); +create index if not exists idx_retro_date on retro_snapshots(team_id, date desc); +create unique index if not exists idx_retro_natural_key + on retro_snapshots(team_id, repo_slug, date, user_id); + +-- QA reports +create table if not exists qa_reports ( + id uuid primary key default gen_random_uuid(), + team_id uuid references teams(id) not null, + repo_slug text not null, + user_id uuid references auth.users(id), + url text not null, + mode text not null default 'full', + health_score numeric(5,2), + issues jsonb, + category_scores jsonb, + report_markdown text, + created_at timestamptz default now() +); + +create index if not exists idx_qa_team on qa_reports(team_id); +create index if not exists idx_qa_repo on qa_reports(team_id, repo_slug); + +-- Ship logs +create table if not exists ship_logs ( + id uuid primary key default gen_random_uuid(), + team_id uuid references teams(id) not null, + repo_slug text not null, + user_id uuid references auth.users(id), + version text not null, + branch text not null, + pr_url text, + review_findings jsonb, + greptile_stats jsonb, + todos_completed text[], + test_results jsonb, + created_at timestamptz default now() +); + +create index if not exists idx_ship_team on ship_logs(team_id); +create index if not exists idx_ship_repo on ship_logs(team_id, repo_slug); + +-- Greptile triage +create table if not exists greptile_triage ( + id uuid primary key default gen_random_uuid(), + team_id uuid references teams(id) not null, + user_id uuid references auth.users(id), + date date not null default current_date, + repo text not null, + triage_type text not null check (triage_type in ('fp', 'fix', 'already-fixed')), + file_pattern text not null, + category text not null default '', + created_at timestamptz default now() +); + +create index if not exists idx_greptile_team on greptile_triage(team_id); + +-- Session transcripts (opt-in) +create table if not exists session_transcripts ( + id uuid primary key default gen_random_uuid(), + team_id uuid references teams(id) not null, + user_id uuid references auth.users(id), + session_id text not null, + repo_slug text not null, + messages jsonb not null default '[]'::jsonb, + total_turns int, + tools_used jsonb, + started_at timestamptz, + ended_at timestamptz, + created_at timestamptz default now() +); + +create index if not exists idx_transcripts_team on session_transcripts(team_id); + +-- RLS for all data tables (same pattern) +-- Each table: team members can read/insert, admins can delete. + +-- retro_snapshots +alter table retro_snapshots enable row level security; +create policy "team_read" on retro_snapshots for select using ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "team_insert" on retro_snapshots for insert with check ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "admin_delete" on retro_snapshots for delete using ( + team_id in (select team_id from team_members where user_id = auth.uid() and role in ('owner', 'admin')) +); + +-- qa_reports +alter table qa_reports enable row level security; +create policy "team_read" on qa_reports for select using ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "team_insert" on qa_reports for insert with check ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "admin_delete" on qa_reports for delete using ( + team_id in (select team_id from team_members where user_id = auth.uid() and role in ('owner', 'admin')) +); + +-- ship_logs +alter table ship_logs enable row level security; +create policy "team_read" on ship_logs for select using ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "team_insert" on ship_logs for insert with check ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "admin_delete" on ship_logs for delete using ( + team_id in (select team_id from team_members where user_id = auth.uid() and role in ('owner', 'admin')) +); + +-- greptile_triage +alter table greptile_triage enable row level security; +create policy "team_read" on greptile_triage for select using ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "team_insert" on greptile_triage for insert with check ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "admin_delete" on greptile_triage for delete using ( + team_id in (select team_id from team_members where user_id = auth.uid() and role in ('owner', 'admin')) +); + +-- session_transcripts (tighter: admin-only read by default) +alter table session_transcripts enable row level security; +create policy "admin_read" on session_transcripts for select using ( + team_id in (select team_id from team_members where user_id = auth.uid() and role in ('owner', 'admin')) +); +create policy "team_insert" on session_transcripts for insert with check ( + team_id in (select team_id from team_members where user_id = auth.uid()) +); +create policy "admin_delete" on session_transcripts for delete using ( + team_id in (select team_id from team_members where user_id = auth.uid() and role in ('owner', 'admin')) +);