Several version axes move independently in this repository; confusing them
is the main hazard. This page says what each one means and what forces it
to change. Schema-3 frontend releases and the Go module major share the
v3.* git tag namespace: the module path is
github.com/docker/sandbox-kit-spec/v3.
| Axis | Where it lives | Moves when |
|---|---|---|
| Module / schema-3 release | git tags v3.X.Y (+ Hub docker/sandbox-kit:3.X.Y) |
A publish of the Go packages (spec, resolve, assemble, fetch, tck), the schema-3 BuildKit frontend, and kit-tck binaries |
| Schema version | spec.SchemaVersion, the descriptor's schemaVersion: "3" |
The descriptor grammar changes shape incompatibly |
| Capability version | the @N in com.docker.sandbox/<name>@N |
That capability's config schema changes after it has shipped |
| Frontend floating tag | docker/sandbox-kit:3 |
Tracks the highest stable schema-3 frontend release |
A kit's own version: and its provides entries are a further axis, but
they belong to kit authors rather than to this repository;
SPEC-v3 §5.2 governs them.
v3.* git tags are Go module versions for
github.com/docker/sandbox-kit-spec/v3. Consumers import packages as
github.com/docker/sandbox-kit-spec/v3/spec (and siblings) and resolve
them with go get github.com/docker/sandbox-kit-spec/v3@v3.X.Y. The same
tag also publishes the frontend image and attaches kit-tck release
assets.
Tag when the Go packages (and the matching frontend / kit-tck release)
should be consumable at a new version:
git tag -a v3.X.Y && git push origin v3.X.Y. Prefer annotated tags.
Pre-release suffixes (v3.0.0-m.2, v3.0.0-rc.1) are valid module
versions and trigger the release workflow without moving floating Hub
:3.
schemaVersion: "3" names the generation of the descriptor grammar.
Decoding is strict (SPEC-v3 §1.2),
in two passes with two mechanisms. The descriptor itself is YAML-decoded
with KnownFields(true) (spec.Decode), which rejects an unrecognized
top-level field. A capability's config survives that pass as a plain
map and is decoded per type later, with DisallowUnknownFields
(spec.DecodeCapabilityConfig), which rejects an unrecognized key in a
well-known type's config. Config for a type the reader does not know is
the exception at both steps — it stays an opaque map, which is what lets
third-party types travel.
For anything the grammar defines, then, the compatibility question is not "would an old reader ignore this?" — it would not; it would refuse the document.
What that buys is a grammar where a descriptor is either understood
completely or rejected loudly. What it costs is that adding a field is
not free: a descriptor using one cannot be read by a frontend or
runtime built before it. Adding a field to a capability's config moves
that capability's version, which is the fine-grained lever; moving
schemaVersion is for changes that lever cannot express — a top-level
field added, renamed, or removed, or a different meaning for one that
stays. An addition counts: strict decoding means an older reader refuses
a descriptor that uses it.
Moving it is expensive: the frontend image tag follows, every # syntax=docker/sandbox-kit:N line in the wild points at the old one, and
spec.SchemaVersion gates decoding. Treat it as a new specification
document (docs/spec/SPEC-v4.md) rather than an edit to the current one.
Each capability type addresses its own config schema by version, so
the version moves when that config schema changes — the rule
SPEC-v3 §7 states — and the old
version stays published: network-policy@1 and @2 both exist, and a
descriptor states one of them.
"Changes" is not only "gains a field an old runtime would reject on decode". A field whose meaning, default, or permitted values change is worse, because an old runtime accepts it and then enforces the wrong policy — a silent misreading of a permission grant rather than a loud failure. Both move the version.
The rule has one carve-out worth stating plainly, because it recurs in review: a capability that has never appeared in a tagged release has no runtime built against it, so its schema may still change in place. The moment it ships, that freedom ends.
Adding a whole new capability type is additive and moves nothing.
Pushing a v3.X.Y (or v3.X.Y-rc.N) tag runs the release workflow, which
publishes docker/sandbox-kit:3.X.Y and, when that tag is the highest
stable v3.*.* on the remote, also moves floating docker/sandbox-kit:3.
Main commits publish docker/sandbox-kit:<short-sha> only.
To publish by hand:
FRONTEND_PUSH_OK=1 task frontend:push FRONTEND_VERSION=3.0.0
# also move floating :3 (only when this is the intended tip):
FRONTEND_PUSH_OK=1 FRONTEND_PROMOTE_MAJOR=1 \
task frontend:push FRONTEND_VERSION=3.0.0FRONTEND_PUSH_OK is a precondition, not decoration: these tags are the
syntax references descriptors resolve, so publishing is never one
forgotten flag away. Kits name the floating major in their # syntax=
line, so :3 must keep building every descriptor of that generation —
rebuild and promote it whenever the grammar gains something kits may
use, and never repoint it at a frontend that would reject an older v3
descriptor.
Both binaries carry the tag they were built from and the commit beside
it, in internal/version. Neither can read git for itself — GoReleaser
links kit-tck from a tagged checkout, and the frontend is linked inside
a Docker build whose context excludes .git — so the values are handed
down as linker flags, from {{.Version}}/{{.FullCommit}} in
.goreleaser.yaml and from the VERSION / REVISION
build args in Dockerfile. A build nobody stamped says
dev, which is what a local docker build or task kit:dev produces.
The stamp is a reflection of the module tag, not a fourth axis: nothing here moves on its own. Where it shows up:
| Surface | Form |
|---|---|
kit-tck version, report header, --format json |
3.0.0-m.5 (2f9a1c4e); the JSON envelope keeps version and revision apart |
| Frontend build progress | [internal] load kit descriptor <file> · sandbox-kit 3.0.0-m.5 (2f9a1c4e) |
| Every kit the frontend publishes | the vnd.docker.sandbox.kit.built-by annotation (SPEC-v3 §9.3) |
frontend:push stamps the same string it tags the image with, so a
frontend can never report a release it was not published as — the rule
kit tags already follow. Only kit-tck feeds its version to spec links,
and it passes the tag alone: a revision is not a ref, and a URL built
from one resolves to nothing.
task tck:runtime ADAPTER=<path> judges a runtime through its adapter,
and task tck:kit REF=<ref> judges a published artifact; task test:tck
runs the same runtime suite against the repository's fake adapter, which
is how the suite itself is kept honest. All are versioned with the module
rather than separately — a release of the Go packages is also the release of the
conformance suites, and a suite that gains a check can fail a runtime
that passed the previous tag. That is intended: the check reflects a duty
the specification already stated.