-
v1.2.0 Stable
released this
2026-09-07 07:28:16 +00:00 | 19 commits to main since this releaseAdded
-
unreadable-version, a check of its own for an entry heading naming no
version that can be read, aterror, which leavesheading-formthe shape
question its name is about. One check was doing both jobs and only one of them
is a policy:heading-formis about the form of a heading, which a repository
may reasonably differ on and switch off, and switching it off also switched
off the only thing standing between a heading nothing can read and a release
described from the wrong entry. It is the splitundated-entryand
undated-releasemade, for the same reason. Sixteen checks. -
oci-incompatible-version, for a version that is valid Semantic Versioning
and cannot be an OCI tag, aterrorand switchable off. The specification
permits1.2.3+build.1; the OCI distribution specification's tag grammar is
[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}, which has no+. Git ref syntax does
permit it, so such a version cuts a tag and is then not a name a registry
takes. The finding names the character at fault, and the length bound is
checked too: a pre-release run has no bound in SemVer and a tag stops at 128.This is the first check whose subject is outside the document. The other
seventeen are about the changelog or about the repository's tags; this one is
about what a version can become somewhere else, and the register says so
rather than pretending otherwise. A repository that publishes no image or
chart switches it off inoff:and it never fires again. Eighteen checks. -
A SemVer compliance section in the README, stating what this action
accepts as a version and what four named tools do with one:x/mod/semver's
two documented deviations,Masterminds/semver/v3admitting a pre-release
only where the constraint's own literal carries one, the difference between
matching a range and selecting from it, and the OCI tag grammar having no+.
Each is documented where its own tool lives and nowhere near the
specification, so a reader holding a valid version had no way to meet them
except by running into one. Every entry names the version it was read against. -
date-mismatch, for a released entry whose date is not the day its tag was
cut, aterror. Nothing compared the two:date-formatreads the shape,
date-orderanddate-futureread the document alone, and the immutability
pair compares text. An entry could claim2026-09-01whilev1.2.3was cut
on the 11th and every check passed. An entry no tag names is silent, because a
release pending is not a date that disagrees. Seventeen checks.Two things it has to get right, and both are settled where the tag is read
rather than where the dates are compared. A repository cuts one kind of tag
or the other, and they carry their date in different places: an annotated
tag has a tagger date of its own, a lightweight tag is a bare reference whose
only date is on the commit it names. And the day is the tag's own, not
UTC — a changelog date means the day the human cut the release, so
normalising a tag cut at 23:30-07:00 turns a correct entry into a finding.The open entry is what makes this check exact. Under
release-branchthe
date is written by the merge that cuts the tag, so it is a record. Under
release-trunkit is written in the pull request before the tag exists, so it
is a prediction, and a pull request open across midnight ships an entry that
disagrees with its own tag legitimately. That is the invocation that switches
this check off, the way a branch accumulating a release switches off
undated-entry.
Changed
-
What counts as a version is Semantic Versioning 2.0.0 exactly, read by a
parser written here rather than bygolang.org/x/mod/semver, whose own package
doc declares two deviations from the specification: it requires a leadingv,
and it recognisesvMAJORandvMAJOR.MINORas alternatives to the
three-component form. Working around those cost a canonicalisation comparison
that carried a second policy nobody chose. Two headings change verdict:
[v1.2.3]named a version and now names none, because the leadingvis
x/mod's requirement and a heading is not a tag; and[1.2.3+build.1]named
none and now names one, because build metadata is valid Semantic Versioning.
Whether a version this project accepts is one it wants is a separate
question, asked under a name of its own. One departure is this project's and
not the specification's: a major, minor or patch component past 2^64-1 is
rejected, where the specification sets no bound. -
golang.org/x/mod/semveris gone, and ordering is this project's too.
Sections 10 and 11 are where the specification stops being obvious: build
metadata is ignored for precedence, so1.0.0+aand1.0.0+bare one version
and two strings; a numeric pre-release identifier ranks below every identifier
that is not one; and numeric identifiers compare numerically with no upper
bound, which1.0.0-99999999999999999999needs and no fixed-width integer
gives. They carry no leading zero, so the longer of two is the larger and the
value is never read as a number. -
A tag is read by
ParseTag, which is deliberately laxer than the
specification and says so. A tag namespace carries a leadingv, and the
GitHub Actions convention keeps a movingv1beside thev1.0.0it points
at; both name one version, and the fuller spelling still wins the tie for the
reference tag. That behaviour used to rest onx/modacceptingvMAJOR
shorthand as a side effect. It is now a named, tested contract of this
project's own, and read strictly it would have gone silently. -
A tag now matches an entry by precedence rather than by string equality, so an
entry naming build metadata matches the tag naming its version. It could not
before, because canonicalisation dropped the metadata from one side only.
Fixed
-
undated-releaseno longer fires where the tag naming the newest entry is a
candidate. The check split on whether a tag already named the entry's
version, read as the release shipped. That reading is sound for a final and
false for a pre-release: a candidate ships its tag and stays undated on
purpose, which is what a stabilization branch is for. It fires on a final
tag now, and a candidate falls through toundated-entry, which is the check
such a branch already switches off. -
The defect was unreachable until the
release-branchexample stopped
composing its tag. While the branch cutvX.Y.Z-pre.<run>and the entry named
X.Y.Z, no tag a branch cut ever named its own entry's version. Once the
entry names what is cut, the tag names the entry's version from the first
candidate on, and every push after it was a finding on the one branch whose
purpose is to cut them. -
The
release-branchexample teaches what the strategy's own adopter
settled on: the changelog names the release it is heading for and never an
attempt at one. The entry says1.3.0from the moment the branch opens; each
pull request into that branch cuts a numbered candidate,v1.3.0-rc.1and up;
merging to the trunk dates the entry and cutsv1.3.0. A heading therefore
never carries an identifier, andprerelease-entryis raised on every
invocation rather than on the trunk alone. -
A release branch has no push trigger. A candidate comes from a pull request
into the branch, which is what lets the suite stand in front of it: the cut is
a job withneeds: [changelog, suite], andneeds:is job-level, so a step
had no boundary a gate could attach to. The two cutting workflows also carry
paths: [CHANGELOG.md], so a change proposing no release starts no run at all. -
The workflows are named for what each run yields, not for what triggers it:
ci,release-candidate,release,publish, and thesuitethey call.
Read that list and it is the strategy. It replacespull-request.yaml,
release-branch.yamlandmain.yaml, whose names described their triggers and,
in one case, described the wrong thing. -
Where the newest entry's heading names no version that can be read,
version
andnotesreport nothing rather than describing the entry below it. That
entry was being read as the newest, so a workflow reading those cut a tag for
the wrong release and published the previous release's notes under it. Nothing
under such a heading is the newest entry, and reporting nothing is what a
document naming no version already does, so a workflow guarding on an empty
versionlands on the path it already has.already-taggedandprerelease
answerfalsebeside it, as they do for a document naming no version. -
A rejected heading names the rule it broke rather than restating the
grammar. One message covered every rejection, so the author of
## [1.3.0+build.1]was told that a version states all three of major, minor
and patch, which it does. A leading zero, a component that is not a number, an
empty identifier, a character outside[0-9A-Za-z-]and a leadingvnow each
say so, and point at the fragment at fault.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-