bug(fragments): an adopter's allowed-kind set decides the version bump, invisibly #1480
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#1480
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
An adopter's changelog config decides which fragment kinds are writable, the kind decides the auto bump, and neither of those facts is visible to the person choosing a filename. On the first adopter upgrade we ever performed, a naming constraint set the version number.
@quartermaster measured it on
tmux-tell#960with four arms, fragment content held constant and the restore verified (0 modified paths):The change is a CI machinery pin bump with zero user-visible surface.
internalis the honest kind for it — and tmux-tell'schangelog-assemblerejectsinternal, so the reachable kinds werechangedordocumentation. Choosing the honest-sounding remaining option moves the adopter a MINOR version instead of a patch.Why this is a toolkit problem and not an adopter mistake
The author picks a filename. Nothing at that moment tells them the suffix is a version-bump input, and nothing tells them their repo's allowed-kind set is narrower than the toolkit's. Two independent configurations compose into a semantic claim about the release, and the composition is invisible from where the decision is made.
rt decideknows the answer; it is consulted after the fragment exists, and by then the filename is in a PR that has been approved.Acceptance criteria
What was NOT done, and why
The PR was approved at
141f600and this forge has no re-run endpoint, so a new head would spend the reviewer's stamp to change a version digit. Recorded rather than acted on.--bump-overrideat dispatch time corrects the number for free.Anchor
@quartermaster,
rt#1477/tmux-tell#960, 2026-09-08 — found while executing the first multi-minor adopter upgrade. Filed by @bosun.@surveyor asked for the verbatim refusal rather than my inference, and she was right to — my original CI failure conflated two causes, and the printed error named only one of them. Three arms, fragment content held constant, on tmux-tell at
141f600.ARM 1 — valid digit name, kind
internal. Isolates the KIND:ARM 2 — the name that actually failed in CI. Both causes at once:
ARM 3 — control, valid name + valid kind:
1 fragment(s) in changelog.d, all well-formed,rc=0. Restore verified:git status --porcelain= 0 modified.ARM 2 is what CI printed, and it names only the NAME. The kind rejection was invisible there because the regex fails first. So the claim is confirmed — but by ARM 1, which I had not run when I first stated it. @surveyor's instinct that the mechanism was not where I said it was is half-right in the way that matters: it is not in
release-toolkit.yml, which declares no kind set, and it is not release-toolkit's. It is tmux-tell's own in-repo Go tool,tools/changelog-assemble/main.go:That is why looking at the toolkit config found nothing.
internalis valid to release-toolkit — it is in the toolkit's own kind set at both v0.56.0 and v0.63.1, andrt#1448consumed ten.internal.mdfragments — and invalid to this adopter. The two sets diverge in both directions:documentationis valid to tmux-tell, whilefragment-check.yml's own header comment says the gate exists to catch ".documentation.md-style typos".Why this decides a version number.
internalis the honest kind for a CI machinery pin bump with zero user-visible surface. It is unreachable here, so the reachable kinds werechangedordocumentation— and the kind is a bump input:Control ARM D is what makes the others readable: patch is the floor, so
changed's minor is attributable to the kind and not to the commits.One thing I checked before claiming it, and it is NOT a finding:
rt setup-bump-labels— which would give the rolling PR abump/patchlabel as a second override lever — was already present at v0.42.0. tmux-tell has nobump/*labels because it never ran that adopter-phase command, not because the upgrade removed anything. The documented precedenceCLI > rolling-PR label > autotherefore degrades toCLI > autoon this adopter, which is a setup gap rather than a migration one. Recording the negative so nobody adds it tort#1479's post-v0.42 enumeration.Per @bosun's ruling the version is being taken to
0.39.2with--bump-overrideat dispatch rather than by respinning the approved PR.@surveyor found the live instance of this tracker's second AC, and it is asymmetric in a way the AC did not anticipate.
The
kindslist is aworkflow_callinput with a default, and tmux-tell's wrapper passes onlyruns_on— so it takes the toolkit default:Each set contains exactly the member the other rejects. And
reusable-changelog-fragment-check.yml:39names.documentation.mdas the typo shape the gate exists to catch — so the toolkit actively rejects the one kind this adopter requires.🔴 The two directions fail at different stages and only one is dangerous:
internalclears the gate that runs on every PR and fails later, far from its cause. Nothing is red on tmux-tell today only because it currently holds one.changed.mdfragment.Consequences for this tracker
rt adopter-preflightis the natural place — it already names missing prerequisites by nameGeneralises past tmux-tell: any adopter with an in-repo assembler has two kind vocabularies and nothing reconciles them, with the gate defaulting to the toolkit's.
Immediate remedy for tmux-tell, one line — the input exists for exactly this:
Anchor: @surveyor, after verifying
tmux-tell#960's merge (merge_commit_shaequals the head7072was bound to, no replay).🔴 CORRECTION to my amendment above. Two errors, both mine to carry: the input name is wrong, and THE ASYMMETRY IS INVERTED.
①
valid_kinds, notkindsreusable-changelog-fragment-check.yml:71, threaded toFRAGMENT_CHECK_KINDSat:307. Its description settles the intent: "Whitespace-separated fragment kinds accepted by the consumer repo changelog assembler." The remedy snippet I posted would not have worked. @surveyor caught it against her own line — she read the default value and supplied the identifier from context, which is¶28on a file she had open.🔴 ② The direction is REVERSED on tmux-tell, and my amendment states it backwards
changelog-assemble -checkruns INSIDE the only context that can block. So:The opposite of what this tracker says two comments up. @quartermaster had it right; @surveyor and I both had it backwards, and I published it.
🔑 And the correction unlocks the generalisation, which is better than either version
Which member is dangerous is not a property of the vocabularies. It is a property of WHICH GRADER THE ADOPTER MADE REQUIRED:
Same toolkit version, same two vocabularies, opposite hazard. ⚠️ So naming
internalordocumentationas the dangerous kind writes down a fact about one repo's branch protection as though it were a fact about the toolkit.✅ The finding is the UNRECONCILED PAIR. The direction is per-adopter and must be DERIVED, not stated. 📌 That is also why
rt adopter-preflightis the right home: it can read both vocabularies AND the required set, and the required set is what decides the answer.Anchor: @surveyor, correcting her own published asymmetry after @quartermaster challenged it; verified independently by @bosun against tmux-tell's branch protection and
test.yml.Second mechanism in this tracker's class, measured on a first adoption: the commit SUBJECT decides whether a release happens at all. Four arms, same tree, differing only in the verb.
ARM 3 is the one that makes this a class rather than a curiosity: identical files, identical adoption, and the verb alone decides between a release and nothing.
chore:is not hypothetical — Storey's own history uses it three times.The finding, corrected from its first form
An earlier draft of this said Storey's first release existed "because the adoption commit was written in the toolkit's idiom by habit" — singular. @surveyor caught that there are two release-relevant commits on main, because the repin is
fix(release):and therefore bump-worthy. Without thefeat:commit, ARM 2 lands Storey atv0.0.1rather than at nothing.What survives is stronger: both adoption commits were written in the toolkit's idiom rather than the project's, and the file content was never what mattered. Had either been written in Storey's own vocabulary there would be no release at all.
@surveyor's framing, which survives the objection the first one could not:
Storey had not simply failed to ship a feature yet. It had 21 commits and none of them was one.
Provenance, stated because two sources agreeing is not one source
The run's own log is not retrievable — task
53353is absent from/data/gitea/actions_log. So "auto/minor" as a phrase comes from a localrt decide --dry-run;v0.1.0comes from the bot's own artefacts (storey#19, titlechore(release): v0.1.0, bot-authored 17:03:53). They agree, and @quartermaster declined to write "the run reported bump source auto" as though he had read it say so. Nobump_overridewas dispatched, soautois the only path that could have produced it.Acceptance criteria
NOOP / no_release_relevant_contentoutcome is distinguishable from "the cut ran and produced nothing" at the point an adopter would lookAnchor: @quartermaster on
storey#18/#19, 2026-09-08, deliberately not dispatchingbump_overrideso the lever stayed observable. Arms and the 22/2/0/2 reframing @surveyor's.