Skip to content

feat(evals): eval the tables and data guide in supabase.com/docs - #264

Open
czenko wants to merge 7 commits into
mainfrom
evals/database-tables-guide
Open

feat(evals): eval the tables and data guide in supabase.com/docs#264
czenko wants to merge 7 commits into
mainfrom
evals/database-tables-guide

Conversation

@czenko

@czenko czenko commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Closes DOCS-1304

Problem

database/tables is the most foundational database page in the docs and it is red: roughly 46 percent negative across its feedback votes, on the highest vote count of any task-driven database page. It teaches table creation competently — primary keys get bigint generated always as identity, foreign keys get a section, the private schema example is correct — and it never once says to protect a table. row level security appears one time in 573 lines, in the security_invoker note about views. No alter table ... enable row level security, no policy, no auth.uid().

Feedback intake carries the consequence as a sustained ask: FDBKIN-2454, FDBKIN-33286, FDBKIN-30897, FDBKIN-32570, FDBKIN-25441, FDBKIN-5065, FDBKIN-5041, FDBKIN-28984, plus four more closed as duplicates. Customers keep asking the platform to close this by default, which says the documentation does not close it.

Solution

  • Adds build-docs-006-database-tables, suite: regression, services: [gotrue, kong, postgrest].
  • The one claim: a page that teaches table creation and never mentions row level security leaves an agent creating world-readable tables.
  • The prompt is a product request for a habit tracker plus the page url. A second clause gives it a starter list anyone can browse before signing up, which is what stops one approach satisfying every case: a solution that locks every table down fails the library check, and a solution that leaves everything open fails the private-data checks.
  • The seed ships no migrations, because the schema is the subject. It carries the contract in local/src/queries.ts instead — the table and column names the app already builds against. Names avoid the ones a memorized reading of "habit tracker" reaches for, so an agent that skips the seed produces tables the app cannot query. routines.id has no fixed type on purpose; uuid and bigint generated always as identity are both correct, so the scorer resolves routine ids by title rather than inventing one.
  • Eight checks: two read the catalog, four probe behavior over the Data API as a signed-out visitor and as the owner, one is the marker-row control the four probes are gated on, and one proves the page was read.
  • Marker rows are written after the agent's code exists, scoped to the run, so a hardcoded array cannot clear a read control and no literal leaks between runs.

Two checks are deliberately not what you might expect.

anon holds no write grant is absent. Supabase's default privileges grant anon write on new public tables and the standard pattern leaves them in place while row level security gates the rows, so asserting the grant is gone would fail the canonical correct answer. a signed-out visitor cannot create a routine measures the outcome instead.

the signed-in owner reads their own routines and nobody else's is present so that enabling row level security and writing no policy cannot score as protection. A schema that refuses everyone is not protected, it is broken.

Verification

Five fixtures scored against the stack before any agent ran, with the verdicts written down first. Every one failed exactly what it was built to fail.

Fixture Result Failed
green 7/8 the guide-read check, which no fixture can pass
page-faithful 2/8 RLS enabled, policy-per-table, anon read, owner read, anon write
rls-no-policies 4/8 policy-per-table, library browsable, owner read
policies-to-public 4/8 anon read, owner read, anon write
nothing-built 0/8 the marker-row control and every check gated on it

page-faithful is the finding. It is the page's own idioms and nothing else, and it hands every routine to a signed-out visitor to read and to write. The leaked titles in its notes are the scorer's own run-scoped markers, so that is a real leak rather than something the fixture hardcoded.

nothing-built is why the probes cannot pass vacuously. Without the control gate, an empty database satisfies "cannot read" and "cannot create" for free.

One prediction was wrong, and it was the check's fault

policies-to-public first scored 5/8, passing the anon-write check on a wide-open table. The probe sent only owner_id and title; cadence is not null in that schema, so Postgres rejected the insert as a not-null violation and the check counted any error as a refusal.

