Skip to main content

Parity and cutover

Objective

Build a parity suite that proves the migrated policy returns identical decisions to the source system, run it in shadow mode, and cut over with a rollback path by prior commit_sha.

Concept

The whole migration is only safe if you can prove the new path decides the same as the old one. That proof has two layers: an offline parity suite and an online shadow period.

Parity as a test suite

A parity suite is a set of golden decisions captured from the source system (the inputs and decisions you recorded during assessment in lesson 1), encoded as OPA tests against the migrated policy. Each test asserts the migrated policy returns the exact decision the old system did. Two kinds of case matter, and the deny cases matter most:

  • Allow parity: an input the source system allowed must still allow. Catches a migration that accidentally tightened access and would break users.
  • Deny parity: an input the source system denied must still deny. Catches a migration that accidentally widened access. A silent widening is the dangerous one, and a deny parity test is what fails when it happens.

Because migration preserves the Rego (lesson 1), a well-built parity suite should pass on the first run. If it does not, you have found either a real behavior difference or a bad golden capture, and either way you found it before cutover, not after.

Shadow mode, then cutover

Passing tests prove parity on the inputs you captured. Live traffic is broader. So before flipping enforcement, run the migrated PDP in shadow mode: it evaluates real requests alongside the source system but its decision is logged, not enforced. Writ's decision logs make this comparison possible, and Enterprise OPA can mask sensitive fields in those logs. Compare the shadow decisions against the still-authoritative source decisions. A mismatch is a parity gap on real traffic; fold it back into the parity suite as a new golden case, fix, and re-shadow.

When shadow decisions match for a representative window, cut over: make the EnforceAuth-delivered PDP authoritative and retire the source system's bundle. Keep the safety net:

  • Rollback by prior commit_sha. Every deploy is commit-pinned (lesson 2). If cutover surfaces a problem, redeploy the last known-good commit's bundle and you are back to the previous behavior.
  • Retire, do not delete, immediately. Leave the source system readable for a short bake period so you can diff if anything looks off.
capture golden decisions (assess)
|
v
parity suite passes offline ---> opa test, required CI check
|
v
shadow mode on live traffic ---> compare logged vs authoritative
|
mismatch? -> add golden case, fix, re-shadow
|
v
cutover: EnforceAuth PDP authoritative
|
problem? -> rollback by prior commit_sha

Hands-on lab

This is where you build offline parity. The lab in courses/migrating-to-enforceauth ships RED: the parity suite in ../lab/policy_test.rego is complete (four allow and four deny golden decisions), but the migrated ../lab/policy.rego is a starter. Tenant isolation (same_tenant) is provided; the four allow rules are a # TODO(learner) stub. Your job is to reconstruct the preserved policy so the suite goes green.

Run it first to see the red state:

git clone https://github.com/EnforceAuth/university-labs.git
cd university-labs
opa test courses/migrating-to-enforceauth -v

You start at PASS: 4/8: the four deny cases already hold under default-deny, and the four allow cases fail because no allow rule exists yet. Open policy.rego and replace the TODO with the four preserved allow paths, each as its own allow if { ... } rule that includes same_tenant:

  1. Admins have full access within their own tenant.
  2. Editors may read and write within their tenant (use in for the action set), but not delete.
  3. Anyone in the tenant may read a public resource.
  4. Owners may delete a resource they own.

Re-run until you reach PASS: 8/8. That green run is offline parity proven: the migrated policy returns the source system's decision for every golden case. Keep iterating until opa test is green.

Then feel a migration bug. Temporarily widen your editor rule so editors may also delete (add "delete" to the editor action set). Re-run the suite: test_parity_editor_cannot_delete fails: a deny parity case just caught a silent widening of access, exactly the failure shadow mode also guards against. Revert the change and confirm PASS: 8/8 again. That failing-then-fixed cycle is the discipline that lets you cut over with confidence and roll back by commit_sha if you ever need to.

Check for understanding

  1. In a parity suite, which is the more dangerous kind of failure to miss, an allow that should be a deny or a deny that should be an allow, and which kind of parity test catches a silent widening of access?
  2. Your parity suite is green but you have not enforced the new PDP yet. What does shadow mode add that the offline test suite cannot, and where do mismatches you find go?
  3. After cutover, a problem appears. What is the rollback mechanism, and why is every deploy being pinned to a commit_sha what makes it work?