releasing
ROADMAP.md is the contract: what a v0.0.x tag promises (nothing beyond
addressability) and what v0.1.0 has to show first. This page is the
mechanics.
When a tag is cut
.github/workflows/release.yml cuts tags itself. Every Monday at 06:17
UTC it looks at main and, if both of the following hold, tags the
commit with the next patch number (v0.0.1, v0.0.2, ...) and releases
it in the same run:
mainhas moved since the lastv*tag. A week with nothing merged produces nothing: a tag names a build, and the same build does not need two names.- CI has finished on that commit and nothing failed. The Rust quality gate must have passed; a nightly-only job that was skipped is fine; a check still running means "not yet", and the next Monday asks again.
The decision is scripts/release-cut.sh, which prints what it would do
when run from a checkout of main; the workflow's dry_run input runs
only that. Between Mondays, workflow_dispatch cuts one on demand, and
bump: minor starts a new line (v0.1.0) when ROADMAP.md's evidence for
it exists. Only one release runs at a time, so a dispatch during the
Monday run waits rather than racing it for the number.
A tag pushed by hand still works, and goes through the same build jobs:
git tag -a v0.0.1 -m "v0.0.1: an addressable prerelease" origin/main
git push origin v0.0.1
Either way the run then:
- builds
spindleforx86_64-unknown-linux-gnuandaarch64-unknown-linux-gnufrom the tagged tree with--locked, and packages each as a tarball with the licences and the README, plus a.sha256beside it; - builds the runtime image (the
Dockerfileat the root) foramd64andarm64and publishes a manifest atghcr.io/tuna-os/spindle:<tag>; - writes an SBOM for each tarball (SPDX, from
Cargo.lock, so it names every crate the build pinned) and attests it to the tarball; - signs a provenance attestation for each tarball and each image, through Sigstore, tied to the workflow's own identity; the image attestations are pushed to the registry beside the images;
- creates the GitHub release with the tarballs, the checksums, the SBOMs
and notes that say what the tag is, followed by the titles of the pull
requests merged since the previous tag, marked a prerelease for every
v0.*tag.
The crate version in Cargo.toml is not the release version and is not
bumped by a release; the tag is the name, and the binary carries the
commit it was built from.
Nothing is published under latest. A tag names a build; the next tag
names the next one.
Checking a build is what it says
A file name proves nothing. The attestation does: it says which workflow, on which commit, produced the artifact with this digest, and Sigstore's log says when.
gh attestation verify spindle-v0.0.1-x86_64-unknown-linux-gnu.tar.gz --repo tuna-os/spindle
gh attestation verify oci://ghcr.io/tuna-os/spindle:v0.0.1 --repo tuna-os/spindle
--repo is the trust root: the check passes only for attestations signed
by a workflow in this repository. The SBOM beside each tarball is an
SPDX document; gh attestation verify --predicate-type https://spdx.dev/Document checks that it, too, was attested to the same
tarball by the same workflow.
What a tag gives the rest of the project
- Benchmarks can name the Spindle they measured, alongside the Synapse, Continuwuity and Tuwunel versions they already name.
SECURITY.md's "fixed in" can name the first tag carrying a fix.- Storage-format changes can say which tag introduced them, with the schema version the store already writes in its first byte.
Running the image
docker run -v ./spindle.toml:/etc/spindle/spindle.toml:ro \
-v spindle-data:/var/lib/spindle \
-p 8008:8008 ghcr.io/tuna-os/spindle:v0.0.1
The image runs as an unprivileged user and expects the store under
/var/lib/spindle, which [storage] path in the mounted config should
name. The Complement image under complement/ is a different thing: it
satisfies that suite's startup contract and is not for operators.