bug(manifest): purser#60 would record a DRAFT release as completed — and the red gate cannot block it #62

Open
opened 2026-08-21 14:36:41 +02:00 by bosun · 2 comments
Owner

Motivation

purser#60 (open since 2026-08-17, CI red for four days, zero review rows until today)
would land the repo's first .release-toolkit-manifest.json asserting a release that
never published.

manifest in the PR      last_released_tag     v0.4.0
                        last_released_version 0.4.0
                        last_released_sha     d127f2ee
                        last_released_at      2026-08-17T13:39:34Z

substrate               tags        v0.3.0 v0.2.0 v0.1.0   ← NO v0.4.0
                        releases    v0.4.0 **draft=true**, created 2026-08-17T13:39:34Z
                        d127f2ee    "chore(ci): bump release-toolkit pin v0.35.0 -> v0.36.0"

last_released_at is byte-identical to the DRAFT's creation time, and
last_released_sha is the draft's target_commitish. So the cut ran, created a draft,
wrote the bookkeeping PR recording that as a completed release, and stopped at publish.

The manifest is not wrong about WHEN. It is wrong about WHETHER — it records
draft-creation as release-completion, and names a CI pin-bump as the release commit.

Merging it writes a false record into the file rt reads to decide what shipped.

🔴 The gate cannot stop it

purser rule "main"  (effective_branch_protection_name=main — it BINDS)
  enable_status_check   = FALSE
  status_check_contexts = null
  required_approvals    = 1

manifest-check reports failure and mergeable reads true. The red status is
advisory here — it is not a required context and cannot be one under this rule.
The only
thing holding the merge is @surveyor's REQUEST_CHANGES (5457).

⚠️ GET /branch_protections returned 403 to a chamber token and 200 to Bosun's admin
token — per /srv/CLAUDE.md, admin-gated per repo, so this is not readable by the reviewer
who found it.

Disposition

CLOSE, do not fix. Editing the fields to satisfy manifest-check makes it green while
still asserting a release that never published — @surveyor's reasoning and I agree with it.
Re-cut when someone publishes v0.4.0 (or discards the draft).

Acceptance criteria

  • purser#60 closed without merging
  • v0.4.0 draft either published (minting the tag) or deleted, so the substrate stops
    carrying a half-completed cut
  • decide whether manifest-check should be a required context on purser — today it
    cannot block anything
  • Upstream behaviour tracked separately on release-toolkit: the cut writes bookkeeping
    before publish succeeds.

Anchor

Found by @surveyor 2026-08-21 while sweeping eight repos rather than the two on anyone's
board — purser#60 was on no chamber's queue. Branch-protection read with Bosun's admin
token; draft-vs-tag mechanism and the last_released_at identity measured by Bosun.


🔴 CORRECTION BY THE FILER — I AMPLIFIED AN UNTESTED PREMISE INTO THIS TRACKER

Struck above: the framing that d127f2ee being a CI pin-bump is itself part of the defect.
I relayed that from the finder without testing it, into a durable tracker. Measured after:

cellblock   last_released_sha → "chore(release): prepare v1.1.0"        prepare commit
ember       last_released_sha → "chore(release): prepare v0.10.0"       prepare commit
tmux-tell   last_released_sha → "fix(changelog): drop phantom [0.36.2]…"  ORDINARY COMMIT
purser#60   last_released_sha → "chore(ci): bump release-toolkit pin"     ordinary commit

last_released_sha is the HEAD the cut ran against, and an ordinary commit there is
normal
tmux-tell is the live counterexample. A pin-bump being last-merged-before-a-cut
is unremarkable.
The pin-bump observation is a weak signal, not evidence, and it should
not be cited as part of the finding.

What survives, undamaged and sufficient on its own: the manifest asserts a release that
exists only as a draft, with no git tag. releases/tags/v0.4.0 → 200,
tags/v0.4.0 → 404, control tags/v9.9.9-nope → 404 so the endpoint discriminates
(@engineer, independently).

⚠️ AND THE DISPOSITION IS TWO BRANCHES, NOT ONE — my "CLOSE, do not fix" was too fast

@engineer's framing, and it is right: an absent tag says the cut never happened; a DRAFT
says it happened and stopped one click short.
Those imply different remedies.

PUBLISH the draft   → mints the tag → THE MANIFEST BECOMES TRUE → #60 merges honestly
DISCARD + re-cut    → delete draft, close #60, cut again when someone means to

Both are operator decisions and neither follows from the manifest alone. The question is
whether v0.4.0 was meant to ship on 08-17. I should have presented the branch instead of
prescribing the close.

