Releases, promotion and rollback

Immutable releases and their states, binding the production and testing environments with a compare-and-set, rolling back to a retained release, and retiring.

  • Availability: Planned
  • Evidence: Read from source
  • Reference

Operations

Operation Request Capability Retry
releases.list GET /v1/applications/{application}/releases?state= application.read
releases.read GET /v1/applications/{application}/releases/{release} application.read
releases.promote POST /v1/applications/{application}/releases/{release}/promote release.promote (release.prepare for testing) version
releases.retire POST /v1/applications/{application}/releases/{release}/retire release.promote natural
environments.list GET /v1/applications/{application}/environments application.read
environments.read GET /v1/applications/{application}/environments/{environment} application.read
environments.rollback POST /v1/applications/{application}/environments/{environment}/rollback release.promote (release.prepare for testing) version

Which capability you need

Promotion and rollback are the two operations whose capability depends on the environment:

Environment Capability Roles
testing release.prepare owner, admin, developer
production release.promote owner, admin

A developer can therefore bind testing and cannot bind production. The contract records the rule as x-beyond-capability-by on both operations: for promotion the environment is read from the request body, for rollback from the path.

The release

A release is an immutable snapshot. Promotion and rollback rebind it; nothing re-resolves or rebuilds it.

State Meaning
candidate It is being prepared
ready The whole serving closure is durable and retrievable
active At least one environment binds it
retired It can no longer be bound
failed Preparation failed; failure says why
Member Meaning
number Sequential number inside the application, part of its immutable URL
registration, graph The inputs it was prepared from; graph carries its id and digest
inventory Digest of the persisted inventory, once analysis completed
loader, sourcemaps, visibility, targets The settings and targets as they were when it was prepared
readiness complete, required, durable, checked. See Readiness.
urls Origin-relative immutable locations of the release: immutable, the release-scoped prefix /_r/<release number>, and resolution. See Caching and releases.
environments The environments that bind it now

Environments

An application has exactly two environments, production and testing. Each is a binding:

Member Meaning
release The bound release, or null until one is promoted
version Changes with every rebind. It is the compare-and-set value of promotion and rollback.
retained Earlier releases kept eligible for rollback and protected from garbage collection, most recent first
hostnames The hostnames that serve this environment

Promote

Promotion binds an environment to a ready release with an atomic compare-and-set on the binding's version:

JSON
{ "environment": "production", "expected_version": 7 }

If expected_version is not current, nothing changes and the answer is 409 CONFLICT_VERSION with details.current. Read the environment again and decide again; do not retry blindly with the new number.

JavaScriptpromote.mjs
// Bind the production environment to a ready release. The binding changes only
// if nobody else changed it since you read it.
const api = process.env.CDN_API_ORIGIN;
const token = process.env.CDN_TOKEN;
const application = process.env.CDN_APPLICATION;
const release = process.env.CDN_RELEASE;

const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };

const binding = await (await fetch(new URL(`/v1/applications/${application}/environments/production`, api), { headers })).json();
console.log(`production binds ${binding.release ?? 'nothing'} at version ${binding.version}`);

const response = await fetch(new URL(`/v1/applications/${application}/releases/${release}/promote`, api), {
	method: 'POST',
	headers,
	body: JSON.stringify({ environment: 'production', expected_version: binding.version })
});
const document = await response.json();

if (response.status === 409 && document.error.code === 'CONFLICT_VERSION') {
	// Somebody rebound the environment first. Nothing changed: read it again and decide again.
	console.log(`not promoted: the binding is now at version ${document.error.details.current}`);
	process.exitCode = 1;
} else if (!response.ok) {
	console.log(`${response.status} ${document.error.code}${document.error.message}`);
	process.exitCode = 1;
} else {
	console.log(`production now binds ${document.release} at version ${document.version}`);
	console.log(`rollback candidates: ${document.retained.join(', ') || 'none'}`);
}

Promotion keeps the tested graph and artifacts. The release you tested in testing is byte for byte the release you promote to production. A release that is not ready answers 409 STATE_INVALID.

Preview and acknowledge exposure

A release freezes its sourcemaps and visibility when it is prepared. When the release publishes source maps, or is public while the application has since become private, promotion requires an acknowledged member that names exactly that exposure. A request without it, or with other values, changes nothing and answers 400 VALIDATION_FAILED with details.exposure:

JSON
{ "error": { "code": "VALIDATION_FAILED", "message": "…", "details": { "exposure": { "sourcemaps": "public", "visibility": "public", "acknowledgement": true } } } }

That refusal is the preview: it shows what binding the release would expose, with the values frozen in the release and not the application's current settings. Show them to the person who promotes, then send the request again with the confirmation:

JSON
{
  "environment": "production",
  "expected_version": 7,
  "acknowledged": { "sourcemaps": "public", "visibility": "public" }
}

Roll back

Rollback is the same compare-and-set, limited to the retained releases of the environment:

JSON
{ "release": "rel_Prev0001x", "expected_version": 8 }

It reuses retained bytes and never re-resolves or rebuilds.

Retire

Retiring a release that no environment binds means it can no longer be bound, and it stops protecting its closure from garbage collection. A release that is still bound answers 409 STATE_INVALID.

Garbage collection computes what is reachable from active bindings, retained releases and in-flight jobs, including shared references and lazy modules. Inactivity never breaks a published site.