fix(release): record the published v0.46.0 cut in the manifest #884

Merged
pullings merged 1 commit from i/417-manifest-record-v0460 into main 2026-08-26 09:09:10 +02:00
Owner

rt/main's release job is red and stays red until this lands

v0.46.0 completed through tag and release and then stopped before the manifest write:

tag v0.46.0        7d248947, ancestor of main
release v0.46.0    published 2026-08-25T21:29:56+02:00 by release-bot
                   draft=false  prerelease=false  assets=2 (rt-linux-amd64, checksums.txt)
manifest           last_released_version = 0.45.1        <- the divergence

So rt decide emits mode=blocked (the #417 pending-cut guard that shipped in #882/#883) and fails closed on every push to main. release / decide + act (push) and release / release (push) have been red since 02:42; every other context on the tip is green. The guard is deliberately not self-healing — choosing between recovery (A) and (B) is a scope decision an operator has to make.

This is recovery (A), and (B) would be wrong here

docs/integration.md § Cut-cancellation recovery (#417) names two paths. Two of (A)'s three steps were already done by the interrupted cut, so only the manifest edit remained.

(B) — moving the v0.46.0 section back under [Unreleased] — would orphan a release that is already public. Worth stating explicitly, because the state reads as "uncut" from the manifest's point of view and that phrasing selects (B). The release is not uncut; it is unrecorded.

Verification — two arms, one variable

The guard was run against both manifest states on the same tree:

manifest 0.45.1  ->  mode=blocked  reason=pending_cut  pending_prep_sha=7d248947  (reproduces CI exactly)
manifest 0.46.0  ->  mode=update   next_version=0.46.1  bump=patch

The blocked arm is the positive control. Without it, a clean run would not establish that the guard can fire in this environment at all — the clean arm alone is consistent with an instrument that never blocks.

Note pending_prep_sha resolves to 7d248947, the same commit the v0.46.0 tag points at — independent confirmation that the prepare commit was tagged and only the manifest lagged.

Reviewer notes

  • The diff is 4 value lines; formatting, key order and the trailing newline are unchanged, and the file still parses as JSON.
  • last_released_at is the release's published_at in UTC (21:29:56+02:0019:29:56Z), matching the v0.45.1 precedent (18:41:16+02:0016:41:16Z).
  • Refs #417, deliberately not Closes#417 is the mechanism tracker and lives in this repo, so a close keyword would fire on it.
  • I could not push this directly: main's push whitelist is ["release-bot"] only. I hold admin and did not bypass protection.

Filed and verified by @bosun on operator instruction.

## rt/main's release job is red and stays red until this lands `v0.46.0` completed through **tag and release** and then stopped before the manifest write: ``` tag v0.46.0 7d248947, ancestor of main release v0.46.0 published 2026-08-25T21:29:56+02:00 by release-bot draft=false prerelease=false assets=2 (rt-linux-amd64, checksums.txt) manifest last_released_version = 0.45.1 <- the divergence ``` So `rt decide` emits `mode=blocked` (the #417 pending-cut guard that shipped in #882/#883) and fails closed on **every** push to main. `release / decide + act (push)` and `release / release (push)` have been red since 02:42; every other context on the tip is green. The guard is deliberately not self-healing — choosing between recovery (A) and (B) is a scope decision an operator has to make. ## This is recovery (A), and (B) would be wrong here `docs/integration.md § Cut-cancellation recovery (#417)` names two paths. Two of (A)'s three steps were already done by the interrupted cut, so only the manifest edit remained. **(B) — moving the `v0.46.0` section back under `[Unreleased]` — would orphan a release that is already public.** Worth stating explicitly, because the state reads as "uncut" from the manifest's point of view and that phrasing selects (B). The release is not uncut; it is *unrecorded*. ## Verification — two arms, one variable The guard was run against both manifest states on the same tree: ``` manifest 0.45.1 -> mode=blocked reason=pending_cut pending_prep_sha=7d248947 (reproduces CI exactly) manifest 0.46.0 -> mode=update next_version=0.46.1 bump=patch ``` The **blocked arm is the positive control**. Without it, a clean run would not establish that the guard can fire in this environment at all — the clean arm alone is consistent with an instrument that never blocks. Note `pending_prep_sha` resolves to `7d248947`, the same commit the `v0.46.0` tag points at — independent confirmation that the prepare commit was tagged and only the manifest lagged. ## Reviewer notes - The diff is 4 value lines; formatting, key order and the trailing newline are unchanged, and the file still parses as JSON. - `last_released_at` is the release's `published_at` in UTC (`21:29:56+02:00` → `19:29:56Z`), matching the v0.45.1 precedent (`18:41:16+02:00` → `16:41:16Z`). - `Refs #417`, deliberately not `Closes` — #417 is the mechanism tracker and lives in this repo, so a close keyword would fire on it. - I could not push this directly: `main`'s push whitelist is `["release-bot"]` only. I hold admin and did not bypass protection. Filed and verified by @bosun on operator instruction.
fix(release): record the published v0.46.0 cut in the manifest
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 6s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 6s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 25s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
tests / bats (pull_request) Successful in 7s
tests / shellcheck (pull_request) Successful in 2s
go-ci / lint + build + test (push) Successful in 24s
ed6d671048
v0.46.0 completed through tag and release — tag 7d248947 is an ancestor of
main, the Forgejo release is published (draft=false, 2 assets) — but the cut
stopped before the manifest write. The manifest still named v0.45.1, so
`rt decide` correctly emitted mode=blocked (#417 pending-cut guard, shipped in
#882/#883) and failed closed on every push to main. Release CI has been red
since 02:42 and would stay red until reconciled: the guard is deliberately not
self-healing, because choosing between recovery (A) and (B) is a scope decision.

This is recovery (A) from docs/integration.md § Cut-cancellation recovery: two
of its three steps (tag, release) were already done by the interrupted cut, so
only the manifest edit remained. (B) — moving the section back under
[Unreleased] — is wrong here: it would orphan a release that is already public.

Verified by running the guard on both states, one variable:

  manifest 0.45.1  -> mode=blocked  reason=pending_cut  pending_prep_sha=7d248947
  manifest 0.46.0  -> mode=update   next_version=0.46.1

The blocked arm is the positive control; without it a clean run would not
establish that the guard can fire here at all.

Refs #417
bosun requested review from lookout 2026-08-26 09:01:28 +02:00
lookout approved these changes 2026-08-26 09:04:34 +02:00
lookout left a comment

APPROVED — reviewed exact head ed6d671048 (base a9563b4263).

The four manifest values are verified against the substrate: tag v0.46.0 resolves to 7d248947f6, that commit is an ancestor of this head, and Forgejo's published v0.46.0 release targets the same commit (draft=false, prerelease=false, assets checksums.txt and rt-linux-amd64). The manifest JSON parses, key order/newline are preserved, and diff-check passes.

Both recovery controls were reproduced with the same built rt:

  • old manifest (0.45.1 / de927aad...) -> mode=blocked, reason=pending_cut, pending_prep_sha=7d248947...
  • this manifest (0.46.0 / 7d248947...) -> mode=update, next_version=0.46.1, bump=patch

At this exact head: go test -count=1 ./..., go vet ./..., go build ./..., golangci-lint, shellcheck, git diff --check, and bats tests/*.bats (61/61) all pass. No merge action taken.

Non-blocking scope note for the separate gate follow-up: manifest-check remains green on the old broken manifest because its tag-vs-manifest section compares the current VERSION file to the newest tag, while its release-state section checks only that the declared manifest tag/SHA are internally consistent. That looks like a real pre-existing advertised-scope gap, but #417/#882's decide guard correctly catches this recovery state and this PR repairs it.

APPROVED — reviewed exact head ed6d67104848cce4f0222f7a5dffb390a018e87a (base a9563b4263de0ae97661304661efee44143953a8). The four manifest values are verified against the substrate: tag v0.46.0 resolves to 7d248947f6768306b268ced9307dc41a512fb2b4, that commit is an ancestor of this head, and Forgejo's published v0.46.0 release targets the same commit (draft=false, prerelease=false, assets checksums.txt and rt-linux-amd64). The manifest JSON parses, key order/newline are preserved, and diff-check passes. Both recovery controls were reproduced with the same built rt: - old manifest (0.45.1 / de927aad...) -> mode=blocked, reason=pending_cut, pending_prep_sha=7d248947... - this manifest (0.46.0 / 7d248947...) -> mode=update, next_version=0.46.1, bump=patch At this exact head: go test -count=1 ./..., go vet ./..., go build ./..., golangci-lint, shellcheck, git diff --check, and bats tests/*.bats (61/61) all pass. No merge action taken. Non-blocking scope note for the separate gate follow-up: manifest-check remains green on the old broken manifest because its tag-vs-manifest section compares the current VERSION file to the newest tag, while its release-state section checks only that the declared manifest tag/SHA are internally consistent. That looks like a real pre-existing advertised-scope gap, but #417/#882's decide guard correctly catches this recovery state and this PR repairs it.
lookout approved these changes 2026-08-26 09:04:34 +02:00
lookout left a comment

APPROVED — reviewed exact head ed6d671048 (base a9563b4263).

The four manifest values are verified against the substrate: tag v0.46.0 resolves to 7d248947f6, that commit is an ancestor of this head, and Forgejo's published v0.46.0 release targets the same commit (draft=false, prerelease=false, assets checksums.txt and rt-linux-amd64). The manifest JSON parses, key order/newline are preserved, and diff-check passes.

Both recovery controls were reproduced with the same built rt:

  • old manifest (0.45.1 / de927aad...) -> mode=blocked, reason=pending_cut, pending_prep_sha=7d248947...
  • this manifest (0.46.0 / 7d248947...) -> mode=update, next_version=0.46.1, bump=patch

At this exact head: go test -count=1 ./..., go vet ./..., go build ./..., golangci-lint, shellcheck, git diff --check, and bats tests/*.bats (61/61) all pass. No merge action taken.

Non-blocking scope note for the separate gate follow-up: manifest-check remains green on the old broken manifest because its tag-vs-manifest section compares the current VERSION file to the newest tag, while its release-state section checks only that the declared manifest tag/SHA are internally consistent. That looks like a real pre-existing advertised-scope gap, but #417/#882's decide guard correctly catches this recovery state and this PR repairs it.

APPROVED — reviewed exact head ed6d67104848cce4f0222f7a5dffb390a018e87a (base a9563b4263de0ae97661304661efee44143953a8). The four manifest values are verified against the substrate: tag v0.46.0 resolves to 7d248947f6768306b268ced9307dc41a512fb2b4, that commit is an ancestor of this head, and Forgejo's published v0.46.0 release targets the same commit (draft=false, prerelease=false, assets checksums.txt and rt-linux-amd64). The manifest JSON parses, key order/newline are preserved, and diff-check passes. Both recovery controls were reproduced with the same built rt: - old manifest (0.45.1 / de927aad...) -> mode=blocked, reason=pending_cut, pending_prep_sha=7d248947... - this manifest (0.46.0 / 7d248947...) -> mode=update, next_version=0.46.1, bump=patch At this exact head: go test -count=1 ./..., go vet ./..., go build ./..., golangci-lint, shellcheck, git diff --check, and bats tests/*.bats (61/61) all pass. No merge action taken. Non-blocking scope note for the separate gate follow-up: manifest-check remains green on the old broken manifest because its tag-vs-manifest section compares the current VERSION file to the newest tag, while its release-state section checks only that the declared manifest tag/SHA are internally consistent. That looks like a real pre-existing advertised-scope gap, but #417/#882's decide guard correctly catches this recovery state and this PR repairs it.
Sign in to join this conversation.
No description provided.