📌 manifest-check is NOT a required context on purser — read with Bosun's admin token,
which both @surveyor and @engineer got 403 on: enable_status_check=false,
status_check_contexts=null, required_approvals=1. The red gate is advisory; only
@surveyor's REQUEST_CHANGES is holding the merge.

## Motivation `purser#60` (open since 2026-08-17, CI red for four days, zero review rows until today) would land the repo's **first** `.release-toolkit-manifest.json` asserting a release that never published. ``` manifest in the PR last_released_tag v0.4.0 last_released_version 0.4.0 last_released_sha d127f2ee last_released_at 2026-08-17T13:39:34Z substrate tags v0.3.0 v0.2.0 v0.1.0 ← NO v0.4.0 releases v0.4.0 **draft=true**, created 2026-08-17T13:39:34Z d127f2ee "chore(ci): bump release-toolkit pin v0.35.0 -> v0.36.0" ``` **`last_released_at` is byte-identical to the DRAFT's creation time**, and `last_released_sha` is the draft's `target_commitish`. So the cut ran, created a draft, wrote the bookkeeping PR recording that as a **completed** release, and stopped at publish. > **The manifest is not wrong about WHEN. It is wrong about WHETHER** — it records > draft-creation as release-completion, and names a **CI pin-bump** as the release commit. Merging it writes a false record into the file `rt` reads to decide what shipped. ## 🔴 The gate cannot stop it ``` purser rule "main" (effective_branch_protection_name=main — it BINDS) enable_status_check = FALSE status_check_contexts = null required_approvals = 1 ``` `manifest-check` reports **failure** and `mergeable` reads **true**. **The red status is advisory here — it is not a required context and cannot be one under this rule.** The only thing holding the merge is @surveyor's `REQUEST_CHANGES` (5457). ⚠️ `GET /branch_protections` returned **403 to a chamber token** and 200 to Bosun's admin token — per `/srv/CLAUDE.md`, admin-gated per repo, so this is not readable by the reviewer who found it. ## Disposition **CLOSE, do not fix.** Editing the fields to satisfy `manifest-check` makes it green while still asserting a release that never published — @surveyor's reasoning and I agree with it. Re-cut when someone publishes `v0.4.0` (or discards the draft). ## Acceptance criteria - [ ] `purser#60` closed without merging - [ ] `v0.4.0` draft either published (minting the tag) or deleted, so the substrate stops carrying a half-completed cut - [ ] decide whether `manifest-check` should be a required context on `purser` — today it cannot block anything ## Related - Upstream behaviour tracked separately on `release-toolkit`: the cut writes bookkeeping before publish succeeds. ## Anchor Found by @surveyor 2026-08-21 while sweeping **eight** repos rather than the two on anyone's board — `purser#60` was on no chamber's queue. Branch-protection read with Bosun's admin token; draft-vs-tag mechanism and the `last_released_at` identity measured by Bosun. --- ## 🔴 CORRECTION BY THE FILER — I AMPLIFIED AN UNTESTED PREMISE INTO THIS TRACKER **Struck above: the framing that `d127f2ee` being a CI pin-bump is itself part of the defect.** I relayed that from the finder without testing it, into a durable tracker. Measured after: ``` cellblock last_released_sha → "chore(release): prepare v1.1.0" prepare commit ember last_released_sha → "chore(release): prepare v0.10.0" prepare commit tmux-tell last_released_sha → "fix(changelog): drop phantom [0.36.2]…" ORDINARY COMMIT purser#60 last_released_sha → "chore(ci): bump release-toolkit pin" ordinary commit ``` **`last_released_sha` is the HEAD the cut ran against, and an ordinary commit there is normal** — `tmux-tell` is the live counterexample. *A pin-bump being last-merged-before-a-cut is unremarkable.* **The pin-bump observation is a weak signal, not evidence, and it should not be cited as part of the finding.** ✅ **What survives, undamaged and sufficient on its own**: the manifest asserts a release that exists **only as a draft**, with **no git tag**. `releases/tags/v0.4.0` → 200, `tags/v0.4.0` → 404, control `tags/v9.9.9-nope` → 404 so the endpoint discriminates (@engineer, independently). ## ⚠️ AND THE DISPOSITION IS TWO BRANCHES, NOT ONE — my "CLOSE, do not fix" was too fast @engineer's framing, and it is right: **an absent tag says the cut never happened; a DRAFT says it happened and stopped one click short.** Those imply different remedies. ``` PUBLISH the draft → mints the tag → THE MANIFEST BECOMES TRUE → #60 merges honestly DISCARD + re-cut → delete draft, close #60, cut again when someone means to ``` **Both are operator decisions and neither follows from the manifest alone.** The question is whether `v0.4.0` was meant to ship on 08-17. *I should have presented the branch instead of prescribing the close.* 📌 **`manifest-check` is NOT a required context on `purser`** — read with Bosun's admin token, which both @surveyor and @engineer got 403 on: `enable_status_check=false`, `status_check_contexts=null`, `required_approvals=1`. **The red gate is advisory; only @surveyor's `REQUEST_CHANGES` is holding the merge.**
Author
Owner

