sprig and prose docs classic docs
Reference› ADRs› ADR021 VersioningPolicy

ADR021 VersioningPolicy

Title: Versioning policy

  • Status: Accepted
  • Decided: D2026-09
  • Affects: Cli, Parser, Receipts, Ui

Context

Seven packages are published independently and versioned with changesets. The root README documents the mechanics well enough — add a changeset, apply the bumps, publish — and carries exactly one line of policy, which is about automation rather than authorship: internal dependents get a patch when their dependencies change.

Every judgment lives inside four words of step one: "choose packages and bump type." Nothing said what to choose. What emerged is therefore not a convention; it is a run of individually reasonable calls that do not agree with each other.

The clearest example is the changeset that introduced the receipt system. The CLI took a minor (0.11.0). receipts, the package the feature was actually built in, took a patch (0.1.1). An entire scanner, a bundle format and a public API shipped as a patch, while the wrapper around it went minor. Nothing about that was wrong at the time, and nobody could have said what right was.

Dependents tell the same story from the other side. Four times a dependency took a minor and the CLI took a patch: 0.12.1, 0.12.2, 0.12.5 and 0.12.6. The last of those is the sharpest, because prose-parser went minor for introducing SP025 — an error that stops some universes compiling — and the CLI carried the "Upgrading" note inside a patch. Twice the CLI took a minor, 0.13.0 and 0.14.0, and both had a reason of their own: the first changed the CLI's own flags, the second changed describe and title in every manifest.

The forcing case was releasing 020. receipts went minor because behaviour changed under its consumers; the CLI took a patch. The reasonable question — shouldn't the CLI inherit the larger bump, since it is the only surface most people ever touch? — had no written answer, and the changelog could be read to support either.

Everything is 0.x. That is a real constraint on what any policy can say, and it is load-bearing rather than incidental.

Decision

A minor means a user could be surprised. A patch means they could not.

Surprise is the test, and it is about the user's experience rather than the shape of the code. A minor is warranted when output changes for unchanged input, when something that worked stops working, when an error appears where none did, or when a command, flag, or exported signature changes shape. A patch covers everything else: a fix that only removes wrong behaviour, a change narrow enough to be conditional, internals, documentation, performance, and dependency ranges.

Each package is judged on its own audience. A dependency going minor does not make its dependents minor. Changesets already bumps dependents a patch and rewrites the range; that is a mechanical concern and not a communication. When the behaviour is visible through a dependent, that package gets its own changeset entry saying so, at whatever level fits its users.

There is no major, and that is accepted. While the ecosystem is 0.x, a change that would be breaking after 1.0 rides a minor and says so plainly in the changeset body. Backward compatibility is preserved within reason, but not at the cost of correctness — this is alpha, and the range of signals available is narrower than the range of things that can happen.

Consequences

What this makes easy. The bump is answerable without archaeology, and it is answerable per package instead of as one negotiation across the tree. A reader who sees a minor knows to read the body before upgrading.

What this costs. Surprise is a judgment, not a rule, so two people can reach different answers in good faith. That is deliberate — the mechanical alternatives were considered and are worse — but it means the changeset body, not the number, remains where the real communication happens. The number is a flag saying read this.

It would reclassify at least one past release. Under this policy the CLI's 0.12.6 is arguable: SP025 made some universes stop compiling, which is squarely "something that worked stops working," even though it only fired on a narrow pattern. Recorded rather than smoothed over, because a policy that agrees with every past decision is not a policy.

Dependents will look quieter than their dependencies, sometimes markedly. prose-parser has moved through six minors while the CLI took patches for most of them. That is the intended reading and not a discrepancy to correct.

What to watch — this is the real one. Minor is carrying two meanings at once: new functionality and behaviour changed under you. Those want different signals and 0.x has only one to give. The moment that ambiguity costs somebody real time is the moment to talk about 1.0, and it should be taken as the signal rather than argued away.

Alternatives considered

Dependents inherit the highest bump from their dependencies. Attractive because it is mechanical, needs no judgment, and can be automated. Rejected: a dependency's bump says nothing about whether anything changed for this package's users. prose-parser adding an exported helper is a minor for its consumers and completely invisible to a CLI user, and auto-inheriting would inflate the CLI on changes nobody can observe. A version that rises for unobservable reasons stops meaning anything, which is the failure this record exists to prevent.

Strict semver read off the API surface — only exported signatures count. Rejected. For a CLI the surface is the observable behaviour. A tool whose output changes for unchanged input has changed for its user whether or not a signature moved, and 020 is exactly that case: no exported signature changed at all.

Go 1.0 now and use major properly. Deferred rather than rejected, and it is the honest answer to the 0.x problem rather than a workaround for it. Refused for now because 1.0 promises a stability the language cannot yet make — 011 is open, 018 is open, and both could move the grammar. Revisit when the open records are closed, or when the minor-means-two-things ambiguity above draws blood.

Patch for everything until 1.0, on the grounds that 0.x promises nothing. Rejected. It is defensible by the letter of semver and it throws away the only signal available, leaving the changelog as the sole way to discover that an upgrade will cost something.

Evidence

The inversion is the strongest piece, because it cannot be explained as a different rule consistently applied. One changeset, two packages, and the one that gained the feature took the smaller bump:

Package Bump Version
@sprig-and-prose/sprig minor 0.11.0
@sprig-and-prose/receipts patch 0.1.1

The dependent history is the second piece. Four CLI patches against dependency minors (0.12.1, 0.12.2, 0.12.5, 0.12.6), two CLI minors (0.13.0, 0.14.0), and in both minor cases the reason was local to the CLI rather than inherited. That is the practice this record is ratifying rather than inventing: the inheritance model was already not being followed, it just was not written down.

Versions at the time of writing: prose-parser 0.9.0, receipts 0.1.7, sprig 0.14.0, sprig-ui 0.2.0, sprig-ui-beta 0.5.0, tutorial 0.2.0, tutorial-ui-csr 0.2.0. Every one is 0.x, and none is close to arguing for 1.0 on stability grounds.