The site uses Supabase to store P(doom) submissions.
- Go to supabase.com and create a free project.
- Copy the Project URL and anon public key from Settings > API.
- Paste them into
_data/supabase.yml:
url: "https://YOUR_PROJECT.supabase.co"
anon_key: "YOUR_ANON_KEY"Open the SQL Editor in the Supabase dashboard and run:
CREATE TABLE submissions (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
submitted_at timestamptz NOT NULL DEFAULT now(),
factors jsonb,
summary jsonb,
quiz_flow_id text,
quiz_answers jsonb,
gate_score int,
gate_recommended_level text,
expert_verified boolean,
gate_answers jsonb,
visitor_key text,
submit_count int,
signed_payload text,
signature text
);The expert gate columns were added on 19 Aug 2026. Existing projects need:
ALTER TABLE submissions
ADD COLUMN IF NOT EXISTS gate_score int,
ADD COLUMN IF NOT EXISTS gate_recommended_level text,
ADD COLUMN IF NOT EXISTS expert_verified boolean,
ADD COLUMN IF NOT EXISTS gate_answers jsonb,
ADD COLUMN IF NOT EXISTS visitor_key text,
ADD COLUMN IF NOT EXISTS submit_count int,
ADD COLUMN IF NOT EXISTS signed_payload text,
ADD COLUMN IF NOT EXISTS signature text;Rows submitted before this date keep NULL in all eight. The four gate columns
are also NULL for any beginner or medium submission that never met the gate,
and the four identity columns are NULL whenever the browser could not generate
a key — a submission is never blocked over identity.
gate_score is non-null exactly when the visitor clicked Expert quiz, since the
check is not reachable any other way. That makes the whole expert journey queryable:
SELECT expert_verified, quiz_flow_id, count(*)
FROM submissions WHERE gate_score IS NOT NULL GROUP BY 1, 2;expert_verified = true cleared the bar; false with quiz_flow_id = 'expert' went on
anyway; false with a lower quiz_flow_id accepted the reroute. What no query can show
is anyone who took the check and left without submitting -- they leave no row.
Grouping repeat submissions is then a plain query:
SELECT visitor_key, count(*) FROM submissions
WHERE visitor_key IS NOT NULL GROUP BY visitor_key HAVING count(*) > 1;As of May 30, 2026, new Supabase projects no longer expose public tables to the Data API by default. Grant access explicitly so the anon key (used by supabase-js) can reach the table:
GRANT SELECT, INSERT ON submissions TO anon;Without this, PostgREST returns a 42501 error.
RLS is required for the anon key to work. Enable it on the table, then add policies for anonymous insert and read access:
ALTER TABLE submissions ENABLE ROW LEVEL SECURITY;
-- Allow anyone to submit predictions
CREATE POLICY "Allow anonymous inserts"
ON submissions FOR INSERT TO anon
WITH CHECK (true);
-- Allow anyone to read submissions (needed for the stats page)
CREATE POLICY "Allow anonymous reads"
ON submissions FOR SELECT TO anon
USING (true);| Column | Type | Description |
|---|---|---|
id |
bigint | Auto-incrementing primary key |
submitted_at |
timestamptz | When the prediction was registered |
factors |
jsonb | Array of {key, label, lower, upper, midpoint, spread} per stage |
summary |
jsonb | {lower, upper, midpoint, p10, p90} — the combined P(doom) result |
quiz_flow_id |
text | Which quiz was taken: beginner, medium, expert, or null if skipped. Historical rows may hold decide, the knowledge-check path retired on 19 Aug 2026. |
quiz_answers |
jsonb | Array of {question_id, type, value/values} — the user's quiz selections |
gate_score |
int | Expert gate score, 0–30. null if the gate never ran. |
gate_recommended_level |
text | Level the gate recommended: beginner, medium or expert. null if the gate never ran. |
expert_verified |
boolean | True when gate_score >= 26. A submission with quiz_flow_id = 'expert' and expert_verified = false is a self-declared expert. null if the gate never ran. |
gate_answers |
jsonb | The term ids ticked on the expert check, so per-decoy performance can be measured. null if the gate never ran; [] means it ran and nothing was ticked. |
visitor_key |
text | p256: + base64url of the browser's ECDSA P-256 public key. null for unsigned rows and everything submitted before 19 Aug 2026. |
submit_count |
int | 1-based submission number for that visitor_key. |
signed_payload |
text | The exact string the signature covers. Stored verbatim so verification never has to reconstruct it. |
signature |
text | base64url of the raw 64-byte r‖s ECDSA signature over signed_payload. |