Versioning: independent releases
The rule
Every release under releases/ is a complete, independent copy of the code. v0.1.1 starts as a copy of v0.1.1, changed where the change is; nothing in it imports from v0.1.1, overrides a method of v0.1.1, or loads a stylesheet of v0.1.1. A page that pins v0.1.1 keeps running v0.1.1 whatever lands after it.
| Kind | Example | What happens |
|---|---|---|
| Minor | v0.1.1, v0.1.2 | Copy the current release whole, make the change, add the test that fails without it, cut the manifest. Quality control on each |
| Major | v0.2.0 | Consolidate: refactor what the minors proved into a clean copy, retire what did not survive, keep the contracts |
| Release | v1.0.0 | The previous major with no code change: version and documentation only |
Why no overlays this time
The IFD methodology that send.sgraph.ai is built with shares code between minor versions: a minor holds only its surgical overrides and links back to the base, so v0.3.2 is v0.3.0 plus two patch files loaded after it. That was the right trade when every line was written by hand and a full copy per version was a cost. Two things changed. The code here is small and its scope is deliberately narrow, so a copy is a few hundred kilobytes. And the agents that now write most of it work better with one tree they can read whole than with a base and three layers of overrides to compose in their heads; the override chains in send.sgraph.ai's index.html (load order comments eight items long) are the evidence. So the overlay goes, and what stays from IFD is everything else: minors for active development, majors for consolidation, tests co-located with what they test, real data from the first version, zero external dependencies.
What it costs, and what it buys
- Costs: disk, trivially; and a fix that applies to several live releases has to be made in each, as a new minor of each, which is the point: a reader pinned to an old release gets nothing they did not ask for.
- Buys: a release that can be read, reviewed, hashed and reasoned about on its own; a manifest that is the whole truth; a consuming site that cannot be changed under its readers; and a fractal shape, where each component is independent by itself and can be lifted out, which is where this is going.
The mechanics
cp -r releases/v0.1.1 releases/v0.1.1, then make the change inv0.1.1only.VERSION = 'v0.1.1'insend/send-api.js; the gate checks it matches the directory.- A test in
tests/that fails without the change;node --test 'tests/*.test.mjs'green. python3 tools/release.py cut v0.1.1writes the manifest;tools/version.jsonsaysrelease: v0.1.1.- A line in
website/data/versions.jsonthat says what changed and why, in words a reader can act on. python3 tools/build.py && python3 tools/check.py, commit, push.devdeploys, tags, and checks the live host.
How to make a release is the same list as a checklist. The article that argues this out is Independent releases.