fix(fragment-check): recognize the release cut as fragment-consuming (#1051) #1052
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
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1052
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1051-cut-consumed-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?
Refs frankenbit/release-toolkit#1051
What
#1033's--diff-filter=dcorrectly excludes a PR that deletes a fragmentand adds nothing from
fragment-present. The rolling cut PR deletesfragments as its entire purpose — consuming them into
CHANGELOG.md—and is the one legitimate member of that same population. With the
deletions filtered out, its change set was empty and
fragment-coveragerefused it as
forgotten. v0.56.0 could not cut.Measured on the live
#1043:Fix — the invariant, not where the cut happens
Bosun's framing, taken directly: the predicate cannot separate "deleted a
fragment and provided nothing" from "consumed the fragments into the
CHANGELOG" — and the second is the release mechanism itself.
Took the first of the three candidates on the tracker — deletions +
CHANGELOG.mdmodified describes the actual invariant; a branch-nameexemption or a synthetic
no-changelogdeclaration both encode where thecut happens rather than what a cut is.
Added a third
FragmentCoverageState,cut-consumed.FragmentCoveragenow takes two more inputs —
deletedFragments []stringandchangelogChanged bool— neither derivable from the existing change set:--diff-filter=dalready excludes deletions from it, andCHANGELOG.mdisoutside
fragmentsDirentirely. Both are required for thecut-consumedverdict itself (that part of the predicate is unchanged from the first
push):
CHANGELOG.mdchanged alone (an unrelated doc edit) is not a cut,and deletions alone with no
CHANGELOG.mdchange is exactly#1033'spopulation and must still fail.
The workflow computes the two new signals from the same diff already in
scope:
--diff-filter=D(uppercase — deletions only, the exact complementof
#1033's-d) recovers what that filter drops, and aCHANGELOG.md-scoped diff answers whether the cut actually happened.
The env-var contract — this is the part that changed mid-PR
Shipped, as of
8562281— the first push made all four coverage inputsrequired-or-refuse; this pushed back to a narrower requirement after
@bosun's diagnosis below:
Why the pivot. This repo's own toolkit-self gate pulls the reusable
workflow from
@main, which lags the branch that introducesDELETED_FILES/CHANGELOG_CHANGED— so requiring them made this PR's ownrtrefuse its ownlegitimate-nonecontrol, and#1043(the v0.56.0rolling cut) inherited the identical mismatch. A PR that tightens a
contract cannot satisfy the pre-tightening caller.
Why degrade rather than split the landing into two PRs. Splitting
avoids the mismatch too, but costs a full review/merge cycle while the
release is actively blocked. Degrading costs nothing on correctness: the
lenient fallback (no cut signal ⇒ pre-
#1051behavior) is byte-identicalto what
#1033-erartalready did before this tracker existed, so itreopens no wrong-pass risk — a caller too old to supply the two new
signals simply cannot benefit from the
cut-consumedexemption, exactly asbefore this PR.
Directly reproduced the bootstrap scenario against the built binary before
touching the fix:
FRAGMENT_CHECK_COVERAGE=requiredwith onlyCHANGED_FILES/PR_BODYset and the other two entirely absent gaverc=2pre-fix,
rc=0post-fix.The AC's own requirement: arms that fail on each other's fixture
Three layers, each pairing the cut-shape fixture against
#1033's owndelete-only fixture adversarially, so a fix for the fix cannot silently
reopen
#1033:internal/gates/fragment_coverage_test.go—TestFragmentCoverageCutConsumed: cut shape passes; same deletions withchangelogChanged=falsestill fails forgotten (#1033's exactpopulation);
CHANGELOG.mdchanged alone with no deletions still fails;an added fragment wins over a coincidental cut signal; deletions outside
fragmentsDirdon't count. Unaffected by the env-var pivot — this layernever dealt in env vars.
cmd/rt/fragment_check_test.go—TestFragmentCheckCutConsumedexercisesthe cut-vs-delete-only pairing through the CLI env-var wiring.
TestFragmentCheckCoverageRefusesUnavailableMetadatacovers only theoriginal two required vars (
CHANGED_FILES,PR_BODY).TestFragmentCheckCoverageCutSignalsAreOptionalpins the exact bootstrapincident — both new vars entirely unset (asserted via
LookupEnv, notmerely empty) must not refuse, and legitimate-none must still pass.
TestFragmentCheckChangelogChangedRejectsMalformedValuepins that apresent malformed value still refuses even though absent is fine.
tests/workflows.bats— two new arms extracting the actual shipped--diff-filter=DandCHANGELOG.mddiff lines (same_fragment_check_diff_linepattern#1028's arm established, not ahand-copied duplicate), reproducing the tracker's own 7-fragment fixture
for the cut case and a companion delete-only-with-untouched-
CHANGELOG.mdfixture for
#1033's case.Two separate mutation-verifications, one per layer of the fix:
of the reusable workflow, confirmed both new bats arms redden (on the
missing extraction), restored.
two new vars in
rt, confirmedTestFragmentCheckCoverageCutSignalsAreOptionalreddens with the exact old
rc=2refusal, restored.(Caught and fixed while writing the CLI test: my first draft of
TestFragmentCheckCutConsumedhardcodedchangelog.d/...deleted pathsagainst a real
t.TempDir()dir argument — same prefix-matching mismatchchangedFragmentPathsguards against — and failed for the wrong reasonuntil I built the paths from the actual tempdir, matching the established
pattern the "changed fragment passes" sub-test above it already uses.)
Verification
go build ./.../go vet ./.../go test ./... -count=1cleanbats tests/*.bats— 146/146 passgofmt -lclean on everything touched (pre-existing, unrelated drift oncmd/rt/main_test.go, confirmed viagit stashagainst a cleanmaincheckout — not touched by this PR)
rt fragment-check changelog.dPASS on this repo's own fragmentsbefore and after the fix
REQUEST_CHANGES — exact head
856228182e.The implementation and controls are otherwise sound. I independently ran go test ./... -count=1, go vet ./..., go build ./..., bats tests (146/146), and git diff --check. Runtime controls gave cut shape (deleted fragments + CHANGELOG_CHANGED=true) rc=0 cut-consumed; the same deletions with false and CHANGELOG-only both rc=1 forgotten; both new vars completely unset with an old-caller no-changelog body rc=0; present malformed CHANGELOG_CHANGED (yes and empty) rc=2. Removing the new workflow computations makes both new #1051 bats arms red; a mutant that re-requires the optional vars makes the old-caller and optional tests red.
I agree with the degrade-over-split choice: an old caller cannot recognize a cut, but it retains the pre-#1051 delete-only refusal and cannot silently pass it; a new caller gets cut-consumed only from both signals. Strict validation of a present CHANGELOG_CHANGED value is the safe direction.
Blocker: the PR description is stale and contradicts this shipped contract. Its lines 35 and 43-45 say the deleted-files and CHANGELOG_CHANGED inputs are required/refuse when absent, and lines 61-64 describe four now-required env vars. HEAD
8562281deliberately makes those two optional, and changelog.d/1051.fixed.md plus cmd/rt/fragment_check.go document that. Please update the PR body (or add a clearly superseding correction) before approval; otherwise the durable review record directs a future maintainer to restore the self-gate failure this follow-up fixes.APPROVED — re-bound to exact head
856228182eafter the PR description correction. No code re-review was needed because the head is unchanged.The shipped contract is now accurately recorded: CHANGED_FILES/PR_BODY remain required-or-refuse; DELETED_FILES/CHANGELOG_CHANGED are optional, with absent meaning no cut signal; a present malformed CHANGELOG_CHANGED still refuses. I agree with degrade-over-split: old callers retain pre-#1051 delete-only refusal and cannot silently pass.
Exact-head verification remains: go test/vet/build pass, Bats 146/146, diff-check pass; cut, delete-only, CHANGELOG-only, unset-optional, malformed-value, and workflow-mutation controls all behaved as expected.