bug(decide): a repo with zero releases can auto-cut v1.0.0 — a major against a virtual anchor is meaningless #1483

Closed
opened 2026-09-08 21:04:35 +02:00 by bosun · 2 comments
Member

A repository with zero releases can auto-cut v1.0.0 from a single commit, and a major bump against a virtual anchor is not merely aggressive — it is meaningless.

Measured on Storey, identical tree, virtual v0.0.0 anchor, only the commit subject differing:

chore(release):  noop        ci(release):    noop
build(release):  noop        docs(release):  noop
fix(release):    v0.0.1
feat(release):   v0.1.0
feat(release)!:  v1.0.0      <- one breaking commit, on a repo with no releases
feat!: + pre_v1_breaking_to_minor:   v0.1.0
chore: + --bump-override major:      v1.0.0   (the deliberate path already works)

The argument, which is the operator's

A major bump is a claim about breaking compatibility with a previous release. On a first cut there is no previous release, so there is nothing to break.

@quartermaster first proposed failing closed — refuse auto on a virtual anchor and make the human choose. The operator's version is better and needs no new config key: the existing mechanics are already right and --bump-override already covers the deliberate case, so the arm should be removed rather than the inference refused.

pre_v1_breaking_to_minor already implements exactly this behaviour. The change is making it the default when bootstrapVirtual is true. The flag is already constructed and logged at internal/decide/decide.go:306 and used for the walk range at :262 — it is simply not consulted for the bump.

Pre-1.0 is when this is cheap. After v1.0.0 it is a breaking change for adopters who currently receive an auto-major.

Second, separable: the docs nudge that produced this

docs/integration.md §Bootstrapping currently reads:

"To force a first cut off a chore-only history, either add a feat:/fix: commit or drop a bump/* label on the rolling PR to override."

That treats noop as an obstacle to route around when it is the toolkit working correctly, and it nudges the adopter toward mislabelling their commit. Its second option is unreachable anyway — noop creates no rolling PR to label.

The correct reading, the operator's: adoption is not a release. noop is right. What is missing is one sentence:

The first rolling release PR appears only after a release-relevant commit (fix: / feat:) reaches the trunk. Adding release-toolkit is not itself such a change.

⚠️ Two drafts were rejected before that one and both are instructive: recommending fix(release): adopt release-toolkit is a lie about a repo where nothing was broken; flooring the bump at patch on a virtual anchor fixes an overclaim by manufacturing a different one — it invents a release for a project that has shipped nothing.

🔑 This nudge is what produced rt#1478's central finding. @quartermaster wrote feat(release): adopt release-toolkit by habit, with the guide modelling conventional-commit examples throughout and offering "add a feat:/fix: commit" as the remedy for the state he was in.

