Files
supabase-postgres-best-prac…/packages/evals/scenarios/rls-update-needs-select.md
2026-02-25 19:50:54 +00:00

4.3 KiB

Scenario: rls-update-needs-select

Summary

The agent must write a migration for an orders table where users can view and update only their own orders. The classic trap is writing an UPDATE policy without a matching SELECT policy — causing UPDATE to silently affect zero rows because RLS cannot find any rows to update.

Real-World Justification

Why this is a common and important workflow:

  1. "Why is my UPDATE returning empty data?" — The Supabase troubleshooting guide lists "Why is my select returning an empty data array and I have data in the table?" which is the same root symptom. UPDATE with no SELECT policy silently returns {data: [], count: 0} with no error, making it extremely hard to diagnose.
  2. Documented RLS behavior — The official RLS docs state that UPDATE requires a SELECT policy to identify which rows are accessible for modification. This is non-obvious and contradicts most developers' expectations from SQL semantics.
  3. WITH CHECK requirement — An UPDATE policy also needs a WITH CHECK clause to prevent users from updating a row to a state that would no longer be visible to them (e.g., changing their own user_id). Missing this allows data ownership hijacking.

Skill References Exercised

Reference File What It Teaches What the Agent Should Apply
references/db-rls-common-mistakes.md UPDATE needs a SELECT policy; WITH CHECK clause Separate SELECT and UPDATE policies, WITH CHECK
references/db-rls-policy-types.md USING vs WITH CHECK semantics WITH CHECK on UPDATE policy
references/db-rls-performance.md (select auth.uid()) subquery caching Subselect form in all USING/WITH CHECK
references/db-rls-mandatory.md Enable RLS, TO authenticated Full mandatory boilerplate
references/db-schema-timestamps.md timestamptz for time columns timestamptz not timestamp

Workspace Setup

  • Empty workspace with a pre-initialized supabase/config.toml (no migrations)

Agent Task (PROMPT.md draft)

Create a migration for an orders table. Each order has a status (text), total (numeric), and created_at. Orders belong to users. Users should be able to view their own orders and update the status of their own orders. Put the migration in supabase/migrations/.

Evaluation Criteria

# Test Name What It Checks Quality Dimension
1 migration file exists At least one .sql file in supabase/migrations/ structure
2 creates orders table SQL contains CREATE TABLE and orders correctness
3 RLS enabled ALTER TABLE orders ENABLE ROW LEVEL SECURITY security
4 has SELECT policy A FOR SELECT policy exists on orders correctness
5 has UPDATE policy with WITH CHECK A FOR UPDATE policy with WITH CHECK clause exists correctness
6 all policies TO authenticated Every CREATE POLICY has TO authenticated security
7 uses (select auth.uid()) Subselect form in policy USING clauses performance
8 uses timestamptz not timestamp created_at timestamptz not plain timestamp correctness
9 FK to auth.users with CASCADE REFERENCES auth.users ... ON DELETE CASCADE correctness

Reasoning

  1. Baseline differentiator: Without the skill, agents write only an UPDATE policy (or a single ALL policy), skip WITH CHECK, and use bare auth.uid() calls. The result is a migration that looks complete but breaks silently.
  2. Skill value: db-rls-common-mistakes.md explicitly covers this UPDATE-needs-SELECT behavior with working examples.
  3. Testability: The presence of both FOR SELECT and FOR UPDATE with WITH CHECK is directly detectable via regex on the SQL.
  4. Realism: "My UPDATE isn't working, returns empty" is among the most common questions from developers new to RLS in the Supabase community.

Difficulty

Rating: MEDIUM

  • Without skill: ~40% of assertions expected to pass (table and RLS likely, but wrong policy structure)
  • With skill: ~92% of assertions expected to pass
  • pass_threshold: 8