Runbook — reviewing an atom proposal
Audience: a regime maintainer, and whoever administers the repository.
Trigger: an atomize/* branch appears on the regime repository.
Time: minutes, plus however long the reading of the law takes.
An obligation atom is a machine’s reading of a legal text. Merging one makes it part of what OpenRegs answers with, so a person with standing has to say yes first. This runbook is that step, and the rules below are not advice: the remote refuses a push that breaks any of them.
What is enforced by a machine, and what is not
Section titled “What is enforced by a machine, and what is not”Read this table before trusting anything else in this document.
| Rule | Enforced by |
|---|---|
main advances only by merging a branch | the pre-receive hook, mechanically |
| at least one approving review, from a code owner of every changed path | the hook, mechanically |
| an approval covers exactly one commit; new work dismisses it | the hook, mechanically |
| nobody approves a branch they wrote; a bot never approves | the hook, mechanically |
| every required check green at the merged commit, published by the ci identity | the hook, mechanically |
| the merge commit records the reviewers it was made on | the hook, mechanically |
force-pushes and deletions of main | the hook, mechanically |
| the same rules on a hosted GitHub repository | a human, by hand — see below |
| who a pusher actually is | the transport, not the hook — see below |
Two honest limits:
- GitHub is configured by a person. Branch protection on a hosted repository
is server-side configuration, and no test in this project may talk to GitHub.
openregs review github-settingsrenders.github/branch-protection.yamlinto the exact API payload, and.github/branch-protection.github.jsonis that payload committed. Until somebody applies it, the hosted branch is protected by nothing. Three rules have no GitHub setting at all; the command names them every time it runs. - The hook authorizes; it does not authenticate. The pushing identity arrives
in
OPENREGS_PUSHER. A real server sets it from the authenticated connection (an ssh forced command, a forge). Over a localfile://remote there is no authentication, so the client asserts its own name — which is why the fixture harness demonstrates that the rules bind, not that the names are true.
0. Ownership: one source, one generated file
Section titled “0. Ownership: one source, one generated file”MAINTAINERS.yaml names the teams, the people in them, and which team answers for
which regime. .github/CODEOWNERS is generated from it:
make gen-codeownersNever edit .github/CODEOWNERS. The test suite runs the same generator with
--check, which fails when the committed file and the roster have drifted apart:
python -m openregs.governance --check(That command is a required status check by name — check-canon and test are in
the policy’s contexts — but the repository’s own GitHub workflow is defined by
the CI/CD task, not this one. Today make test is what runs it.)
For the fixture regime the routing the task cares about reads:
/regimes/fixreg/atoms/ @openregs/fixreg-maintainers/regimes/fixreg/canon/ @openregs/fixreg-maintainersOwnership resolves by last matching rule, so the file is written general-to-specific and the generator, not a person, decides the order.
1. Protect the branch
Section titled “1. Protect the branch”Once, per remote. On a local bare repository — the only kind these commands are tested against:
REMOTE=$PWD/.test-remotes/atom-review.gitopenregs review protect --repo $REMOTEThat installs hooks/pre-receive and stores the policy at
openregs/branch-protection.yaml inside the repository directory — beside the
object store, outside every branch, exactly as a forge keeps branch protection
outside the branch it protects. A pull request therefore cannot weaken the rules
being applied to it. The roster and CODEOWNERS are read from the current tip of
the protected branch, so widening ownership needs a merge under the old ownership.
On a hosted repository, instead:
openregs review github-settings --out .github/branch-protection.github.jsongh api -X PUT repos/<owner>/<repo>/branches/main/protection \ --input .github/branch-protection.github.jsonThe second command is run by a human with admin rights. Nothing in CI runs it and nothing in CI can check that it was run.
2. The proposal arrives
Section titled “2. The proposal arrives”openregs atomize stages its proposals on a branch and pushes it. Nothing else
happens by itself:
git push $REMOTE atomize/<unit>/<date>Look at what it would change, and who owns that:
openregs review status --repo $REMOTE --branch atomize/<unit>/<date>branch atomize/eu-act-reg-2024-1-art_5__para_2/2025-06-01 @ 4f2a9c1b6d07 base 1a0b2c3d4e5f contains the tip approvals: none checks green: none checks missing: lint, typecheck, test, check-canon changed paths and their owners: regimes/fixreg/atoms/FIXREG-Art5.2-Ob1.yaml @openregs/fixreg-maintainers3. Checks report, as the ci identity
Section titled “3. Checks report, as the ci identity”A result belongs to one commit and is published by the one identity the policy names. Anybody else’s result is refused at the push:
openregs review check --repo $REMOTE --sha <sha> \ --context check-canon --result success --as openregs-ci--result failure records the failure as a ref of its own. A red result is a
record, not a draft: it is not overwritten by a later pass, and while it stands
the merge is refused. The way past it is new work on a new commit.
4. Approve — what you are actually saying
Section titled “4. Approve — what you are actually saying”Read the proposed atom against the text it cites. Then:
openregs review approve --repo $REMOTE --sha <sha> --as <your-handle>The approval is a ref naming you and that commit. Consequences worth knowing:
- Push more work and the approval is gone. It named a commit, not a branch.
- You cannot approve your own branch, and a machine identity cannot approve at all.
- Approving does not merge. Someone still has to, and the merge is checked again.
- Changed your mind:
openregs review approve --sha <sha> --as <you> --withdraw.
Being a maintainer is not enough — you must own the paths the branch changes.
An approval from a team that owns spec/ does not admit an atom into fixreg.
5. Merge
Section titled “5. Merge”openregs review merge --repo $REMOTE --branch atomize/<unit>/<date> --as <your-handle>The merge commit is built with the reviewers read from the approval refs on the remote:
Merge branch 'atomize/eu-act-reg-2024-1-art_5__para_2/2025-06-01'
Reviewed-by: Lieve Verheyen <l.verheyen@openregs.invalid>Checks-passed: check-canon, lint, test, typecheckMerged-by: Caesar Mukama <mcaesar@openregs.io>The remote re-derives all of it. A merge commit whose trailers name somebody who
did not approve that commit is refused, so the history’s record of who reviewed
what is a checked fact rather than a convention. The approval and check refs stay
in the repository afterwards — git for-each-ref refs/reviews/ is the audit.
6. When a push is refused
Section titled “6. When a push is refused”Every refusal names the rule. The common ones:
| Message | What to do |
|---|---|
is not a merge commit | do not push to main; merge a branch |
0 approving review(s) | get a code owner to approve the current commit |
no approval from a code owner | the approver does not own that path; see CODEOWNERS |
has not reported green | wait for CI, or publish the result as the ci identity |
reported FAILED | fix it and push a new commit; a red result stands |
Reviewed-by trailers do not match | merge with openregs review merge |
non-fast-forward update refused | never rewrite main; land a new commit |
has no identity | the transport did not set OPENREGS_PUSHER |
What this costs, and why
Section titled “What this costs, and why”A machine can propose an obligation atom in a second and a person cannot check one in a second. The gap is the point: the corpus is used to answer questions about what the law requires, and an unreviewed answer with a citation attached is worse than no answer at all. Everything above exists so that the record shows which person accepted which reading of which text, and so that no amount of automation can quietly become that person.
Rendered from openregs/openregs@cbe9615:docs/runbooks/atom-review.md