A query that returns rows tells you the role has a grant. It does not tell you the policy filtered anything, because the most common failure in row-level security is not an error but a quiet widening: the query succeeds and returns one tenant's rows to another tenant's user. Postgres itself frames this as a default-deny system: once you run ALTER TABLE ... ENABLE ROW LEVEL SECURITY, every normal access must be allowed by a policy, and with no applicable policy you get zero rows rather than an error the PostgreSQL row security documentation. That default-deny behavior is good design and terrible for discovery, because a broken policy and a correct one both return HTTP 200 to your application.
Two checks run before a client touches a table. Grants decide whether a role can run the operation at all, and policies decide which rows the operation applies to; a missing grant raises a 42501 error before any policy runs the Supabase RLS guide. That split matters for your harness: a test that fails with 42501 is telling you about grants, not policies, and you should assert the two separately so a failure names the right layer.
There is a third bypass that tests catch and code review usually misses. Table owners normally bypass row security unless the table is marked FORCE ROW LEVEL SECURITY, and superusers and roles with BYPASSRLS always bypass it PostgreSQL CREATE POLICY. If your test connection is the table owner, every test passes and proves nothing. Run tests as a non-owner role, the same way production does the oneuptime multi-tenant walkthrough.



