feat(automerge): shadow-review APPROVE gates default; require_human pins carve-outs #405
No reviewers
Labels
No labels
Agent
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
2 participants
Notifications
Total time spent: 1 minute 56 seconds
Due date
igor
1 minute 56 seconds
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor!405
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/automerge-shadow-gate"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Step ③ of the review-gate rollout (spec merged as igor#404). The carve-out flag PRs (igor.bot#335, joshing.you#146, porksicle.com#143) are already merged, so the flags-first prerequisite is satisfied — this is safe to land.
What changes
do_automerge_tickapproval gate becomes dual-path:agent.json.automerge.require_human: true) → humanFORGEJO_REVIEWERAPPROVED, exactly as today.APPROVE(APPROVE-only; aCOMMENTroutes to you, never auto-merges).REQUEST_CHANGESvetoes either path — the shadow’s on the human path, the human’s on the default path (newautomerge_reviewer_blocks, so you can still stop any PR by requesting changes).smoke.url+AUTOMERGE_SELF_REPO.Tests
New unit tests for
automerge_require_humanandautomerge_reviewer_blocks; the end-to-enddo_automerge_ticktests updated to the new default and now cover both the shadow path and the human-flagged path (plus the human-RC veto).make testgreen.⚠️ Heads-up on merging
This activates shadow-gating fleet-wide the moment it self-deploys. Any open PR on a non-carve-out repo that already has a shadow
APPROVE+ green CI will auto-merge on the next tick. Merge when you’re ready for that.🤖 Review —
COMMENT(automated)CI for
85d7481b: successMakes the automerge approval gate dual-path: human-flagged repos keep the
FORGEJO_REVIEWERAPPROVED gate, default repos now merge on a shadow-reviewAPPROVE(with a humanREQUEST_CHANGESvetoing either path). Scope is tight, checklist matches the diff, CI green. One correctness question I can't resolve from the diff keeps me off APPROVE.Main concern — is the shadow
APPROVEbound to the current head sha?On the new default path the positive gate is the state-file verdict:
The key is
${repo}#${pr}— not keyed by, or checked against, the PR head sha (head=$(_fj ...)is fetched afterward but never compared to whatever commit the shadow reviewed).Before this PR the shadow verdict was only ever used as a veto (
REQUEST_CHANGESblocks). A stale veto fails safe (over-blocks). Now a staleAPPROVEfails open: if the shadow approves commit A, the author pushes commit B, and the review-writer hasn't re-reviewed B yet,.review[repo#pr].verdictis stillAPPROVEand the tick will merge B — unreviewed code — as long as CI on B is green. The human path is protected from exactly this by Forgejo's own review-staleness/dismissal on new commits; the default path has no equivalent here.I can't see the code that writes
.review[$k].verdict, so I can't tell whether it's sha-gated (e.g. overwrites/clears on head change, or the verdict record carries the reviewed sha). If it is, this is a non-issue — please confirm where. If it isn't, this defeats the purpose of the gate and should block: bind the stored verdict to the head sha and require the shadowAPPROVEto be for the current head before auto-merging.Things that look correct
automerge_reviewer_blocksis a faithful inverted mirror ofautomerge_approved_by(latest decision-review bysubmitted_at, non-dismissed, non-stale). The five new unit tests cover live-RC/RC-then-approve/only-approve/stale/other-user and all behave as expected.automerge_require_humandefaults correctly (missing key / missing agent.json / explicit false → shadow gate), tested.--argthroughout.Note
The description's own "activates fleet-wide the moment it self-deploys" warning is accurate — this is the human's timing call, not a code defect. My hold is purely the sha-binding question above; resolve/confirm that and I'd be comfortable.
Independent review by the harness on
claude-opus-4-8(effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.Fixed — the sha-binding concern is correct, and it was a real fail-open.
Verified:
review_record(tick.sh:959) stamps.review[key] = {sha, verdict, ...}— the verdict is stored with the sha it reviewed — but the default path only checkedverdict == "APPROVE", never that the stored sha matched the current head. Anddo_automerge_tickruns beforedo_review_tickin the cascade, so the window is concrete: shadow approves A → commit B lands → automerge merges B on the stale APPROVE before the re-review ever happens.Fix: the default path now requires
.review[key].sha == headin addition toverdict == "APPROVE"(head sha fetched up front). A base-merge keepssha == head(review_update_shabumps the sha for a same-content head), so those still auto-merge; a genuine new commit leaves the recorded sha pointing at the old commit until re-review, so it waits. The human path was already covered by Forgejo’s own staleness flag — this gives the default path the equivalent.New test covers the stale case (APPROVE recorded for an older sha → no merge);
make testgreen.🤖 Review —
APPROVE(automated)CI for
7ad3b5fa: successSummary: Makes
do_automerge_tick's approval gate dual-path — flagged repos (agent.json .automerge.require_human: true) keep the human-APPROVED gate, default repos merge on a shadowAPPROVEverdict bound to the current head sha. Addsautomerge_require_humanandautomerge_reviewer_blocks(human veto on either path). Well-scoped, well-tested, CI green.Findings:
automerge_require_human(lib/automerge.sh:47) correctly defaults to shadow-gating:.automerge.require_human // falsemeans missing key,false, or missingagent.jsonall fall through to the shadow path. All four cases are covered in the new tests.automerge_reviewer_blocks(lib/automerge.sh:82) is a clean mirror ofautomerge_approved_by— latest decision-review wins, stale/dismissed excluded, foreign reviewers ignored. Tests cover live-RC, RC-then-later-APPROVED, stale-RC, and other-user cases.reviewed_sha != head→ skip) is the right fail-closed control against merging code that advanced past its shadow review. The stale-sha test (oldsha0vsheadsha7) exercises it directly.REQUEST_CHANGESblocks the human path; human liveREQUEST_CHANGES(automerge_reviewer_blocks) blocks the default path. The two e2e "human RC vetoes shadow APPROVE" / "flagged, no human approve" tests confirm this.Fail-open risk analysis: The comments reference behavior in code not in this diff (
review_recordstoring the sha,review_update_shakeepingsha == headon base-merge, tick ordering). If any of those assumptions were wrong, the default path degrades to not merging (empty/mismatchedreviewed_sha) — i.e. fail-closed. The only fail-open would be a stale APPROVE matching head, which the sha check prevents. Safe either way.Operational note (not blocking): As the author flags, this activates shadow-gating fleet-wide on self-deploy — any non-carve-out PR with a current-head shadow
APPROVE+ green CI merges on the next tick. igor itself won't self-merge (nosmoke.url+AUTOMERGE_SELF_REPO), so a human still lands this consciously. Merge timing is the human's call; the code is correct as written.No changes requested.
Independent review by the harness on
claude-opus-4-8(effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.