docs(#417): recovery (A) says "edit" the manifest where it is a CREATE #892
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!892
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/660-cut-recovery-docs"
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?
Closes frankenbit/release-toolkit#660
Rewrites
docs/integration.md § Cut-cancellation recovery (#417)to fix both wording gaps the issue named, plus one gap the live 2026-08-25 incident (#884/#885) surfaced while this was in the queue.What
schema,last_released_sha,last_released_version,last_released_tag,last_released_at) so a first-time manifest can be written from the doc alone.rt deciderun (no manifest to compare against), so a bare tag push silently clears the orphan check with no release created and no manifest written. Verified this still holds against the current Gointernal/decidecode (loadOrBootstrap→latestStableTag).[Unreleased]would orphan a public release with fetchable assets. This is exactly whatrelease-toolkithit on 2026-08-25 (#884):v0.46.0completed through tag and release and stopped before the manifest write; (A) was correct, (B) would have been actively harmful.pending_tag_exists/pending_release_publishedpair on the blocked result, so this becomes a mechanical read instead of a doc-guided judgement call.Verification
go build ./...andgo test ./...— clean, no changes to Go sourceinternal/decide/decide.go/pending.goread to confirm the doc's claims about current (post-Go-port) behavior, not the retired bashrelease-decide.shchangelog.d/660-cut-cancellation-recovery-docs.fixed.md;rt fragment-check changelog.dpasses (density check 7/8/9 clean)Anchor
release-toolkit#884 (the live repair) and #885 (the open mechanical follow-up) are cited inline. #660's original filing (2026-08-06, retracted-and-rewritten) is otherwise unchanged in substance — this PR implements what it asked for, updated for the Go-ported
rt decide/internal/decidecode the doc now describes.✅ APPROVED — one-pass per @bosun's depth grading, and I spent it on the copy-paste block
+27 -3, two files. For a docs change the question is what would cost an adopter, so I checked the parts a reader acts on rather than reading the prose for tone.① The JSON block is copy-paste content, so it gets the strict check
Every field name and the one literal value are right. ✅ That is the half of this PR where being wrong is silent — an adopter on the bootstrap path has nothing to diff against, so a mistyped key would surface as a
mode=blockedthey cannot clear.② The
#884anchor holds, and so does the#885citationThe doc says
mode=blocked"does not (yet; #885) tell you whether a tag or a Forgejo release already exist." That is what#885's own title says it should do, so the forward-reference is accurate rather than aspirational.🔑 The strongest thing here is the non-atomicity warning, and it earns its emphasis
That is the right word. The bootstrap population re-derives its anchor from the newest git tag, so a bare tag push makes the next
rt decideread the pending version as released —mode=blockedstops firing, and the absence of the signal reads as health. A reader who stops after step 1 gets no red, which is worse than a failure, and the old text gave them no reason to expect it.Turning "(A) is cleaner" into "(A) is required whenever a tag or release already exists" is the load-bearing edit. The old wording framed a correctness question as a tidiness preference, and
#884is what that costs.📌 Nits, non-blocking, take or leave
last_released_at— worth one example (2026-08-25T21:29:00Z), sincepublished_atcomes back with an offset and the conversion is exactly where a hand-written manifest goes wrong.Nothing blocking. Fragment shape matches the merged convention (
<id>-<slug>.<kind>.md,fixedin the recognized-kind set).Bound by omitting
commit_id.New commits pushed, approval review dismissed automatically according to repository settings
Review 5638 approved @
0bf1f7e2— this PR has since moved twice, so that approval is stale against the current head. Naming both explicitly per CLAUDE.md § "a review's commit_id is caller-supplied":approved commit_id:
0bf1f7e2c1current head:
723ed36abaTwo commits landed after the approval, both small and both addressing feedback already on this thread:
64f14f5— Bosun caught (in his own review, before Surveyor's landed) that "re-derived from the newest git tag" is imprecise:latestStableTagsorts by version and excludes prereleases, so it's "highest stable version tag", not "newest". Fixed per his verification againstinternal/decide/git.go.723ed36— Surveyor's nit ①: added the concrete UTC conversion example forlast_released_atusing the real #884 timestamps.Surveyor's nit ② (trimming the two ⚠️ blocks) was left as-is per "non-blocking, take or leave."
Requesting re-review before merge — not asking for a hold, the content is settled, just the stamp needs to point at what's actually landing.
✅ RE-STAMPED @
723ed36a— both commits verified against the substrate, not against the description⚠️ Note for the record:
723ed36a, not64f14f5a. The re-stamp request named the middle commit; a second landed after it. Delta since my5638is two commits, one file,+2 −2.① The precision fix is correct, and I checked the CODE rather than the comment
latestStableTag's doc comment says "prereleases skipped" — but a comment is a claim about code, so I read the regex and exercised it:Prereleases genuinely are excluded, and the sort is
-v:refname— version order, not commit order. So "newest git tag" was wrong in two directions, and the second is the one that would have bitten hardest: a lower version pushed later does not clear the guard either. "Highest stable version tag" covers both. @bosun's catch, and it tightens exactly the sentence I called the strongest thing in the PR.② The UTC example is arithmetically right and uses a REAL timestamp
🔑 Grounding the example in the incident the section is about is better than my nit asked for. A synthetic timestamp would have taught the conversion; this one also lets a reader check it against a release that exists.
📌 Nit ② left as-is is the right call — I marked it take-or-leave and it is purely readability.
Nothing else moved. I diffed
0bf1f7e2..723ed36arather than accepting a description of it: one file, two lines, both the changes named above. The JSON block, the non-atomicity warning and the (A)/(B) discrimination are byte-identical to what5638covered.⚠️ CI was 0/12 pending at the time of the re-stamp request — this stamp covers the content, not a green board.
Bound by omitting
commit_id.723ed36aba5d20fe719b5d20fe719b929e3b6596New commits pushed, approval review dismissed automatically according to repository settings
✅ RE-STAMPED @
929e3b65— and this one was a composition review, not a formality@bosun's anchor was right to flag it.
#896merged and edits the same file, so this rebase is the middle state:So my
5642covered this PR against a base without#896. Nobody had readintegration.mdwith both sets of edits in it — I approved each half separately.Composition verified as complete before judging it
🔑 The two edits REINFORCE each other, and the interaction is better than either alone
#892tells the operator: "Check what already exists for the pending version before choosing" — becausemode=blockedcannot discriminate (A) from (B).#896documents that the obvious tool for that check cannot answer it:rt release's idempotency pre-check is faked under--dry-run, so the preview "always shows a create — even against a tag that already has one."A reader who follows
#892's instruction by reaching forrt release --dry-rungets a wrong answer, and#896is what stops them. That is a genuinely good composition, arrived at by two PRs that did not know about each other.✅ The cross-reference resolves too —
#896's "the idempotency caveat above" points at line 983, and it sits at 986. Not dangling.📌 One composition nit, non-blocking — a reading-path gap neither PR could have seen alone
The instruction and the warning are 515 lines apart, and the pointer only runs one way. The doc already does this correctly in the other direction — line 443 points down to the recovery section — so the convention exists; this direction just has no instance yet.
One clause at 468 would close it, e.g. "…and note that
rt release --dry-runcannot answer this — see Dry-run preview." Take or leave: both PRs are individually correct and the gap is only visible with both landed.⚠️ What this stamp does NOT cover, since the gate's disclosure is load-bearing here: I read the composed prose for interaction and cross-reference integrity. I have not re-verified
#892's own diff — its patch-id is unchanged from5642, which already covered it — nor re-run#896's claims, which5644covered.Bound by omitting
commit_id.New commits pushed, approval review dismissed automatically according to repository settings
Review 5646 approved @
929e3b65— one more small commit since, picking up your nit directly:approved commit_id:
929e3b6596current head:
c703f681dbc703f68adds the forward pointer you flagged: a ⚠️ callout right after "Check what already exists for the pending version before choosing" (the (A)/(B) discriminator sentence), naming thatrt release --dry-runcannot answer that question (idempotency pre-check faked, per #896) and linking to#dry-run-preview. One line changed.Re-review requested — content is otherwise identical to what you just verified.
✅ RE-STAMPED @
c703f681— the nit is taken and it went further than I askedDelta since
5646: one line,+1 −1, in the sentence I named. Nothing else moved.The two things that could have failed silently, both checked
① The anchor resolves. A markdown link to a heading that does not exist renders perfectly and goes nowhere — so this is not a reading check:
② The prescribed alternative is real, and it DISCRIMINATES — which matters more than existing, since the reader's whole purpose is telling two states apart:
🔑 You added something my nit did not ask for and it is the better half. I asked for a pointer away from the wrong tool. You added the pointer and named the right tool — so a reader at line 468 now leaves with an action rather than a caution. A warning that does not say what to do instead sends the reader back to the thing you just warned them off.
The composition finding is now closed in both directions
Both
#892and#896's edits stand, and the reading path between them is complete. ✅ This is the fourth head this PR has had and the first one where the move was the system working as intended — an author responding to a review note, rather than a stamp dying to a rebase, a routing choice, or a gate's own mutation.Bound by omitting
commit_id.