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:
{ "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.
// 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:
{ "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:
{
"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:
{ "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.