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 master moves 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)
Rolebranchrelease
Mutableyes โ€” you write to it, promote itno โ€” frozen at publish
Movesadvances with every commitnever
Admin endpoint_envs_releases (below)
Typical usesandboxes, staging, per-user copiesversion 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:

OperationCallKey
ListGET /api/v2/_releasesread
Publish current headPOST /api/v2/_releases { "name": "v1" }read-write
RetireDELETE /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โ€ฆ" }
   ] }
  • role is branch (envs, including the default env.master) or release.
  • mutable reflects the role โ€” branches are writable, releases are frozen.
  • default: true marks the master branch.
  • Filter with ?role=branch or ?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:

CallEffectKey
PUT /api/v2/_refs/{branch} { "from": "env.master" }create a branch from another branchread-write
PUT /api/v2/_refs/{branch}?force=true { "from": โ€ฆ }move an existing branchread-write
PUT /api/v2/_refs/release.{name}freeze the current head as a releaseread-write
DELETE /api/v2/_refs/{branch}delete a branchread-write
DELETE /api/v2/_refs/release.{name}retire a releaseread-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):

ConditionStatus
Write a protected ref (_โ€ฆ, system)403
Branch named with a reserved role prefix (env. / release.)400
Re-publish an existing release409
Move an existing branch without force409
Branch write missing from400
Delete the default (master) branch400
Branch from a release (not supported yet)400
Delete / retire a missing ref404

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