Promotion: one-click dev to staging to prod
Objective
Walk a signed bundle through one-click dev-to-staging-to-prod promotion, explain why the same signed artifact moves forward unchanged, and state how rollback works by prior commit_sha.
Concept
Once Writ has built and signed a bundle from Git, the next question is how it reaches production. Writ's answer is promotion: moving one signed bundle across environments in order, dev to staging to prod, with one click at each gate.
Promote the artifact, do not rebuild it
The key property is that promotion moves the same signed bytes forward. Staging does not rebuild the bundle from source, and prod does not rebuild it from staging. The artifact you tested in dev is byte-for-byte the artifact that reaches prod, and its signature proves it. This is what makes promotion trustworthy: there is no "works in staging, differs in prod" gap introduced by a per-environment build, because there is no per-environment build. You verified a specific signed bundle; that specific signed bundle is what runs.
Git merge
|
v
[Writ builds + signs bundle @ commit_sha]
|
v
dev --promote--> staging --promote--> prod
(same signed bundle at every hop)
Text version: a merge produces one signed bundle tied to a commit_sha. Promotion advances that one bundle from dev to staging to prod. Each arrow is a one-click gate; the artifact never changes across the arrows.
Rollback is a promotion of a prior commit
Because every bundle is tied to the commit_sha it was built from, rollback is not a special mysterious operation. It is promoting the bundle from a prior commit_sha back into the target environment. You are not editing prod by hand; you are pointing the environment at a previously-signed, previously-good artifact. That keeps rollback inside the same audited, signed path as a forward promotion.
Where deploy-action fits (the CI counterpart)
Promotion can be driven from CI as well as from the Writ console. EnforceAuth ships deploy-action, a GitHub Action that deploys OPA bundles via the EnforceAuth API. It authenticates with GitHub OIDC workload identity (token exchange per RFC 8693): the workflow requests an OIDC token with id-token: write permission, and a trust policy on the EnforceAuth side accepts that token. There are no long-lived API keys stored in the repo. Rollback with deploy-action is the same idea as in the console: redeploy the bundle for a prior commit_sha.
This lesson does not run deploy-action (it requires a configured GitHub OIDC trust relationship and the EnforceAuth API, not something a local lab reproduces). What you should take away is the shape: OIDC-authenticated, keyless, and it moves the same signed bundle that Writ built, identified by commit_sha.
Hands-on lab
The lab policy is the payload that promotion carries. Before a bundle of this policy is promoted, its tests must pass; a promotion gate is only as good as the deny tests behind it. Run the suite:
git clone https://github.com/EnforceAuth/university-labs.git
cd university-labs
opa test courses/govern-with-writ -v
Expected: PASS: 6/6.
Now do the promotion-gate exercise by hand. Open ../lab/policy_test.rego and read test_editor_cannot_write_other_org and test_viewer_cannot_write_own_org. Imagine a change that accidentally let a viewer write. That change would build into a signed bundle just fine, but the deny test would fail, and a promotion gate wired to opa test would refuse to advance the bundle to staging or prod. That is the everyday value of the deny tests: they are the thing standing between a bad merge and a promoted bundle. Do not remove them to make a change "pass"; fix the policy.
Check for understanding
- When a bundle is promoted from staging to prod, is it rebuilt from source in prod? Explain what actually moves and why that property matters.
- A promoted policy is causing denials in prod. Describe how rollback works in Writ's model, and what identifier it keys on.
- deploy-action authenticates to the EnforceAuth API without storing an API key. What mechanism does it use instead, and what one workflow permission does that require?