Three Zeros
You are shipping in public before 1.0. What version number tells a stranger whether to wait, to try it with a pinned version, or to use it freely? Standard semver has one answer for all three: “major is zero, anything goes.” That is not enough information to plan around.
The fix does not require changing semver. It only requires treating each slot below 1.0 as a distinct stage of the release lifecycle, with a promise it is still missing. Three zeros, three promises given up. Each transition adds one back.
I am building Muxon right now and I want the version number to carry that distinction out of the box, so nobody has to read the changelog to find out what stage the project is in.
0.0.0 — nothing
The repository exists. The crates compile. The plan is written. There is no implementation. You could clone it and cargo build would succeed; you could not use it for anything.
The promise that is missing: code that does what the project is for.
0.0.x — building the first usable thing
Patch versions while major and minor are both zero are pre-MVP. Each patch lands one piece of the road to a usable workflow. Daemon handshake. Durable state. Directory binding. The Mux trait. The pieces appear; the workflow does not, yet.
A user running 0.0.4 cannot do anything end to end. They can poke at parts and watch them work, but the parts have not met. The code exists. The product does not.
The promise that is missing: a usable end-to-end workflow.
0.1.0 — the first usable thing
The minor bumps from zero to one when there is something to use. For Muxon, that is muxon --dir . opening a directory-bound workspace and reopening it restoring the layout. A user can do something real.
This is the gate everything before was building toward. The MVP, in the honest sense of the word.
0.x.0 — iterating on the usable thing
Once the MVP exists, minor bumps add capability. Stateful capture. The first aigent. The capability handshake. Editors. Remote. Each bump lands a meaningful slice. Compatibility may still break — the surfaces are absorbing real use, finding their right shape.
The promise that is missing: stable surfaces.
1.0.0 — the freeze
The major bumps from zero to one when the surfaces have settled. 1.0.0 ships nothing new on top of the last 0.x.0. It changes only the policy: from now on, no breaks.
The promise that is gained: compatibility.
Why three zeros
Each zero, in this scheme, represents a missing promise. Each transition adds one back.
- 0.0.0 → 0.0.1: code now exists.
- 0.0.x → 0.1.0: a usable workflow now exists.
- 0.x.0 → 1.0.0: stable surfaces now exist.
Standard semver collapses the first two transitions into one. Anything below 1.0 is “early”. That is enough information for a published library where users can wait for 1.0 before adopting. It is not enough for a project shipping in public, where the difference between still bootstrapping and usable but evolving is the difference between do not bother yet and try it, but pin your version.
Three numbers, three audiences:
- 0.0.x reads as wait.
- 0.x.0 reads as try it, but pin.
- 1.x.0 reads as use freely; we will not break you.
How it plays out
Three projects, same rule
A single-binary CLI
You are writing a small grep-alike. The commit that first prints matches is 0.0.1. You add file globbing, then regex, then --color; each is a patch bump while the workflow is still incomplete. The first release that can replace grep on your own machine is 0.1.0. A month of real use later, when you are confident the flag set is the one you want to live with, you tag 1.0.0 and stop breaking it.
A library with plugins
A plugin host has two surfaces: the user API and the plugin API. Both need to freeze before 1.0. Early on (0.0.x) the plugin loader does not even exist; you are still shaping the user API. At 0.1.0 you ship a single built-in plugin end-to-end. The 0.x.0 range is where the plugin API mutates under load — this is exactly the stage the scheme names, and the stage where pinning matters most. Bumping to 1.0.0 is the decision that both surfaces have met enough real plugins to stop changing. Without the 0.0.x / 0.x.0 distinction, early plugin authors cannot tell whether to invest.
A restaurant's soft opening
A new restaurant runs the same arc. 0.0.x is the build-out: kitchen installed, menu drafted, staff training — nothing a diner can consume. 0.1.0 is the soft open: friends-and-family night, the first real meal served end to end. 0.x.0 is the weeks of tweaking the menu, plating, and service flow under real diners paying real money. 1.0.0 is when the menu is printed on real card stock and the kitchen stops changing the recipes — the stage where breaking “compatibility” with last week’s regulars stops being acceptable. Same three promises: kitchen exists, meal exists, menu is stable.
Rust did this implicitly
Pre-1.0 Rust shipped usable software. Each release broke things. The distinction between early-bootstrapping Rust (you could not write much yet) and late-pre-1.0 Rust (you could write real code, but the stdlib churned) was real, and it shaped which kind of user belonged on each version.
The version number did not carry that information. The community knew it from context.
The extension makes the context explicit.
Zero is a thing, not a placeholder
Zero is not a default. It is not “nothing yet, ignore”. It is a specific thing that does not exist yet, and naming it three different times tells the user exactly what is missing.
Code. Workflow. Compatibility.
Three zeros. Three promises. One number that means what it says.
Reactions
Discussion
Public notes from logged-in readers.
Loading comments…