Versioning: Releases (v2)
A release is an immutable, named pointer to a committed database head โ the
frozen counterpart to a mutable environment. Publishing
v1 snapshots the current state under the name release.v1; that pointer never
moves again. It's the same idea as a git tag: a branch advances, a tag stays
put.
A release is the tag role of the ref model: an env
is a mutable branch, a release is an immutable tag, and both are names pointing
at a committed head. The _refs endpoint lists them together so a console can
render the whole version graph from a single call, and (on v2) offers a generic,
git-style write path underneath the intent-named _envs/_releases endpoints.
This version-level ref (the _refs admin endpoint) is unrelated to the
$refs query operator, which follows a link the other way
inside a query. One versions the database; the other reads a row's inverse
links.
Why releases
- Reproducible pins. Freeze the dataset behind a benchmark or a demo so a
later query returns exactly what it returned then, even as
mastermoves on. - Immutable by contract. Re-publishing an existing release is refused
(
409) โ a version can't be silently rewritten. Retiring is the only way a release leaves. - Free durability. A release is a garbage-collection root: the head it pins (and everything reachable from it) survives GC until the release is retired. No extra pinning bookkeeping.
- Mountable reference data. A shared repository database can publish
release.v1,release.v2, โฆ and a tenant database mounts a specific release read-through (zero-copy). See Mountable repositories below.
Releases vs environments
| Environment (branch) | Release (tag) | |
|---|---|---|
| Role | branch | release |
| Mutable | yes โ you write to it, promote it | no โ frozen at publish |
| Moves | advances with every commit | never |
| Admin endpoint | _envs | _releases (below) |
| Typical use | sandboxes, staging, per-user copies | version pins, published reference data |
Both are selected by the same machinery and both are visible in _refs. An env
is addressable in the URL (/env/{name}/api/v2/โฆ); a release is a pin you
publish from and mount, not a query path.
Managing releases
The _releases admin endpoints (shared across v1 and v2 โ releases are one
mechanism) list, publish, and retire:
| Operation | Call | Key |
|---|---|---|
| List | GET /api/v2/_releases | read |
| Publish current head | POST /api/v2/_releases { "name": "v1" } | read-write |
| Retire | DELETE /api/v2/_releases/{name} | read-write |
Publish freezes the database's current committed head under
release.<name> and returns its content hash:
POST /api/v2/_releases { "name": "v1" }
โ 200 { "status": "published", "name": "v1", "hash": "q3zโฆ" }
- Re-publishing an existing name โ
409(a release is immutable; retire it first). - A name starting with
_is reserved โ400; names follow the same rules as env names. - Publishing before the database has any committed state โ
400.
List returns every published release with its frozen head:
GET /api/v2/_releases
โ { "releases": [ { "name": "v1", "hash": "q3zโฆ" } ] }
Retire removes the pin. The head becomes GC-eligible again unless another
ref still reaches it. Retiring a name that doesn't exist โ 404.
The unified ref view
GET /api/v2/_refs lists branches and releases together โ the git plumbing
read beneath _envs and _releases:
GET /api/v2/_refs
โ { "refs": [
{ "name": "env.master", "role": "branch", "mutable": true, "default": true, "head": "a1bโฆ" },
{ "name": "sandbox", "role": "branch", "mutable": true, "head": "c4dโฆ" },
{ "name": "release.v1", "role": "release", "mutable": false, "head": "q3zโฆ" }
] }
roleisbranch(envs, including the defaultenv.master) orrelease.mutablereflects the role โ branches are writable, releases are frozen.default: truemarks the master branch.- Filter with
?role=branchor?role=release.
A read key is sufficient. _refs is read-only on v1; the generic write
plumbing below is v2-only.
Generic writes (v2)
PUT and DELETE on /api/v2/_refs/{ref} are the single write path the
intent-named endpoints are sugar over. The same invariants are enforced here in
the core, so no surface can bypass them:
| Call | Effect | Key |
|---|---|---|
PUT /api/v2/_refs/{branch} { "from": "env.master" } | create a branch from another branch | read-write |
PUT /api/v2/_refs/{branch}?force=true { "from": โฆ } | move an existing branch | read-write |
PUT /api/v2/_refs/release.{name} | freeze the current head as a release | read-write |
DELETE /api/v2/_refs/{branch} | delete a branch | read-write |
DELETE /api/v2/_refs/release.{name} | retire a release | read-write |
main and master are accepted as aliases for the default branch key. A
release ref is addressed as release.{name} โ the same <role>.<name> shape as
the default branch env.master, so branches and releases share one separator.
Because those role prefixes are how the two kinds coexist in one {ref} space,
a branch may not be named with a reserved env. or release. prefix.
Invariants (all enforced in the core, identical across plumbing and porcelain):
| Condition | Status |
|---|---|
Write a protected ref (_โฆ, system) | 403 |
Branch named with a reserved role prefix (env. / release.) | 400 |
| Re-publish an existing release | 409 |
Move an existing branch without force | 409 |
Branch write missing from | 400 |
| Delete the default (master) branch | 400 |
| Branch from a release (not supported yet) | 400 |
| Delete / retire a missing ref | 404 |
Plumbing vs porcelain. Prefer the intent-named _envs and _releases
endpoints for everyday work โ they read as what you mean (branch, promote,
publish, retire). _refs is the uniform read and the low-level write the
others are built on; reach for it when you want the whole ref graph in one
call or a generic tool that treats every ref the same.
Mountable repositories
Releases are the versioning primitive behind mountable repositories: a shared
repository publishes immutable releases of reference data (company registries,
postal codes, demo datasets), and a tenant database mounts a chosen release.vN
read-through. This is how free-trial accounts get demo data the moment they're
created. See the Mountable Repositories guide for the
mount mechanics (the basedOn store with a baseTag), what a tenant sees, and
the full free-trial provisioning lifecycle.
Related: Environments & Auth ยท Quickstart ยท Common Errors