Staging and production disagree about who can read
Every team asserts it after a staging pass: verified, ship it. The assertion smuggles a premise — staging’s policies equal production’s. In Supabase projects that premise fails routinely, because production accumulates console hotfixes, dashboard edits and manual interventions that never travelled back down the pipeline.
RowShield monitors environments as separate projects, so divergence surfaces as ordinary drift evidence rather than archaeology. Mechanism first, though: where the gap comes from, and how to measure it yourself today.
RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.
Where the gap comes from
Production-only mutations form a familiar list: the urgent policy tweak applied during an incident, the index added by hand at 2am, the bucket flipped public for a campaign and back incorrectly, the hotfix migration executed ahead of its own pull request. None malicious; all bypassing the path staging validates, because production was burning and process felt expensive — the justification writes itself, which is precisely why the pattern survives every retrospective intact.
Meanwhile staging evolves too — seeded differently, migrated from other branches, occasionally rebuilt with best-effort fidelity. Two databases, both changing, sharing no memory. The assertion "tested in staging" measures similarity at some past moment, decaying ever since. Neither side is wrong, which is the trap: auditing either database alone reveals nothing amiss, only the comparison exposes the wedge, and the wedge collects in policy text first because incident fixes touch authorization before anything else.
Measuring the divergence yourself
Run one identical posture inventory against both connection strings and diff the outputs — small enough to live in CI:
-- Run against BOTH projects; diff the results
SELECT tablename, policyname, cmd, roles
FROM pg_catalog.pg_policies
WHERE schemaname = 'public'
UNION ALL
SELECT relname, 'rls_enabled', relrowsecurity::text, NULL
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND relkind IN ('r','p')
ORDER BY 1,2;Verifying from outside
Catalog diffs establish what each environment believes; behavioural probes establish what each environment serves, and staging-versus-production disagreements show up in the second layer even where the first looks aligned. Extensions differ, runtime settings differ, and auth configuration lives partially outside the catalog, so identical requests can legitimately return different shapes.
Run the same probe suite against both connection strings: anonymous listing requests against your governed tables, bucket fetch attempts against private storage, authenticated reads scoped to a test principal. Where responses agree, confidence transfers; where they diverge, the catalogue diff above explains why — or reveals a runtime-only gap the catalog could never show.
Closing the loop
Once measured, process does the rest: promote changes one direction only; fold emergency edits back into migrations within a day while memory holds; reconcile historical divergence once, deliberately, using the diff output as the worklist. Give the reconciliation its own ticket and its own review: folded into feature work, it loses priority every sprint until the next surprise, which is how historical gaps become permanent residents.
Keeping both environments connected to monitoring makes reconciliation continuous instead of heroic — each environment streams its own drift history, and cross-environment disagreement becomes visible the week it is born rather than the incident it eventually causes. History on both sides also shortens the post-incident conversation: what production absorbed that staging never saw stops being archaeology and becomes a filter.
Frequently asked
- Why does staging diverge from production so fast?
- Because production absorbs out-of-band fixes — incident hotfixes, console edits, manual operations — that never reverse through the pipeline, while staging keeps evolving from different inputs. Pressure selects for speed and speed selects for direct action; meanwhile staging merges branches on a calmer clock. Two inputs, two tempos, one shared name in the documentation — divergence is the inevitable arithmetic of those facts.
- Can one account watch both environments?
- Yes — connect each environment as its own project and they accumulate independent histories, which is the whole point: divergence evidence requires two timelines to compare. Team plans cover up to fifteen projects at fifteen-minute cadence, leaving room for preview environments beyond the core pair, while Free-tier daily scans still catch the slow wedge, only later.
- Diff first, or rebuild staging first?
- Diff first: it distinguishes cosmetic divergence from load-bearing gaps. Rebuilding staging without the worklist restarts the clock on the same process hole, because the rebuilt environment inherits fresh divergence the first time someone hotfixes production anyway. Measure, reconcile deliberately, repair the out-of-band pathway, and only then decide whether a rebuild still earns its cost.
- Does probing help here too?
- Strongly — behavioural differences show up even where catalog text looks aligned, because runtime configuration and extensions differ per environment in ways DDL dumps miss. Probing both environments with identical requests turns that observation into evidence: matching shapes transfer confidence, diverging ones pinpoint exactly which assumption broke. Pair the probes with the catalogue diff and neither layer can hide the other’s blind spot.
Check your project in about ten seconds
Paste a URL. No signup, no writes, nothing stored.
Run the free audit