feat(checks): split an unreadable version heading out of heading-form #14
No reviewers
Labels
No labels
accessibility
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
actions/changelog!14
Loading…
Reference in a new issue
No description provided.
Delete branch "mikluko/octant/1721-unreadable-version"
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?
Bottom of a three-PR stack. The release ceremony is the top one, so merging this alone cuts no tag.
The defect
A
##heading whose version the parser cannot read was skipped rather than kept, so the entry beneath it became the newest and every output described that one instead.heading-formwas the only thing reporting it, andheading-formis configurable off.Measured on
mainbefore the fix, withoff: heading-form:Nothing fires,
validis true, and a workflow reading those outputs cuts a tag for the wrong release and publishes the previous release's notes under it.The loud case was loud only by luck. With the skipped heading above an entry that happens to sit below the reference tag,
version-behind-tagfires — reporting a true thing about the wrong entry, for a reason unrelated to the fault. Move the tag and it goes silent.There is a path leading straight into it. A repository that tries a two-component heading finds it rejected and reaches for
off: heading-formas the obvious next move, which is exactly the configuration that turns a loud rejection into a wrong release.The fix
heading-formwas doing two jobs and only one of them is a policy. Its name, description and configurability all say it is about heading shape, which is a style question a repository may reasonably differ on. It was also the only thing standing between an unparseable version heading and a silently shiftedversion, which is not a style question at all.D1629 settled that a check catching a defect defaults to error and a check encoding a policy defaults to off. A check that is both cannot honour that, and switching off the policy switched off the defect. Same split
undated-entry/undated-releaseneeded, for the same reason.unreadable-version, defaulterror, fires where a##heading is neither[Unreleased]nor a heading whose version parses.heading-formkeeps the shape question it is named for. Sixteen checks.versionnow reports nothing for such a document, which is the existing "names no version" case reached by construction rather than by a second branch:Latest()stops at the first non-[Unreleased]entry and reports false where its version cannot be read. Soversionandnotesare empty,already-taggedandprereleasefalse,latest-tagstill the reference tag — a fact about the repository rather than the document — andvalidfalse.A consuming workflow already guards on
[ -n "$VERSION" ], so this lands on the existing no-op path rather than a new failure mode.After:
Two knock-ons, both intended
version-behind-tagstops firing on such a document: there is nothing to compare, and its firing was incidental to begin with. And an undated entry below an unreadable heading takesheading-formrather thanundated-entry, since it is no longer the newest.Verification
TestOutputsDescribeNoEntryUnderAnUnreadableHeadingis the demonstration: it was written before the fix and fails onmainwithversion="9.8.0",valid="true"and the 9.8.0 body innotes. Beside it,TestAnUnreadableHeadingBelowTheNewestEntry,TestLintUnreadableVersionIsSeparateFromHeadingForm(on by default, still fires withheading-formoff,[Unreleased]silent),TestLatestStopsAtAnUnreadableHeading, and aTestLintFindingsrow. Mutation-checked both directions.go test ./...green,go vet ./...andgofmt -lclean,gen-readmeidempotent,-list-checksreports sixteen.Release
The entry is under
## [Unreleased]. No version heading and noaction.yamlbump — those are the ceremony at the top of the stack, so the release is armed once, when the whole stack lands, and its notes cover everything in it.action.yamlis touched only in theversion,notesandprereleaseoutput descriptions, which claimed the value came from "the newest versioned entry" and would otherwise ship false.AI assistance
Written by Claude Code (Opus 5) under an octant worker contract; the defect was measured independently before and after.
Octant B1721.
This branch now carries more than the title says. Octant T1723 landed here via #17 and two commits after it, and the branch was rebased onto
main(it had goneCONFLICTINGwhen #18 landed under it).Four commits,
CLEANand green:golang.org/x/mod/semveris out of the module. What counts as a version, and how two are ordered, are both this repository's own now, pinned against the specification's suggested regex as a differential oracle — 54 valid forms and 42 invalid ones, plus three pinned departures where theuint64bound is narrower than the spec.Two verdicts change for a consumer:
[v1.2.3]vis a tag's, not a heading's[1.2.3+build.1]The one thing worth a reviewer's attention: the dependency was load-bearing in
internal/gitin a way nothing recorded.Tag.Version()read the movingv1tag — the Actions convention, and this repo ships one — only becausex/modacceptsvMAJORshorthand. Read strictly,v1names no version and drops out of reference-tag selection, silently, with every existing test still green.ParseTagnow makes the tag namespace's laxness an explicit contract andTestVersionsReadsTheMovingMajorTagpins it.The title and description still describe the original
unreadable-versionsplit only; renaming them is yours, not mine.