ADR-0008: Auto-merge: a driven session lands when every gate is obeyed, and the reader reads first
- Status: Proposed
- Date: 2026-10-05 (originally decided 2026-09-25 and promoted to the scaffold 2026-09-27, re-recorded here on the second lift)
- Deciders: Process maintainer
Context
Section titled “Context”With a driver and a chain (ADR-0007) a plan continues on its own, but every session still stopped for a person to merge it. The owner’s ruling was that a driven session may merge with no human push provided every gate that applies to a PR is obeyed, and that speed is never bought by loosening a check.
Three facts constrain how that can be built:
- Branch protection may not be available. On some plans and private repos it is not, so there are no required checks, no merge queue and no native auto-merge. Every guarantee has to come from the pipeline itself.
- CI runs the head’s own gate. A session that weakened its gate would be judged by the gate it weakened.
- The gate cannot see everything that matters: a test that proves nothing, a cited clause that nothing realises. A merge must not race the reviewer.
Decision
Section titled “Decision”- Merging is its own workflow,
merge.yml, separate from the driver and the chain. It fires on a successful driver run, or by dispatch as the re-entry path. The chain never merges, because a second merge site would ask none of the questions below. - One script says yes,
merge-decision.sh, and the first failure is a named refusal:- The head’s plan differs from
main’s only in this session’sstatusandevidence. - No path in
.xal/protected-paths(the gate, fixtures, workflows, declared inputs, vendored spec, the overlay’s tool configuration) changes unlessmain’s plan lists it among the session’s artifacts. - The session’s status on the head is
passed. autonomy_level, read frommain, admits the session’srisk_class, read frommain.code-onlyadmits {code-only}.code-and-tests-pre-deploymentadmits {code-only, data-migration, secrets}.unsetand anything else admit nothing.infra,billing,public-surfaceanddeleteare never admitted.- CI’s gate on the head is green. No verdict refuses: unjudged is not green.
- The reader’s commit status on the head is green.
- If the head is behind
main, the answer isupdate: the branch is brought up to date and judged again from condition 1. Red is refused before staleness is considered, so updating is never a second chance for a red PR.
- The head’s plan differs from
- The merge pins the head commit and is a merge commit, so the agent’s red-to-green
commits stay on
mainas the evidence that test-first was performed rather than claimed. Admin and auto merges are refused by the workflow’s shape check. - A refusal is a designed stop, and the run is green. The PR gets the escalation label, a comment with the reason, and an issue. The plan halts because nothing merged, and a human merge resumes it through the unchanged trigger.
- The reader reads every session PR before it can land. The
readeragent, distributed in the plugin, gives two verdicts: Spec (each cited clause is realised and asserted by a test that fails without it) and Standards (the repository’s laws as they stand onmain).reader.ymlruns it at a pinned plugin tag, and every script it runs comes frommain, so a PR cannot change its own judge. A verdict counts as green only when it is a well-formed pass on both axes with no findings. The reader’s failing fixture, a diff that omits a cited clause, must be reported by id before any reader version is tagged. - The driver refuses
autonomy_level: unset. A driver that can merge does not start a session under a plan with no autonomy ruling. - One session in flight per repository. Two service chains are two repositories, two plans and two independent ceilings.
Consequences
Section titled “Consequences”- A person reads only the stops: out-of-class sessions, reader findings, red or unjudged CI, conflicts and plan tampering. For everything else the model and the reader are the only controls on what the gate cannot see, which is why the reader is required and pinned.
- Cost. Each session PR adds a reader run, and another whenever the branch is updated. Reader spend goes into the ledger and counts against both ceilings without counting as an attempt.
- The merge credential is a personal token until a forge app replaces it, so a merged PR reads as the owner’s.
- If branch protection becomes available, required checks and a merge queue can take over conditions 5 to 7 natively. That is a revisit, not a reason to wait.
Rejected alternatives
Section titled “Rejected alternatives”- Merge inside the chain. One workflow would decide both what runs next and what lands, and the failure where it believes its own verdict would have no second check.
- Serialise merges without re-gating. Two green sessions can be red once combined.
- Keep a generic review bot as the reviewer. It posts comments, not a verdict a script can read, so “must not race the reviewer” would reduce to “waited for a bot to finish”.
- Read the reader’s definition from the plugin’s default branch. An unreleased edit would change the merge gate with nothing having evaluated it.
Revisit triggers
Section titled “Revisit triggers”- The ratchet. A run of consecutive passes in one class, counted by the improvement
session, is the input to widening
autonomy_level, and widening is still a ruling. - Branch protection becomes available. Move conditions 5 to 7 to native checks.
- A forge app replaces the personal token. The merge credential changes, and provenance is fixed.