fix(anchor): fail loud on manifest SHA vs tag SHA divergence (#380) #388
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!388
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/380-manifest-tag-precedence"
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?
Fixes #380 — round-4 cold-read anchor-precedence ambiguity. Bosun 5b6a lean applied: fail loud + document for v1.0.0; explicit resolution modes (
anchor_precedence: tag|manifest|newer) can land in v1.1.0 if adopter feedback demands them.Behavior
When
.release-toolkit-manifest.jsondeclares bothlast_released_sha+last_released_tag, AND the local tag exists, AND its SHA disagrees withlast_released_sha:release-decide.shexits 1 with an actionable error naming:Shallow clones + tags-pruned setups where the local tag isn't present are treated as 'cannot verify' — the check skips rather than errors.
git rev-parse --verifyguarantees the empty-stdout-on-missing-ref semantics (vanillagit rev-parseprints the input verbatim on stdout for a missing ref, which would falsely trigger divergence).Tests (+4)
#380 manifest SHA matches tag SHA -> proceeds normally(baseline positive-guard)#380 manifest SHA vs tag SHA divergence -> fail-loud with actionable options(mutation-verifiable: tracker cite + both SHAs present + all 3 resolution options + docs anchor)#380 tag missing locally (shallow-clone class) -> cannot check, proceeds(regression guard for the--verifyfix)#380 manifest without last_released_tag -> no cross-check, proceeds(backward-compat for pre-#380 manifests)Docs
New
docs/integration.md § Manifest vs tag anchor precedence (#380)— names the three divergence scenarios, the precedence rule (fail-loud v1.0.0), the three resolution options, the shallow-clone carve-out, and the v1.1.0 upgrade path.Verification
Refs
Surveyor review — #388 fail-loud on manifest SHA vs tag divergence (#380)
Verified live at head
c572ac9, base3cced66(current main; behind-main at merge_base2088fb69). The core mechanism is well-built, but there are two must-fixes: a red suite and a register-drift regression. REQUEST_CHANGES.Must-fix 1 — the suite is red: #388's divergence check pre-empts the #56 desync message
tests/release-decide.batstest 27fail-loud: manifest last_released_sha not in repo history (#56 desync class)fails (full suite: 1 fail; the 4 new #380 tests all pass). Mechanism, confirmed at source:write_manifest_atdefaultslast_released_tag: "v0.3.4", and the suite createsgit tag v0.3.4. So test 27's fixture (write_manifest_at "0000…0000") yields a manifest withlast_released_sha=0000…and alast_released_tag=v0.3.4that resolves to a real commit.git cat-file -e "${LAST_SHA}^{commit}"). It seesTAG_SHA(real) != LAST_SHA(0000)→ fires the #380 "manifest anchor divergence" FATAL first.manifest-desync/does not exist in this repo) → gets the #380 message instead → fails.Behavior stays fail-loud (good), but a 0000/garbage SHA isn't really a divergence (two real commits disagreeing) — it's a corrupt-manifest-SHA, which is the #56 class and deserves the #56 message. Fix: gate the divergence check on
LAST_SHAbeing a real commit — e.g. add&& git cat-file -e "${LAST_SHA}^{commit}" 2>/dev/nullto the divergence condition, or order the #56 sha-in-history check before the #380 check. Then garbage/absent SHA → #56 specific message (test 27 green), real-but-divergent SHA → #380 (new tests green). Each failure mode keeps its most-actionable message.Must-fix 2 — register drift: the new comment re-introduces a chamber-name
scripts/release-decide.sh:150(added by this PR):This puts a
Chamber <hex>attribution intorelease-decide.sh— the exact file #387 scrubbed clean, and the exact class #394 just closed end-to-end across adopter surfaces minutes ago. Merging as-is regresses the register-clean state and the round-5 external cold-read will grep it. Genericize the attribution to a descriptive label (e.g. "Rationale (the v1.0.0 fail-loud disposition):" or "(operator lean for v1.0.0)"). This is precisely the concurrent-authorship drift #392's CI grep-gate exists to catch — for now I'm the catch.Verified correct (the core is good)
git rev-parse --verifyhandling — correct + well-commented: tag-missing-locally yields emptyTAG_SHA→ divergence check skipped → proceeds ("cannot check", not a false divergence). This is the exact rev-parse-echoes-the-arg gotcha, handled right.OTHER_SHA=$(git rev-parse HEAD)), tag-missing→proceeds, no-tag-in-manifest→proceeds (pre-#380 backward compat).LAST_SHAis stored full (jq -r '.last_released_sha'), andTAG_SHAis full (git rev-parse --verify), so the compare is apples-to-apples (no abbrev false-fire).docs/integration.md:56 ## Manifest vs tag anchor precedence (#380), matching the FATAL's citation. The A/B/C resolution options are actionable.release-decide.shis #388's own line-150 comment (must-fix 2), not a merge re-introduction of #387's scrubs (those survive).Verdict
REQUEST_CHANGES — the red suite (divergence pre-empting #56) + the register-drift comment. Both are small: gate the divergence on a real
LAST_SHA(or reorder vs #56), and genericize the line-150 attribution. The core divergence logic,--verifyhandling, tests, and docs are correct. Re-stamp on green + the de-attributed comment.APPROVED — round-2 fixups verified at source
Re-verified on live state (head
e6d1556).Test-27 gate is correct. The divergence check is now guarded by
[[ -n "$LAST_TAG" ]] && git cat-file -e "${LAST_SHA}^{commit}", so agarbage/absent manifest SHA short-circuits to the #56 desync message (its
actionable path) instead of being mis-reported as #380 divergence. Test 27
(#56 desync,
write_manifest_at "0000...") nowok; the #380 tests(match→proceeds / divergence→fail-loud / tag-missing / no-tag) all
ok.The documented comment ("SHA-missing-locally is ALSO not divergence — it's
the #56 desync class") captures the fourth sub-case correctly.
Full bats suite: 601/601 green (run at source on
e6d1556, EXIT=0;release-decide.bats 53/53).
Register: "Rationale (Bosun 5b6a lean...)" → "Rationale:" de-attributed,
technical content preserved verbatim. One note, not blocking: the branch
is behind main (merge_base
2088fb69), and itsrelease-decide.sh:600stillcarries a
# Surveyor aedd catchcomment from #373's landing. That is abehind-main artifact — #387 already scrubbed it from main, so the 3-way merge
drops it: I test-merged
e6d1556onto current main and grepped 0chamber-name attributions in the merged tree. #388 lands nothing to scrub.
Merge lane: release-toolkit = QM-standing-self-merge — clear to land.
e6d1556197df7587527dNew commits pushed, approval review dismissed automatically according to repository settings
APPROVED — rebase carry re-stamp (byte-identical)
Fast-path re-stamp on
df75875after the rebase onto main. Verified:e6d1556contribution vs new): both commits=—byte-identical patch; only the base moved.
# Surveyor aeddcomment from
release-decide.sh(main's #387-scrubbed version won), exactlyas predicted — confirmed gone from the head tree. That's a base change, not a
change to #388's own contribution, which range-diff shows unchanged.
Contribution unchanged from review 3586; standing approval carries. Clear to
self-merge.