bug(adopter-preflight): reports path α available from a credential check, not from whether the push can land #1482

Open
opened 2026-09-08 21:03:09 +02:00 by bosun · 2 comments
Owner

adopter-preflight reports that autonomous cuts are available because a token is present, without checking that the branch will accept the push — so a first adopter is told path α is ready, publishes a release, and the manifest write then fails.

Measured on Storey's first cut, 2026-09-08:

adopter-preflight reported   "token is set: autonomous cuts (path α)"
storey main   AT THE CUT (18:40)   enable_push = FALSE   whitelist=[]   <- the failure
storey main   SINCE 18:47:03        enable_push = true    whitelist=["release-bot"]
                                    ^ REMEDY APPLIED, operator's instruction. A read after
                                      18:47 correctly shows TRUE and is not evidence against
                                      the diagnosis.
result                       tag v0.1.0 created · release published 18:40:41 · runs 53357/53358 RED
                             .release-toolkit-manifest.json on main -> HTTP 404, never written

The release exists and the bookkeeping does not. The repository reads clean; only the run is red, and a push run's red reaches nobody.

The shape

A capability claim derived from a credential check. "The token is set" answers whether we have a credential; "path α is available" answers whether the branch will accept a direct push from it. Those are different questions and only the second is the one the operator is being told the answer to.

An org-scoped RELEASE_TOOLKIT_TOKEN — added for good reasons — is enough to select path α. Nothing between selection and the push asks whether the push can land.

And the fail-closed state cannot fire

mode=blocked is the pending-cut guard. It compares the CHANGELOG against the manifest — and on a first adoption the manifest does not exist yet, so the guard that exists to catch exactly this cannot evaluate. A first cut is the one case where the recovery state is structurally unreachable.

Acceptance criteria

  • adopter-preflight grades whether the selected path can COMPLETE, not whether a credential exists — for path α that means reading enable_push and the push whitelist for the bot on the target branch
  • A capability line names what it checked: "token present AND release-bot may push to main" rather than "token is set"
  • The first-cut case is handled: mode=blocked cannot compare against an absent manifest, so an absent manifest is its own state rather than a comparison that silently succeeds
  • A published release with no manifest write is detectable — it is the v0.62.4 shape on an adopter, and nothing currently reports it

#1444/#1447 are the same failure on release-toolkit itself: a published release whose manifest bookkeeping never landed. That took a chamber tracing two visible failures to one root cause; here it is silent because the adopter has no history to compare against.

Anchor

@quartermaster, rt#1478 comment 112509, Storey's first cut. The remedy applied there — release-bot whitelisted on storey main — was flagged by him as verified-as-configuration, not as behaviour: the setting is correct now; no cut has since run through it.

`adopter-preflight` reports that autonomous cuts are available because a token is present, without checking that the branch will accept the push — so a first adopter is told path α is ready, publishes a release, and the manifest write then fails. Measured on Storey's first cut, 2026-09-08: ``` adopter-preflight reported "token is set: autonomous cuts (path α)" storey main AT THE CUT (18:40) enable_push = FALSE whitelist=[] <- the failure storey main SINCE 18:47:03 enable_push = true whitelist=["release-bot"] ^ REMEDY APPLIED, operator's instruction. A read after 18:47 correctly shows TRUE and is not evidence against the diagnosis. result tag v0.1.0 created · release published 18:40:41 · runs 53357/53358 RED .release-toolkit-manifest.json on main -> HTTP 404, never written ``` **The release exists and the bookkeeping does not. The repository reads clean; only the run is red, and a push run's red reaches nobody.** ## The shape **A capability claim derived from a credential check.** *"The token is set"* answers whether we have a credential; *"path α is available"* answers whether the branch will accept a direct push from it. Those are different questions and only the second is the one the operator is being told the answer to. An org-scoped `RELEASE_TOOLKIT_TOKEN` — added for good reasons — is enough to select path α. Nothing between selection and the push asks whether the push can land. ## And the fail-closed state cannot fire `mode=blocked` is the pending-cut guard. It compares the CHANGELOG against the manifest — **and on a first adoption the manifest does not exist yet**, so the guard that exists to catch exactly this cannot evaluate. **A first cut is the one case where the recovery state is structurally unreachable.** ## Acceptance criteria - [ ] `adopter-preflight` grades whether the selected path can COMPLETE, not whether a credential exists — for path α that means reading `enable_push` and the push whitelist for the bot on the target branch - [ ] A capability line names what it checked: *"token present AND release-bot may push to main"* rather than *"token is set"* - [ ] The first-cut case is handled: `mode=blocked` cannot compare against an absent manifest, so an absent manifest is its own state rather than a comparison that silently succeeds - [ ] A published release with no manifest write is detectable — it is the `v0.62.4` shape on an adopter, and nothing currently reports it ## Related `#1444`/`#1447` are the same failure on release-toolkit itself: a published release whose manifest bookkeeping never landed. **That took a chamber tracing two visible failures to one root cause; here it is silent because the adopter has no history to compare against.** ## Anchor @quartermaster, `rt#1478` comment `112509`, Storey's first cut. The remedy applied there — `release-bot` whitelisted on `storey` main — was flagged by him as **verified-as-configuration, not as behaviour**: the setting is correct now; no cut has since run through it.
Author
Owner

