Skip to content

Versioning and Release

Versioning and releases

A version number is a promise to whoever is about to upgrade. That is the whole idea; the rules below are just that idea applied. Two independent questions get answered by two different parts of the number:

  • the three digits say how careful the upgrade is — 8.1.0 vs 9.0.0
  • the suffix says whose data it is running on — 8.1.0-alpha.3 vs 8.1.0

Keeping those apart is what makes the scheme make sense.

The three digits

Semver is written for libraries with a public API. ZFM has no public API, so what the number promises about is the facility's data and the way people work.

Bump The question that triggers it Example
8.0.1 — patch Did I only fix something that was wrong? Tank walk sorted wrong; a marker didn't show its nickname
8.1.0 — minor Can users now do something they couldn't, with nothing else changing? The genomic-feature editor ships
9.0.0 — major Must someone be told before upgrading, or can't I undo it? An irreversible migration; a field facilities relied on is gone; the GUI reorganizes enough to need retraining

Consequences worth being explicit about, because they are where the intuition goes wrong:

  • 8.0.17 is perfectly normal. Seventeen rounds of fixes with no new capability. The number is not a score and there is no pressure to round up to 8.1.0.
  • A major is not earned by accumulating minors. It is a warning label. Most releases are patches, some are minors, majors are rare.
  • Only the leftmost changed digit moves, and the ones to its right reset: a minor after 8.0.17 is 8.1.0, not 8.1.17.

The suffix: who is exposed

-alpha does not mean "unfinished". It names the blast radius of a mistake.

Stage Who runs it What a bug costs
8.1.0-alpha.N This machine and the dev facilities. No real fish data. An afternoon
8.1.0-beta.N One real facility that agreed to it, on real data, watched. A researcher's morning — which is why they have to have agreed
8.1.0 All eleven facilities. Eleven phone calls

So the moves between stages are events, not feelings:

  • alpha → beta: a facility said yes.
  • beta → release: that facility did ordinary work on it for two weeks and nothing bit.

Pick that period once and don't renegotiate it per release.

Pre-release ordering works the way you would hope, and not by luck — 8.1.0-alpha.0 < 8.1.0-beta.0 < 8.1.0, and alphabetical order happens to agree with intent.

No facility has volunteered yet. Until one does, beta is a stage nobody occupies, and tagging it would be theatre. The working policy in that case is alpha → release, with the carefulness moving into how long a build sits on alpha and the dev facilities before it goes out.

There is deliberately no rc stage. Three gates is a release-engineering ritual for a team with a QA organization; a third gate here would only mean a facility sits on an unreleased build longer.

When not to pre-release at all

A pre-release costs a deploy cycle and costs a facility its attention. Spend it when there is reason to be nervous, not on a schedule.

  • Patch — ship it. A fix that needs a beta is not a fix, it is a change.
  • Minor — beta it if it touches the data model or runs a migration. Otherwise ship it.
  • Major — always. That is what the number means.

What a release actually is

The version is derived from the git tag and nothing else, so the policy above is literally a sequence of tags:

v8.0.0-alpha.0     the 8.x lineage starts
v8.0.0-beta.0      the rewrite goes to one real facility (if one volunteers)
v8.0.0             all eleven. The rewrite is live.
v8.0.1             first fix after that
v8.1.0             first new capability after that

Two rules keep this cheap:

Tag only when something ships somewhere. Between tags git describe already yields v8.0.0-alpha.0-37-gabc1234, so every commit is identifiable without a tag. Tagging is not how work gets marked as done; it is how a deployment gets named.

A tag and a RELEASE-NOTES entry are the same event. If it got a tag, a user can read what changed. If the sentence can't be written, it isn't a release — it's a commit.

The browser walk runs before the tag. cd zf-react-client && npm run e2e:built exports the client and opens every screen in a real browser against the gui-test-built.test facility (how it works). A tag names a deployment, and that is the one check which looks at exactly what a browser will be handed.

git tag -a v8.0.1 -m "v8.0.1"

The tag is repo-wide, so it moves the reported version of both the server and the client even when only one of them changed. That is intended: the number names the release of the system. The two halves still report different commits whenever they are deployed at different times, which is the drift worth detecting.

The guides are published with the tag. The production and development guides at https://deployment.zebrafishfacilitymanager.com carry, in every page's footer, the version they were built from. Publish them after the tag, so that footer names the release rather than the commit before it:

docs/publish.sh

Between releases, run it whenever a change to a guide is worth having live.

Reading the version off a running facility

Both halves bake their identity in at build time (git describe --tags --always --dirty, plus the full SHA and a build timestamp) and report it:

  • the server publishes it on GET /facility-settings, so the client gets it on a fetch it already makes, and on GET /health for a curl during an outage
  • the client reports its own, generated from its own build

A -dirty suffix means the working tree had uncommitted edits when that build was made — on a deployed facility, that means someone edited code on the box.