Appendix D · Migration-review checklist for AI-generated schema changes
The RLS Field Guide · 3 min read
Generators and assistants produce complete-looking migrations, and completeness of appearance is exactly the hazard: nothing in a plausible migration announces that its security half is missing. Review every schema-change pull request against this list. "No" is not automatically a rejection — but it must come with a written sentence from the author.
Tables
- [ ] Every
create tableis followed in the same migration byenable row level security. - [ ] Grants follow RLS in the same file: explicit
revoke all ... from anon, authenticated, then grants matching actual API usage. - [ ] Every table that holds tenant-scoped data carries the tenant id or an unambiguous path to it.
- [ ] Any table deliberately left without policies (internal, locked-by-design) says so in a comment.
Policies
- [ ] Every command the client performs (
select/insert/update/delete) has its own named policy; no barefor allwithout justification. - [ ] Every policy names its role (
to authenticatedor similar); none rely on the defaultpublic. - [ ] No condition is constant true, and no insert/update policy ships with a null
with check. - [ ] Multiple permissive policies on one table + command are listed with their derived union, in words.
- [ ] Any rule that must constrain future policies uses
as restrictive.
Destructive and reconstructive change
- [ ] Every
drop tableaccounts for the policies, grants, indexes, foreign keys, and publications attached to it. - [ ] Every
drop ... cascadelists what the cascade removed (it reports each one as aNOTICE, not an error); a dropped foreign key is a silent loss of integrity, not a formality. - [ ] Column renames/rebuilds check for policies referencing the old name (they fail at evaluation time, not deploy time).
- [ ] Anything resembling a drop-and-recreate of an existing table is treated as a security change, not just a schema change — Chapter 4, Pattern 2.
- [ ]
alter table ... disable row level securityappears nowhere. If someone believes it is required, that belief is the review discussion.
Functions and views
- [ ] Every
security definerfunction hasset search_path = ''(or an explicit safe path), fully qualified names inside, lives outside exposed schemas unless justified, and carries explicit execute grants. - [ ] New views set
security_invoker = true(Postgres 15+) or document why owner-rights are intended. - [ ] Functions callable by clients state who may execute them (
revoke ... from publicpresent when needed).
Surfaces beyond tables
- [ ] New storage buckets state their visibility and carry
storage.objectspolicies for user-driven operations. - [ ] New realtime publication members have current policies, and delete-event visibility has been decided explicitly.
- [ ] Edge-function changes touching the service key include the one-sentence justification for bypassing policies.
Tests
- [ ] The change includes pgTAP coverage for new/changed policies: one allowed and one denied case per command per role touched (Chapter 6's shapes).
- [ ] Existing isolation proofs still pass locally (
supabase test db) before requesting review. - [ ] If the change alters policy expressions for performance reasons (Chapter 9), before/after plans are pasted into the description.
Process
- [ ] The reviewer checks this list against the migration file itself, not the PR description.
- [ ] Anything unexplained fails the review with a request for one specific sentence — never a silent approval.