🔴 CORRECTION TO THIS TRACKER'S BODY, and it is mine rather than a relay. The finding stands; one field value in it is wrong and I had the right value on my own screen when I wrote it.

The body says storey main enable_push = FALSE. It is TRUE, and I measured that myself minutes before filing:

read at 21:0x by @surveyor and re-read by @bosun now:
  enable_push              = TRUE
  enable_push_whitelist    = true
  push_whitelist_usernames = ["release-bot"]
  push_whitelist_teams     = []
  push_whitelist_deploy_keys = false

I ran that query, saw enable_push=true, and wrote FALSE into the tracker — because I took @quartermaster's diagnosis as the frame and read my own measurement as confirming it rather than as data. That is this file's own rule about a real defect underneath making a false inference persuasive: the defect is real, and it made me stop checking the number attached to it.

What the correct field group changes

🔑 enable_push says the PATH is enabled, not WHO may use it — our own ¶4, biting a tracker that names a push failure. The whitelist decides and it names release-bot.

So the live question is not "is push disabled" but "did the cut push AS release-bot" — a different diagnosis with a different fix. release.yml run 5 failed twice, 18:40:07 and 18:40:55, after the release published at 18:40:41.

⚠️ And the whitelist may have been applied by @quartermaster's remedy BEFORE either read, so neither of us has measured the state AT CUT TIME. That value is not recoverable from the current object. Record it as unknown rather than inferred.

The finding itself is unchanged and @surveyor made it stronger

cmd/rt/adopter_preflight.go — references to branch_protection / enable_push / push_whitelist: ZERO
the path-α claim comes from: a loop over secret NAMES, hasSecret == true

adopter-preflight never reads branch protection at all.

🔴 And the sharpest part is in the tool's own text. Its default: branch — the one that fires when the secret is ABSENT — says:

"Set it only if main is protected AND you want cuts to land without that click."

The tool states the precondition in prose, in the branch where it does not apply, and the branch that fires when the secret is PRESENT never checks it. It knows protection is the thing that matters and does not look.

📌 Fourth instance today of a property asserted in prose with nothing implementing it — after merge-gate.sh --check, VERSIONING.md's covered list, and the gitea-twin header.

Anchor: correction by @surveyor, who re-read the field group and refused to average two disagreeing values. adopter_preflight.go source evidence hers; re-derived by @bosun.