Fixed by making the probe send the whole contract row, so the only thing left that can refuse it is access control; by confirming the row's absence as the superuser rather than inferring it from the error; and by failing loudly with could not measure on any error outside 42501. The same guard now covers the read probe. The constraint is recorded in README.md so the insert does not get narrowed again.

A risk worth naming

This measures a docs gap that customers are asking the platform to close (FDBKIN-2454, FDBKIN-30897). If the default grants for anon on new public tables ever change, the central check saturates and the finding goes stale. That is the "check measures the platform, not the page" trap, better named here than discovered on a CLI bump.

Manual testing

  1. Confirm the ports are free.

    docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E '5432[0-9]->'
    
  2. Confirm the eval is discovered and its metadata validates.

    pnpm eval:dry
    

    18 planned pairs, mode=local-stack, including the -no-skills variants.

  3. Typecheck. Clean.

    pnpm typecheck
    
  4. Format the eval's own paths, since Biome walking local artifacts fails on fixtures that are nobody's diff.

    npx biome check evals/build-docs-006-database-tables
    
  5. Score the fixtures against the stack. Fixtures are git-excluded, so rebuild them from the table above.

    node --import tsx/esm ../../evals/build-docs-006-database-tables/solutions/score-stack.ts green page-faithful
    

The Tables and Data guide teaches table creation and never once says to
protect a table. `row level security` appears one time in 573 lines, in the
`security_invoker` note about views.

Adds build-docs-006-database-tables, suite regression, services gotrue, kong
and postgrest. The prompt is a product request for a habit tracker plus the
page url, with a second clause for a starter list anyone can browse, so a
solution that locks every table down and a solution that leaves everything
open both fail.

The seed carries the contract in src/queries.ts and ships no migrations,
because the schema is the subject. Table names avoid the ones a memorized
answer reaches for, and routines.id has no fixed type, so the scorer resolves
routine ids by title rather than inventing one.

Eight checks. Two read the catalog, four probe behavior over the Data API as a
signed-out visitor and as the owner, one is the marker-row control the probes
are gated on, and one proves the page was read. The marker rows are written
after the agent's code exists, scoped to the run.
@czenko
czenko requested a review from a team September 4, 2026 18:44
@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
evals Ready Ready Preview Sep 5, 2026 12:28am UTC

Request Review

@czenko czenko added the run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes label Sep 4, 2026
czenko and others added 2 commits September 4, 2026 14:54
The first baseline came back with zero docs calls on all six runs. Agents
wrote a correctly protected schema from memory, never opened the page, and
scored 7/8, so the number was evidence about the model rather than the page.

Adds the reliance instruction the skill requires verbatim, which both
build-docs-002 and build-docs-003 already carry and this eval omitted.
Records in README.md that the sentence is load-bearing, with what happened
without it.

Also folds motivation into a >- block per the skill, and drops the em dashes
from the eval's prose and comments.
@czenko czenko removed the run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes label Sep 4, 2026
supabase status writes a Stopped services line naming every service this
eval does not start, plus a CLI upgrade notice. Both are normal, and both
led the note ahead of the psql error, which got a genuine missing-table
failure recorded as a lost infrastructure run.

Silences the status call's stderr and hoists ERROR, FATAL, DETAIL and HINT
lines to the front. The note now opens with the relation that does not
exist.
The scorer finds the agent's tables by name, so a schema it cannot find scores
2/8 however well that schema is protected. Leaving the names in the data layer
for the agent to infer measured whether it read `local/src/queries.ts`, which is
a fact about the agent rather than about the page under test.

A run that modeled the starter set as a nullable `owner_id` on `routines`, with
row level security enabled and correct policies on every table, failed six of
eight checks on the names alone.

PROMPT.md now states the three tables and their columns. The seed still carries
them, and no security vocabulary enters the prompt.
@czenko czenko added the run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes label Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-evals-changed Add to a PR to refresh only the benchmark evals that have had changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant