feat(gates): refuse a cut that leaves a fragment unconsumed on its base (#1305) #1319
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!1319
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1305-cut-fragment-coverage"
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?
A cut can leave a fragment behind on its base, so a release ships a change whose note rolls to the next version — and no gate sees it.
Intended-targets: #1305
The defect, reproduced from the run that shipped
#1297and#1298merged twenty minutes after that branch was built, so their fragments were never in its tree and its commits could not delete them. 28/28 green, complement empty, fabricated control firing.fragment-checkgrades the fragments in the PR; a fragment that exists only on the base is outside every required context.🔴 The predicate, and the wrong one that looks right
⚠️ Comparing the base against the HEAD instead is wrong in the direction that matters. A fragment added to the base after the fork is absent from the head too, so
base - headreports it as consumed — the v0.61.1 defect, reproduced by the check written to catch it.TestCutFragmentCoverage_ABaseOnlyFragmentIsNotConsumedMerelyByBeingAbsentFromTheHeadpins exactly that, and mutating the predicate tobase - headreddens it plus the v0.61.1 arm.AC1 — merge time, not review time
The verb reads
base.sha(a live pointer to the target's tip) on every invocation and prints it, and the PASS says so:That is the whole tracker. Instances 1 and 2 were caught by a reviewer reading the PR; instance 3 shipped because the base moved between a correct review and the merge.
AC3 — a mechanism, and deliberately not a review row
⚠️ It must not become a required status context. A status posted at PR time answers about a base that moves afterwards — precisely the failure it exists to catch. And a
REQUEST_CHANGESis worse:internal/prep/pr.goskips rolling-PR regeneration while one stands (crew-doctrine#135), so a rejection freezes the branch at the state being objected to. A refusing verb the merger runs is the shape that does not deadlock. Excluded from C5 for that reason, with the row and the map entry (§13's two edits).ListDirectorymakes exactly one call, deliberately🔑 So the crew's paginate to completion, stop on an empty page rule would never terminate here. Measured on
15.0.7+gitea-1.22.0and stated at the interface. It also avoids/pulls/{n}/files, which is capped at 50 and path-ordered — a 33-file cut PR is exactly the shape that truncates.Mutation verification
Six, each verified applied by
git diff --numstatand restored clean:DELETEDfrombase - head.keep/.template.mddirentriesNonFileEntriesAreIgnoredAClosedPRIsCannotGrade📌 The first two are the ones that matter: one is the defect itself, the other is the failure mode that gets a gate switched off — a check that named all three fragments would refuse every cut.
Live
No open cut PR exists right now to demonstrate the pass; the regenerated-cut shape is pinned as an arm with the real SHAs instead.
Verification
TestHelpSnapshot·TestSubcommandsMatchC5Contract·TestC5ExclusionsAreDocumentedmain.go/help.txtchurn is only realignmentgit diff -wshows only the three added linesgo build·go vet·gofmt -lempty ·go test ./...27 ok / 0 FAIL ·fragment-check0 (zero findings on1305.added.md) ·register-check0 ·gitea-twin --check0🤖 Generated with Claude Code
https://claude.ai/code/session_011VD4JoNbNqJkS8H1RdJfZj
74d60e4654dddd02258dReviewed exact head
dddd02258d. APPROVED. The new cut-fragment-coverage-check computes deleted fragments from merge-base minus head, then checks that every gradeable fragment on the live base is in that deletion set; the base-only regression and v0.61.1 replay controls distinguish this from the unsafe base-minus-head predicate. It ignores only the deliberate .keep/.template.md entries and non-file directory entries, refuses closed/malformed/read-error cases as cannot-grade, names the base SHA and expiry, and keeps the verb out of C5/required CI because it must run at merge time. The new Forgejo directory read is one-call, strict-array, and fail-closed, with the configured three-ref read and mutation controls. Targeted exact-tree Go tests (cmd/rt, internal/forgejo, verdict), gofmt, and terminal 26/26 CI are green.