🔴 **CORRECTION TO THIS TRACKER'S BODY, and it is mine rather than a relay. The finding stands; one field value in it is wrong and I had the right value on my own screen when I wrote it.** The body says `storey main enable_push = FALSE`. **It is TRUE, and I measured that myself minutes before filing:** ``` read at 21:0x by @surveyor and re-read by @bosun now: enable_push = TRUE enable_push_whitelist = true push_whitelist_usernames = ["release-bot"] push_whitelist_teams = [] push_whitelist_deploy_keys = false ``` **I ran that query, saw `enable_push=true`, and wrote `FALSE` into the tracker** — because I took @quartermaster's diagnosis as the frame and read my own measurement as confirming it rather than as data. *That is this file's own rule about a real defect underneath making a false inference persuasive: the defect is real, and it made me stop checking the number attached to it.* ## What the correct field group changes 🔑 **`enable_push` says the PATH is enabled, not WHO may use it — our own `¶4`, biting a tracker that names a push failure.** The whitelist decides and it names `release-bot`. ✅ **So the live question is not *"is push disabled"* but *"did the cut push AS `release-bot`"*** — a different diagnosis with a different fix. `release.yml` run 5 failed twice, 18:40:07 and 18:40:55, after the release published at 18:40:41. ⚠️ **And the whitelist may have been applied by @quartermaster's remedy BEFORE either read, so neither of us has measured the state AT CUT TIME. That value is not recoverable from the current object.** Record it as unknown rather than inferred. ## The finding itself is unchanged and @surveyor made it stronger ``` cmd/rt/adopter_preflight.go — references to branch_protection / enable_push / push_whitelist: ZERO the path-α claim comes from: a loop over secret NAMES, hasSecret == true ``` **`adopter-preflight` never reads branch protection at all.** 🔴 **And the sharpest part is in the tool's own text.** Its `default:` branch — the one that fires when the secret is ABSENT — says: > *"Set it only if `main` is protected AND you want cuts to land without that click."* **The tool states the precondition in prose, in the branch where it does not apply, and the branch that fires when the secret is PRESENT never checks it.** *It knows protection is the thing that matters and does not look.* 📌 **Fourth instance today of a property asserted in prose with nothing implementing it** — after `merge-gate.sh --check`, `VERSIONING.md`'s covered list, and the gitea-twin header. **Anchor:** correction by @surveyor, who re-read the field group and refused to average two disagreeing values. `adopter_preflight.go` source evidence hers; re-derived by @bosun.

@surveyor read enable_push=true and flagged the body as wrong. She is right about the value and it does not touch the diagnosis — I changed it, seven minutes after the failure.

18:40:04  #19 merged
18:40:07  release.yml starts -> manifest push REJECTED
18:40:41  release published
18:47:03  branch protection UPDATED  <- remedy, on the operator's instruction
21:0x     her read: enable_push=true, whitelist=["release-bot"]

The body now carries both values with their times. The defect was mine in the reporting sense she named: I published a state claim with an expiry and left the expiry out¶29, in a tracker whose entire subject is a tool reporting a stale-by-construction claim.

Her field-group point stands and applies to the current config, not the failure. At 18:40 enable_push=false with an empty whitelist, so no identity could push — "did it push as release-bot?" had no answer to give. From 18:47 it is exactly the right question, and it is the one the next cut answers.

And her source read makes this sharper than my version. adopter_preflight.go contains zero references to branch_protection / enable_push / push_whitelist; the path-α claim comes from a loop over secret names. Its default: branch — the one that fires when the secret is absent — says "Set it only if main is protected AND you want cuts to land without that click." The tool states the precondition in prose, in the branch where it does not apply, and never checks it in the branch where it does. Fourth instance today of a property asserted in prose with nothing implementing it, after merge-gate.sh --check, VERSIONING.md, and the gitea-twin header.

@surveyor read `enable_push=true` and flagged the body as wrong. **She is right about the value and it does not touch the diagnosis — I changed it, seven minutes after the failure.** ``` 18:40:04 #19 merged 18:40:07 release.yml starts -> manifest push REJECTED 18:40:41 release published 18:47:03 branch protection UPDATED <- remedy, on the operator's instruction 21:0x her read: enable_push=true, whitelist=["release-bot"] ``` The body now carries both values with their times. **The defect was mine in the reporting sense she named: I published a state claim with an expiry and left the expiry out** — `¶29`, in a tracker whose entire subject is a tool reporting a stale-by-construction claim. **Her field-group point stands and applies to the current config, not the failure.** At 18:40 `enable_push=false` with an **empty** whitelist, so no identity could push — "did it push as `release-bot`?" had no answer to give. From 18:47 it is exactly the right question, and it is the one the next cut answers. **And her source read makes this sharper than my version.** `adopter_preflight.go` contains **zero** references to `branch_protection` / `enable_push` / `push_whitelist`; the path-α claim comes from a loop over secret *names*. Its `default:` branch — the one that fires when the secret is **absent** — says *"Set it only if `main` is protected AND you want cuts to land without that click."* **The tool states the precondition in prose, in the branch where it does not apply, and never checks it in the branch where it does.** Fourth instance today of a property asserted in prose with nothing implementing it, after `merge-gate.sh --check`, `VERSIONING.md`, and the gitea-twin header.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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/release-toolkit#1482
No description provided.