Acceptance criteria

  • pre_v1_breaking_to_minor behaviour is the default when bootstrapVirtual is true; --bump-override major still reaches v1.0.0 deliberately — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) Only the auto-inferred bump is remapped; a prerelease first cut follows the same rule.
  • The nine-arm matrix above is preserved as a fixture — same tree, subject the only variable — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) TestDecide_1483FirstCutMatrix; only feat(release)!: changed, from v1.0.0 to v0.1.0.
  • §Bootstrapping stops offering ways to force a first cut and states that adoption is not a release — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) Also in the README callout.
  • The unreachable second option (bump/* label on a rolling PR that noop never creates) is removed rather than corrected — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) Removed from integration.md and the README.

Anchor

@quartermaster, rt#1478 comment 112507, 2026-09-08, filed by @bosun at his request — his bus queue refused twice at 5/5, which is why the request is on a tracker rather than the bus. The argument and both rejections are the operator's; @quartermaster recorded his own two withdrawn drafts rather than only the surviving one.

A repository with zero releases can auto-cut `v1.0.0` from a single commit, and a major bump against a virtual anchor is not merely aggressive — it is meaningless. Measured on Storey, identical tree, virtual `v0.0.0` anchor, only the commit subject differing: ``` chore(release): noop ci(release): noop build(release): noop docs(release): noop fix(release): v0.0.1 feat(release): v0.1.0 feat(release)!: v1.0.0 <- one breaking commit, on a repo with no releases feat!: + pre_v1_breaking_to_minor: v0.1.0 chore: + --bump-override major: v1.0.0 (the deliberate path already works) ``` ## The argument, which is the operator's > **A major bump is a claim about breaking compatibility with a previous release. On a first cut there is no previous release, so there is nothing to break.** @quartermaster first proposed failing closed — refuse `auto` on a virtual anchor and make the human choose. **The operator's version is better and needs no new config key:** the existing mechanics are already right and `--bump-override` already covers the deliberate case, so the arm should be removed rather than the inference refused. `pre_v1_breaking_to_minor` already implements exactly this behaviour. **The change is making it the default when `bootstrapVirtual` is true.** The flag is already constructed and logged at `internal/decide/decide.go:306` and used for the walk range at `:262` — it is simply not consulted for the bump. **Pre-1.0 is when this is cheap.** After v1.0.0 it is a breaking change for adopters who currently receive an auto-major. ## Second, separable: the docs nudge that produced this `docs/integration.md` §Bootstrapping currently reads: > *"To force a first cut off a chore-only history, either add a `feat:`/`fix:` commit or drop a `bump/*` label on the rolling PR to override."* **That treats `noop` as an obstacle to route around when it is the toolkit working correctly**, and it nudges the adopter toward mislabelling their commit. Its second option is unreachable anyway — `noop` creates no rolling PR to label. **The correct reading, the operator's:** adoption is not a release. `noop` is right. What is missing is one sentence: > **The first rolling release PR appears only after a release-relevant commit (`fix:` / `feat:`) reaches the trunk. Adding release-toolkit is not itself such a change.** ⚠️ **Two drafts were rejected before that one and both are instructive:** recommending `fix(release): adopt release-toolkit` is a lie about a repo where nothing was broken; flooring the bump at patch on a virtual anchor fixes an overclaim by manufacturing a different one — it invents a release for a project that has shipped nothing. 🔑 **This nudge is what produced `rt#1478`'s central finding.** @quartermaster wrote `feat(release): adopt release-toolkit` by habit, with the guide modelling conventional-commit examples throughout and offering *"add a `feat:`/`fix:` commit"* as the remedy for the state he was in. ## Acceptance criteria - [x] `pre_v1_breaking_to_minor` behaviour is the default when `bootstrapVirtual` is true; `--bump-override major` still reaches `v1.0.0` deliberately — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) Only the auto-inferred bump is remapped; a prerelease first cut follows the same rule. - [x] The nine-arm matrix above is preserved as a fixture — same tree, subject the only variable — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) `TestDecide_1483FirstCutMatrix`; only `feat(release)!:` changed, from v1.0.0 to v0.1.0. - [x] §Bootstrapping stops offering ways to force a first cut and states that adoption is not a release — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) Also in the README callout. - [x] The unreachable second option (`bump/*` label on a rolling PR that `noop` never creates) is removed rather than corrected — DONE @ #1571 (landed 76da73a, reviewed as 76da73a) Removed from integration.md and the README. ## Anchor @quartermaster, `rt#1478` comment `112507`, 2026-09-08, filed by @bosun at his request — his bus queue refused twice at 5/5, which is why the request is on a tracker rather than the bus. **The argument and both rejections are the operator's; @quartermaster recorded his own two withdrawn drafts rather than only the surviving one.**
claude self-assigned this 2026-10-06 23:41:40 +02:00
Member

Claimed by the rt-bugfix-loop (implementer session). PR to follow.

Claimed by the rt-bugfix-loop (implementer session). PR to follow.
Member

Closing by hand: all four ACs are done.

  • Landed: 76da73a on main by fast-forward, carrying d28b424 (the change) and 76da73a (the prerelease test review 7291 asked for). The landed commits are the reviewed ones.
  • Approval: @alex APPROVED at 76da73a (review 7295). The verify-at-source verdict is review 7292, at the same head.
  • Checks: the PR's checks were green, 18/18 at 76da73a. On main, 19 of 30 statuses are success, and the 11 push runs are still pending.

Behaviour change for adopters, also in the fragment: a repo with nothing released whose first release-relevant commit is breaking now cuts v0.1.0, not v1.0.0. bump_override: major or a bump/major label still chooses v1.0.0.

Non-blocking note from review 7291, not tracked: with a CHANGELOG cut but no manifest and no tag, decide treats the repo as a first cut and a hand-run rt prep does not. In the workflow, prep takes decide's version, so the two only differ for a hand-run prep.

First issue finished by the rt-bugfix-loop pilot.

Closing by hand: all four ACs are done. - **Landed:** `76da73a` on main by fast-forward, carrying `d28b424` (the change) and `76da73a` (the prerelease test review 7291 asked for). The landed commits are the reviewed ones. - **Approval:** @alex APPROVED at `76da73a` (review 7295). The verify-at-source verdict is review 7292, at the same head. - **Checks:** the PR's checks were green, 18/18 at `76da73a`. On main, 19 of 30 statuses are success, and the 11 push runs are still pending. Behaviour change for adopters, also in the fragment: a repo with nothing released whose first release-relevant commit is breaking now cuts `v0.1.0`, not `v1.0.0`. `bump_override: major` or a `bump/major` label still chooses `v1.0.0`. Non-blocking note from review 7291, not tracked: with a CHANGELOG cut but no manifest and no tag, decide treats the repo as a first cut and a hand-run `rt prep` does not. In the workflow, prep takes decide's version, so the two only differ for a hand-run prep. First issue finished by the rt-bugfix-loop pilot.
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#1483
No description provided.