• v1.2.0 d30e5ab9e9

    v1.2.0 Stable

    github-actions[bot] released this 2026-09-07 07:28:16 +00:00 | 19 commits to main since this release

    Added

    • unreadable-version, a check of its own for an entry heading naming no
      version that can be read
      , at error, which leaves heading-form the shape
      question its name is about. One check was doing both jobs and only one of them
      is a policy: heading-form is 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 split undated-entry and
      undated-release made, for the same reason. Sixteen checks.

    • oci-incompatible-version, for a version that is valid Semantic Versioning
      and cannot be an OCI tag
      , at error and switchable off. The specification
      permits 1.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 in off: 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/v3 admitting 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
      , at error. Nothing compared the two: date-format reads the shape,
      date-order and date-future read the document alone, and the immutability
      pair compares text. An entry could claim 2026-09-01 while v1.2.3 was 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-branch the
      date is written by the merge that cuts the tag, so it is a record. Under
      release-trunk it 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 by golang.org/x/mod/semver, whose own package
      doc declares two deviations from the specification: it requires a leading v,
      and it recognises vMAJOR and vMAJOR.MINOR as 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 leading v is
      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/semver is 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, so 1.0.0+a and 1.0.0+b are 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, which 1.0.0-99999999999999999999 needs 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 leading v, and the
      GitHub Actions convention keeps a moving v1 beside the v1.0.0 it points
      at; both name one version, and the fuller spelling still wins the tie for the
      reference tag. That behaviour used to rest on x/mod accepting vMAJOR
      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-release no 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 to undated-entry, which is the check
      such a branch already switches off.

    • The defect was unreachable until the release-branch example stopped
      composing its tag. While the branch cut vX.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-branch example 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 says 1.3.0 from the moment the branch opens; each
      pull request into that branch cuts a numbered candidate, v1.3.0-rc.1 and up;
      merging to the trunk dates the entry and cuts v1.3.0. A heading therefore
      never carries an identifier, and prerelease-entry is 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 with needs: [changelog, suite], and needs: 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 the suite they call.
      Read that list and it is the strategy. It replaces pull-request.yaml,
      release-branch.yaml and main.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
      and notes report 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
      version lands on the path it already has. already-tagged and prerelease
      answer false beside 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 leading v now each
      say so, and point at the fragment at fault.

    Downloads