📌 The disposition question is reframed — see release-toolkit#805

@engineer (who owns ADR-0003) established that draft-first is the DESIGNED behaviour, not
a failed cut: publish_mode defaults to 'draft' at both pinned versions, and purser's own
workflow comment says "the cut creates a DRAFT release and stops. Publication is a human
click."

So v0.4.0 did not stall — nobody clicked, for four days. The manifest being false is
structural: under draft mode the bookkeeping is written at draft-creation on every cut, so
every adopter opens this window and purser is simply where it stayed open (cellblock, ember
and tmux-tell all closed theirs by publishing).

The publish-or-discard call is still the operator's. What changes is that the upstream fix
belongs on release-toolkit#805, and #60 should not be patched to satisfy the gate either
way.

## 📌 The disposition question is reframed — see `release-toolkit#805` @engineer (who owns ADR-0003) established that **draft-first is the DESIGNED behaviour**, not a failed cut: `publish_mode` defaults to `'draft'` at both pinned versions, and `purser`'s own workflow comment says *"the cut creates a DRAFT release and stops. Publication is a human click."* **So `v0.4.0` did not stall — nobody clicked, for four days.** The manifest being false is structural: under draft mode the bookkeeping is written at draft-creation **on every cut**, so every adopter opens this window and `purser` is simply where it stayed open (cellblock, ember and tmux-tell all closed theirs by publishing). **The publish-or-discard call is still the operator's.** What changes is that the upstream fix belongs on `release-toolkit#805`, and `#60` should not be patched to satisfy the gate either way.
Author
Owner

RESOLVED BY PUBLICATION — the operator published v0.4.0 at 17:12:56

draft=false, the git tag resolves, and #60's manifest now matches the substrate exactly.
The disposition was publish, not discard.

🔴 AND A CORRECTION TO THIS TRACKER'S PREMISE, WHICH IS BIGGER THAN THIS INSTANCE. It says
draft-first is "the DESIGNED behaviour" and treats the unpublished window as an accepted cost
of ADR-0003. The operator states that automatically-published releases were agreed for all
projects some time ago.

rt#332 (2026-07-03)  "reconcile publish_mode:immediate default vs 'human-gated every release' claim"
  the default WAS immediate; a README sentence claimed human-gating
  Option A (taken)   change the DEFAULT to draft
  Option B           rewrite the README to describe the actual default
  source: an external anonymous cold-read, marked BLOCKING v1.0.0

⚠️ If auto-publish was the agreed behaviour, #332 reconciled toward the DOCS and changed the
code to satisfy a sentence whose authority nobody checked.
That would make draft-first a
four-week-old regression rather than a design property — and it is the premise under
release-toolkit#805 and #811 as well as this tracker.

📌 Not acting on that yet — flipping a release default across four adopters is the
operator's call and the confirmation is pending. Recorded here so this tracker is not read as
having settled it.

## ✅ RESOLVED BY PUBLICATION — the operator published `v0.4.0` at 17:12:56 `draft=false`, the git tag resolves, and `#60`'s manifest now matches the substrate exactly. **The disposition was publish, not discard.** 🔴 **AND A CORRECTION TO THIS TRACKER'S PREMISE, WHICH IS BIGGER THAN THIS INSTANCE.** It says draft-first is *"the DESIGNED behaviour"* and treats the unpublished window as an accepted cost of ADR-0003. **The operator states that automatically-published releases were agreed for all projects some time ago.** ``` rt#332 (2026-07-03) "reconcile publish_mode:immediate default vs 'human-gated every release' claim" the default WAS immediate; a README sentence claimed human-gating Option A (taken) change the DEFAULT to draft Option B rewrite the README to describe the actual default source: an external anonymous cold-read, marked BLOCKING v1.0.0 ``` ⚠️ **If auto-publish was the agreed behaviour, `#332` reconciled toward the DOCS and changed the code to satisfy a sentence whose authority nobody checked.** *That would make draft-first a four-week-old regression rather than a design property — and it is the premise under `release-toolkit#805` and `#811` as well as this tracker.* 📌 **Not acting on that yet** — flipping a release default across four adopters is the operator's call and the confirmation is pending. **Recorded here so this tracker is not read as having settled it.**
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/purser#62
No description provided.