Shadow before cutover
“Shadow before cutover” is not a feature; it is the strategy the whole package encodes. Every class
exists to make that ordering possible and safe. This page argues for it.
Motivation
Authorization is the one subsystem you cannot get wrong silently. A wrong decision either locks out a
legitimate user (an outage you will hear about) or lets in an illegitimate one (a breach you might not).
Both failure modes are unacceptable in production, and both are exactly what a blind authorization switch
risks.
The risk model of a blind switch
Suppose you migrate
probability that a big-bang cutover is completely clean is
For even a small per-mapping error rate over a realistic estate this collapses toward zero:
| 50 | ||
| 200 | ||
| 1000 |
A blind switch is a bet that every mapping is right at once. Shadow mode changes the game: instead of
betting, you measure
The ordering
Observation must precede enforcement because:
- You cannot measure parity after you switch — once IAM enforces, Spatie is no longer answering real
checks, so there is nothing to compare against. - The cost of a wrong decision is asymmetric and high — measuring is cheap (a log line); a production
lockout or escalation is not. - A reversible switch is only valuable if you know when to use it — the clean diff is the signal that
says “safe to go”, and the same evidence tells you a regression has appeared if mismatches return.
What a clean diff actually buys you
A clean iam.shadow.mismatch log over representative traffic is a proof of behavioral equivalence on the
paths that occurred:
where
the window must be representative (month-end, admin flows, rare roles). But it is evidence from your own
system, which is far stronger than a passing test suite written against assumptions.
The clean diff is the artifact you show stakeholders. “We ran both systems on production traffic for a week
and they agreed on every decision” is a sentence you can defend. “We mapped it carefully and it should be
fine” is not.
How the package enforces the ordering
- Default is
shadow. Installing the bridge cannot accidentally enforce anything. - Shadow returns
nullfromGate::after. Observation is structurally incapable of changing an outcome. - Enforce only stops observing. The bridge never flips the authority itself — the client’s Gate adapter
does — so “enforce” is a deliberate, separate decision. - Rollback is the same flag. The escape hatch is always one env var away, so attempting the forward move
is safe.
ADR — observe-then-enforce as the package’s reason to exist
Problem. Teams either avoid migrating off Spatie (fear of breakage) or migrate big-bang (and get burned).
Both come from the absence of a safe transition.
Decision. Encode “shadow before cutover” as the package’s core strategy: read-only scan → manifest →
shadow observation that changes nothing → evidence-gated, reversible cutover. Make shadow the default and
enforcement the client’s job, so the bridge’s only responsibility is the safe transition.
Consequences. Migration becomes a measured, defensible, reversible process. The trade-off is operational
patience and running both systems in parallel for a while — a small price for not getting authorization
wrong in production.
- A clean diff covers only observed checks. Unexercised paths (a quarterly admin job) are not proven —
size the shadow window accordingly. - “Shadow before cutover” is per application. Proving parity for
billingsays nothing aboutcrm. - Do not shorten the shadow window to hit a deadline; the diff’s value is entirely in its representativeness.
Next
- Reviewing mismatches — driving the diff to clean.
- Cutover & rollback — the reversible switch the strategy enables.