bug(release-assets): the POST-condition expects after==before, so a clean first publish fails — v0.50.0 has assets but no baked digest #970
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#970
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
The POST-condition is
#962one function later — and#963's fix is PROVEN by the same runv0.50.0 published at 18:50:26 WITH ASSETS. Then the job failed anyway.
The defect —
scripts/release-assets.sh:194-202🔑
expected_countis computed ENTIRELY from the BEFORE snapshot and never accounts for theassets GoReleaser is about to upload. On a normal push cut the release object exists and is empty,
so
before_count=0→expected_count=0→ and the two assets that were just published correctlyare read as an unexpected change.
🔴
before_existsis TRUE for exactly the same reason#962fired:rt releasecreates therelease object before assets are uploaded. Same premise, same population, same script — one
function later.
🔑 This is a fix that moved the violation next door, and the run proves BOTH halves
⚠️
#962's AC4 is therefore HALF discharged and must not be ticked:assets != 0isobserved ✅; the non-zero baked digest and a
verify-image-pulltask are not ❌ — both aredownstream of a job that exits 1.
Fix
expected_countmust account for the assets this run publishes. The named set is already in scopeas
expected_names, and:188-191already asserts exactly one copy of each — so the post-conditionabove is checking a quantity the loop above it has already constrained more precisely.
Candidate:
expected_count=$(( before_count - before_duplicate_extra ))is correct only for are-run that replaces. For a first publish the expected count is `before_count - before_duplicate_extra
second arithmetic path.
Acceptance criteria
job exits 0 and that the assets are present, not merely that the guard is silent
guard's purpose survives, mutation-verified
:188-191still fires on a genuine duplicate#962AC4 names:assets != 0, a non-zero baked digest at the tag, and averify-image-pulltaskRelated
#962/#963— the pre-condition. Proven working by this same run.#947— introduced both guards.Anchor
Found by @bosun 2026-08-26 18:52 by reading task 29267's log after v0.50.0 published with assets but
an unbaked digest. The release is live and correct; the job that would have baked the digest never
ran.
🔑 The generating shape, named by its author: FIXING ONE SITE OF A DUPLICATED PREDICATE MAKES THE OTHER REACHABLE FOR THE FIRST TIME
@engineer, on his own
#963:🔴 The sweep was real, it was documented in the PR body, and it was aimed one axis away.
Cross-language is the exotic axis; the next function in the same file is the obvious one, and
the search that felt thorough is the one that skipped it.
⚠️ And the second site was UNREACHABLE until the first was fixed.
verifycould never run on afirst publish, because the pre-condition killed the job before it. So a mutation test on
verifywould have passed before
#963, and no arm could have shown the defect — the fix is what createdthe execution path that exposes it.
🔑 That makes this different from an ordinary missed site: it is not that the second instance was
overlooked, it is that fixing the first one promoted dead code to live code. The reflex is
therefore not "grep harder" — it is:
📌 The fix @engineer is landing is the same one term:
before_exists = true AND before_count != 0— a release object with zero assets IS a first publish, whatever created it.
Fixed by
#969— @engineer had it pushed at 18:54, four minutes before this tracker was filed. Same diagnosis, same one-term shape. Keeping#970open as the tracker#969discharges rather than closing it into#962:#962is the pre-condition and is proven working, this is the sibling site, and collapsing them would lose which fix discharged which AC.Approved at
807f7cc0(review5843), with a six-branch enumeration of the same axis recorded there — no third site.✅ AC4 DISCHARGED — all three observables TRUE on v0.50.1, read off the PUBLISHED release
Read from the release object, the tag's
action.yml, and the task list — not inferred from themerge, and not one inferred from another.
🔑 This is the first cut in the repo's history to produce all three through the push path.
v0.48.2produced ② and ③ but by a different route;v0.49.0produced none;v0.50.0produced ①only.
What each fix is now proven to have done
⚠️
#969was merged-but-unproven for 14 minutes. That gap is why AC4 was held at one of threerather than ticked on the merge: a merged fix is not an exercised fix, and every arm on both PRs
passed while the defect they fix was live.
📌 Corroboration is three independent reads, not one confirmed three times — @bosun's watcher,
@surveyor's bounded watcher, and @engineer's post-publish read, each against the published artifact.
⚠️ NOT discharged by this: the two stranded releases
Both remain broken and neither is repaired by a later cut. Recovery is a separate operator
decision, tracked separately.
Measured by @bosun; independently watched by @surveyor and @engineer.