# AMG-4 — the records auto-merge workflow judges a PR by the PR's OWN copy of the guard, so its "can never widen itself" claim is circular

status: open · raised panel run 12 (2026-08-21) · migrated 2026-08-24 (body verbatim below) · class: loudness & safety (governance guard) · judgement: **YES** · effort 0.25 pd + one owner scope-check · **re-verified STILL LIVE and aggravated by panel run 13 (2026-08-24)**

---

**17. AMG-4 — the records auto-merge workflow judges a PR by the PR's OWN copy
of the guard script, so its "can never widen itself" claim is circular: a
same-repo PR that edits `scripts/automerge_guard.py` to always-ALLOW is
graded by the edited guard.** · judgement: **YES** · effort 0.25 pd (fix) +
one owner scope-check (shared with the OBX-1 question below) · horizon: live
now, conditional on the pushing credential's reach

*Mechanism, code-verified.* `.github/workflows/auto-merge-records.yml:44` is
`actions/checkout@v5` with no `ref` (on a `pull_request` event → the PR merge
commit), and `:82` runs `python3 scripts/automerge_guard.py` from that
checkout. The `^scripts/` deny pattern (`automerge_guard.py:55`) therefore
lives in the very file a PR replaces. Editing `scripts/` (unlike
`.github/workflows/`) needs **no** `workflow` scope on the pushing credential,
so any routine/session with contents-write can open a records-classed PR that
rewrites the guard to ALLOW and edits the guard's own tests in the same PR;
the only remaining gate is required CI under `--auto` (`:103`). The workflow
comment `:20-21` and the guard docstring `:17-18` both assert self-widening is
impossible — false for `scripts/`. This is the second firing of the 08-11
"cannot-widen-itself" valve (queue `:2119`), which pre-registered mint **as
its own row**; AMG-2 was the first firing of the same valve, so the precedent
for a separate row is in the file.

*Data plan.* None — static code + workflow semantics. Owned. One fact is
**not** repo-verifiable and is the shared owner-check below: whether the
`.github/`-flank is even reachable depends on whether the routines' GitHub
credential holds `workflow` scope.

*Test design + kill criterion — pre-registered, HARMLESS-ON-FAILURE (this is
the sharp part).* Fix: execute the guard from the **base** ref, not the merge
ref — the workflow already has the exact primitive (`git show
"origin/$BASE:$Q"` at `:75` materialises a base-ref file). Verification probe:
a PR that edits the guard to unconditionally ALLOW must draw a **DENY**
end-to-end. **Critical constraint on the probe, or the test IS the incident it
tests for:** a naive probe against the real base branch, if the fix regressed,
would let its own edited guard ALLOW, arm `gh pr merge --auto`, and **merge an
always-ALLOW guard onto main**. The probe MUST be harmless on failure — target
a scratch base branch (the `pull_request` trigger has no base filter, so the
workflow still fires; a wrongful merge lands on the scratch branch, not main),
or carry a deliberately-failing required check so `--auto` can never complete.
Specify this in the PR before touching code. **Kill criterion:** with the fix
in place, the always-ALLOW probe PR draws DENY from the base-ref guard;
reverting the fix → the probe's edited guard ALLOWs (against the scratch base
only). **Recorded residual:** the workflow *file itself* is still evaluated
from the merge ref, shielded only by the `workflow`-scope requirement — whose
status is the very unknown the OBX-1 owner-check resolves. If that scope is
held by the routines' credential, this fix is necessary-but-not-sufficient and
the residual must be recorded, not silently assumed closed.

## Triage note — 2026-08-24 (annotation only; the body above is untouched)

**Re-verified STILL LIVE this week, and aggravated.** Panel run 13 records
that `05895ab` ("gates: merge exactly the SHA the guard evaluated") fixes the
TOCTOU leg only — *which* SHA gets merged — not *who supplies the evaluator*.
`.github/workflows/auto-merge-records.yml:~44` still checks out the PR merge
ref and runs the PR's own copy of the guard. For same-repo `pull_request`
events the workflow file itself also runs from the merge ref with a
`contents: write` token, and this plan has no branch protection (05895ab's own
commit message states it), so base-ref-guarding the *script* is
necessary-but-not-sufficient. See the cross-reference section of
`research/queue/open/amg-5-sanctioned-move-fail-open.md`, which states plainly
that **AMG-5 does not subsume AMG-4**.

**Escalated by the daily audit as well.** The 2026-08-21 daily-audit report
(branch `outbox/daily-audit`) raised this as escalation E1, asking for either
the base-ref checkout fix or a temporary disable of the records fast path
before the 08-26 implementer window. That ask was not actioned.

---

## BUILT — 2026-09-02

**Outcome: shipped.** `auto-merge-records.yml` now materialises the guard from
`origin/$BASE` (`git show origin/$BASE:scripts/automerge_guard.py`) and grades
the PR with THAT copy; an unreadable base-ref guard exits 1 (deny), never
falling back to the PR's copy.

**Route — recorded honestly:** built on the owner's direct instruction in an
interactive session (2026-09-02 deep self-audit, `research/2026-09-02_self_audit.md`
finding M3), NOT via `queue/approved/` and the Wednesday implementer — the
EXQ-1 precedent. The guard correctly denies this open→built move, so a human
merges.

**Verification.** Two lint tests in `tests/test_execution/test_automerge_guard.py`:
the base-ref materialisation must be present and no `python3 scripts/automerge_guard.py`
invocation may remain; the read failure must fail closed. **Negative control:**
both tests were written first and ran RED against the unfixed workflow (2 failed),
then GREEN after the edit. The row's end-to-end probe (an always-ALLOW PR against
a scratch base branch) was NOT run — it needs a scratch branch and a live
workflow run, which is an owner-run step; recorded as not done, not as done.

**Residual, recorded as the row required:** for same-repo PRs the workflow file
itself still runs from the merge ref (shielded only by the `workflow` scope
requirement on the pushing credential, still unverified — the OBX-1 check).
The main-push watcher (separate PR, same audit) covers the other bypass: a
commit landing on main with no PR at all.

---

*Provenance recorded by triage 2026-09-07 (annotation only; the record above is untouched): shipped in **PR #127** (`8c0f731`, merged 2026-09-03). The residual OBX-1 probe named in this file — does the routines's GitHub credential hold `workflow` scope? — is still unanswered and is carried in the 2026-09-07 triage report.*
