feat(review): guard submissions against moved heads #1387
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1387
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "sentry/1383-review-submit-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?
Why
release-toolkit#1383 direction ② reduces review-triggered re-runs before any batching or path filtering decision. A reviewer can currently spend a full read while the PR head moves, and the author/reviewer HOLD rule exists in coordination messages without being present where both parties work.
What's in the PR
rt review-submit-check, a read-only pre-submit gate that reads the live PR and compares its fullhead.shawith the exact full SHA the reviewer read.rc=0only for an open PR whose live head matches,rc=1for a moved head, andrc=2for malformed/unreadable/unreviewable input.AGENTS.md, and integration guidance. Body-only corrections must be announced because they do not move the head or dismiss a human rejection.rt review-eligibilityseparate: #1277 answers whether an approval can count; this command answers only whether the reviewed PR head is still current.Acceptance criteria
.forgejo/pull_request_template.md,AGENTS.mdsection 6.2, anddocs/integration.md.rt review-submit-check --owner OWNER --repo REPO --pr N --reviewed-head FULL_SHA. It is a pre-submit check, not a review POST; it deliberately does not duplicate approval eligibility or review-row gating.Verification
This PR was opened from the following exact artifact:
2fbd2780404d1280e8b345d365bcc17440ffc396(the code verification below).4385e96da1627886bd9a90a725bde49cb6b52b57(fragment-summary correction only; no Go code changed).bfcb02136be72d8443e14609b7ae1cbe150df1a0.The head gate is intentionally narrow: its fake seam implements only
GetPullRequest; it never reads branch protection, approval eligibility, review rows, mergeability, or CI status.Controls:
READY, rc=0;REFUSED, rc=1, naming both values;COULD-NOT-GRADE, rc=2, with no Forgejo read;COULD-NOT-GRADE, rc=2;Checks run:
go test -count=1 ./...,go vet ./...,go build ./...,bats tests(195/195),golangci-lint run --timeout=5m(0 issues),gofmt, andgit diff --check.The standalone ShellCheck sweep reports three pre-existing warnings in untouched scripts; this PR does not change those scripts.
Refs release-toolkit#1383.
This red is CODE-attributable.
At least one step started and failed, so the failure is inside the job. The log is worth reading.
Posted by
page-ci-attribution.sh(alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a⭐ Runmarker. Failed with zero markers means the container never started.⚠️ COULD NOT GRADE this failure.
The job log is missing or unreadable — Forgejo prunes them, so an older task may be ungradeable rather than clean. This is not a pass. Nothing here says whether the runner or the diff is at fault.
Posted by
page-ci-attribution.sh(alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a⭐ Runmarker. Failed with zero markers means the container never started.This red is CODE-attributable.
At least one step started and failed, so the failure is inside the job. The log is worth reading.
Posted by
page-ci-attribution.sh(alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a⭐ Runmarker. Failed with zero markers means the container never started.This red is CODE-attributable.
At least one step started and failed, so the failure is inside the job. The log is worth reading.
Posted by
page-ci-attribution.sh(alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a⭐ Runmarker. Failed with zero markers means the container never started.⚠️ COULD NOT GRADE this failure.
The job log is missing or unreadable — Forgejo prunes them, so an older task may be ungradeable rather than clean. This is not a pass. Nothing here says whether the runner or the diff is at fault.
Posted by
page-ci-attribution.sh(alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a⭐ Runmarker. Failed with zero markers means the container never started.APPROVE @
4385e96da1627886bd9a90a725bde49cb6b52b57rc=2covers four distinct diagnoses, not one bucket — driven against the live forge rather than read:Same status, four different sentences. A submit gate that cannot say why it refused gets routed around, and this one exists because a gate that refused inconveniently got loosened — so the distinctness is the property, not a nicety.
✅ And the malformed check genuinely precedes the forge read. Pointed at a nonexistent repo with a bad SHA, it names the SHA, not the missing repo — so validation is ordered before I/O rather than merely claimed to be.
Mutation run rather than read:
The arm that fires is the one that owns the behaviour, not a neighbour.
Keeping this separate from
#1277is the right call and the boundary is real:review-eligibilityanswers can my approval count, this answers is the head still the one I read. The fake seam implementing onlyGetPullRequestis what keeps that honest — it cannot accidentally grow into the other question.Status, and a correction to my own first reading
⚠️ My first pass reported
changelog-body-check / changelog body Cold-Read linteras MISSING. That was wrong, and it was my instrument./statuses?limit=50&page=1returns one page; this head has 85 status rows across 2 pages, and the Cold-Read row sits beyond the first 50. Paginating to an empty page shows it green — and task48978confirms the job ran and succeeded on this exact head.🔑 Worth stating the direction, because it decides how much else to re-check: truncation can only hide rows, so it manufactures false MISSING — never a false clean. A truncated read that reports "nothing missing" is still sound, because every required context it did see was green. Only the missing claim needed retracting, and it is retracted here.
Base is behind main (
bfcb0213vsd35d8cd2), so do not two-dot this diff, and thebase-divergencered is that same fact rather than a defect.Reviewed at
4385e96da1627886bd9a90a725bde49cb6b52b57;commit_idomitted so the read-back comes from the substrate.