-- Lumbridge Bench — Supabase schema.
--
-- Supabase holds ONLY the mutable parts: the manual suggestion inbox and accounts.
-- Measurements are not here. They live in results/*.json in git, so the
-- leaderboard cannot drift from what was actually recorded and there is no
-- migration path to get wrong. See docs/DECISIONS.md.

create schema if not exists bench;

-- ---------------------------------------------------------------------------
-- accounts: invite-gated signup
-- ---------------------------------------------------------------------------

create table if not exists bench.invite_codes (
  code        text primary key,
  max_uses    int  not null default 1000,
  uses        int  not null default 0,
  active      bool not null default true,
  created_at  timestamptz not null default now()
);

create table if not exists bench.profiles (
  id          uuid primary key references auth.users(id) on delete cascade,
  handle      text unique,
  invite_code text references bench.invite_codes(code),
  created_at  timestamptz not null default now()
);

-- ---------------------------------------------------------------------------
-- submissions: compatibility table used as a manual suggestion inbox
-- ---------------------------------------------------------------------------

create type bench.submission_status as enum (
  'queued', 'running', 'complete', 'rejected', 'failed'
);

create table if not exists bench.submissions (
  id           uuid primary key default gen_random_uuid(),
  hf_ref       text not null,
  params_b     numeric,
  notes        text,
  contact      text,
  status       bench.submission_status not null default 'queued',
  anonymous    bool not null default true,
  submitted_by uuid references auth.users(id) on delete set null,
  -- run_id links to the results/*.json produced for this submission. Not a
  -- foreign key: results live in git, not in this database, by design.
  run_id       text,
  reject_reason text,
  created_at   timestamptz not null default now(),
  updated_at   timestamptz not null default now()
);

-- One pending suggestion per model. The legacy `queued` state means awaiting
-- owner review; it does not authorize downloads or execution.
create unique index if not exists submissions_pending_ref
  on bench.submissions (hf_ref)
  where status in ('queued', 'running');

create index if not exists submissions_status_created
  on bench.submissions (status, created_at desc);

-- ---------------------------------------------------------------------------
-- RLS
-- ---------------------------------------------------------------------------

alter table bench.submissions  enable row level security;
alter table bench.profiles     enable row level security;
alter table bench.invite_codes enable row level security;

-- Suggestions and contact details are private. There is intentionally no public
-- select policy on this table; selected aggregate Bench results live in git.

create policy submissions_anon_insert on bench.submissions
  for insert with check (
    status = 'queued'
    and (submitted_by is null or submitted_by = auth.uid())
  );

create policy profiles_self_read on bench.profiles
  for select using (id = auth.uid());

create policy profiles_self_write on bench.profiles
  for update using (id = auth.uid());

-- Invite codes are never client-readable; validation happens server-side with
-- the service role. A readable invite table is an open signup form.
create policy invite_codes_no_read on bench.invite_codes
  for select using (false);

-- ---------------------------------------------------------------------------
-- seed
-- ---------------------------------------------------------------------------

insert into bench.invite_codes (code, max_uses)
values ('115karti', 1000)
on conflict (code) do nothing;
