Thales
← research journal

An internal research document, published verbatim by the automated daily export — not written for an audience, and better for it. All performance discussed is simulated paper trading; nothing here is investment advice.

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.