Skip to content

5.10.2.1 Builds

The Builds domain of releases. What a version contains: the catalog of projects, applications, platforms and depots, and the builds with their manifests and chunks, uploaded by CI through signed URLs and deduplicated by content. Staff shape the catalog; the build uploader creates builds; nothing here reaches a client until a release on a channel. It is one API, with its own contract and its own version.

5.10.2.1.1 Versions

Version Status Prod URL Contract
v1 Proposed https://releases.moonitoring.com/builds/v1 builds/v1/openapi.yaml

The resources follow the data model. Adds the upload handshakes (ChunkCheckRequest, ReleaseManifestInput with its signed upload URL, ReleasePatchArtifactInput) and the purge of req 1006.16.

5.10.2.1.2 Operations

Every operation names who calls it and the capability it checks (x-moonitoring-audience, x-moonitoring-capability in the contract); every capability is in the catalog of the data model (req 1002, 1006). Lists are paged by cursor, writes that create something carry an Idempotency-Key, errors are Problem Details.

Operation Request Who Capability
List projects GET /projects Staff releases.releases.read
Create a project POST /projects Staff releases.release_catalog.manage
Update or archive a project PATCH /projects/{project_id} Staff releases.release_catalog.manage
List applications GET /applications Staff releases.releases.read
Create an application POST /applications Staff releases.release_catalog.manage
Update an application PATCH /applications/{application_id} Staff releases.release_catalog.manage
List an application's depots GET /applications/{application_id}/depots Staff releases.releases.read
Compose an application with a depot PUT /applications/{application_id}/depots/{depot_id} Staff releases.release_catalog.manage
Remove a depot from an application DELETE /applications/{application_id}/depots/{depot_id} Staff releases.release_catalog.manage
List platforms GET /platforms Staff releases.releases.read
Create a platform POST /platforms Staff releases.release_catalog.manage
List depots GET /depots Staff releases.releases.read
Create a depot POST /depots Staff releases.release_catalog.manage
Update a depot PATCH /depots/{depot_id} Staff releases.release_catalog.manage
List builds GET /builds Staff releases.releases.read
Create a build POST /builds Staff releases.builds.upload
Read a build GET /builds/{build_id} Staff releases.releases.read
Check which chunks to upload POST /builds/{build_id}/chunks/check Staff releases.builds.upload
Register a manifest POST /builds/{build_id}/manifests Staff releases.builds.upload
Set a build's manifest for a depot POST /builds/{build_id}/depots Staff releases.builds.upload
List a build's depots GET /builds/{build_id}/depots Staff releases.releases.read
List a build's installers GET /builds/{build_id}/installers Staff releases.releases.read
Finalize a build POST /builds/{build_id}/finalize Staff releases.builds.upload
Revoke a build POST /builds/{build_id}/revoke Staff releases.builds.revoke
Read a manifest GET /manifests/{manifest_id} Staff releases.releases.read
List a manifest's files GET /manifests/{manifest_id}/files Staff releases.releases.read
List a depot's patches GET /depots/{depot_id}/patches Staff releases.releases.read
Register a patch POST /depots/{depot_id}/patches Staff releases.builds.upload
Purge unreferenced content POST /storage/purge Staff releases.release_storage.purge

5.10.2.1.3 Contract v1

Proposed version

v1 is a proposal for review: its endpoints, errors and capabilities were designed from the requirements and the flows and are not confirmed. It leaves proposal with its first release; from then on it changes only as described in Every API Is Versioned.

5.10.2.1.4 Rules Confirmed with the Implementation

Decided on 2026-11-05, from the choices made while building releases:

  • Manifest files come from the manifest itself: when releases validates an uploaded manifest it fills its files from it; no input carries them.
  • An application's slug is unique across projects, so the download page is addressed by it alone.
  • Only an application marked carries_adapters, the farm sync client, may declare the adapters its builds carry; a build of any other application that declares some is a 422.
  • Installers: every build has a single installer file per platform (decided 2026-11-11), the installer of releases made for the build's application when the build becomes ready (ReleaseInstallerArtifact). It embeds the application, the build and the root metadata of every listed channel of the application at that moment, so it verifies what it installs from a pinned root and needs no anonymous root path, and the hosts of the environment with what stands in front of them, so one base installer serves every environment (decided 2026-11-13, Installer and Client Library); it carries no secret, and the person signs in through the browser when it runs. Executables stay unsigned for now (docs PR 109). The download links give it.

5.10.2.1.5 Uploading a Version from the Console

A member of staff with releases.builds.upload uploads a new client version from the Console, in the browser or on the desktop (decided 2026-11-11). CI keeps publishing with releases-publish, through the Console's host too (below); staging and the two-person approval do not change, and whoever created the build cannot approve its release.

  1. The person chooses the application, the version label and a folder for each platform and architecture, with the browser's directory picker.
  2. The Console packs each folder itself with releases-client, feature pack, the same code as releases-publish: FastCDC chunks, BLAKE3 or SHA-256 hashes, zstd compression and the manifest of format 1. Compiled to wasm32-unknown-unknown, it needs the C library of zstd built for WebAssembly with clang, through the zstd crate, at the same version and level as releases-publish. A pure-Rust encoder was not chosen: it would store chunks as other bytes than CI does, and a manifest records size_stored for chunks that are deduplicated by their uncompressed hash. blake3 and fastcdc build for WebAssembly as they are.
  3. It creates the build, asks which chunks to upload, and uploads chunks and manifest blobs straight to the signed storage URLs from the browser. This needs CORS on the content bucket for the Console's host (console.moonitoring.com, stage-console.moonitoring.com), a change of moonitoring-infra.
  4. console-server carries only the small JSON calls (create, check, register, finalize). No call of the upload needs a body above Cloud Run's limit of about 32 MiB: chunks, manifests and the installer files never pass through it.
  5. On finalize, releases makes the build's installer of each platform. The Console then shows copy buttons for each version link and for the application's universal latest link, next to each build and each release.

CI uploads through the Console's host as well, with no key (decided 2026-11-13). releases-publish calls the same operations at https://<env>-console.moonitoring.com/api/releases (https://console.moonitoring.com/api/releases in prod), never at the host of releases:

  • Proxy-Authorization: Bearer carries a Google ID token of the CI's Google service account, for an audience the Console's IAP admits; IAP lets it in and passes console-server its assertion.
  • x-moonitoring-console is on every call, as the Console's app sends it; console-server refuses a call without it.
  • No Authorization and no staff token in the CI: console-server exchanges the assertion at access for the staff token of the staff service account linked to that Google service account (Service Accounts That Sign In Through IAP) and forwards each call to the private URL of releases with it. Chunks and manifest blobs go straight to the signed storage URLs, as from the browser.
  • The staff service account holds releases.builds.upload and releases.releases.read, and nothing else (decided 2026-11-13). releases-publish first reads the applications, depots and builds (GET /applications, /depots, /applications/{application_id}/depots, /builds/{build_id} and /builds/{build_id}/depots), which need releases.releases.read. It does not hold releases.releases.stage, so CI creates and finalizes builds but never stages them: a person does, and a second one approves.

5.10.2.1.6 The Manifest, Format 1

A manifest's blob is the full file list of one depot, chunked so that an update downloads only what changed (decided 2026-11-10, implemented by releases-publish and read by the farm sync client). The Builds API keeps the blob opaque; CI writes it and the client reads it. Format 1 (format_version: 1) is the canonical JSON (keys sorted, no whitespace) of:

{"chunk_hash":"blake3",
 "chunking":{"algorithm":"fastcdc","avg_size":65536,"hash":"blake3","max_size":262144,"min_size":16384},
 "files":[{"chunks":[{"compression":"zstd","hash":"<64 hex>","size_raw":70000,"size_stored":31000}],
           "flags":["executable"],"hash":"<64 hex>","path":"bin/farm-sync.exe","size":70000}],
 "format_version":1,"total_size_raw":70000}
  • Files are sorted by path (relative, /); a file is its chunks in order, and its hash is of the whole file.
  • Chunks are cut with FastCDC using the depot's chunking_params, so a changed file shares most chunks with the build before. A chunk's hash is of its uncompressed content, with the depot's hash (blake3 or sha256); it is stored zstd-compressed when that is smaller, otherwise as it is (none).
  • The manifest's content_hash is the SHA-256 of the blob. The channel's targets document names the blob <depot id>/<content_hash>.manifest with its length and SHA-256, so a client checks the blob against the signed chain before it trusts any hash in it, then each chunk and each file against the blob.
  • A chunk is downloaded from the update check's chunk_base_url with its hash appended to the path, keeping the query of the signed prefix. The base is a short signed URL of the CDN in prod and in stage alike (decided 2026-11-11), so it is never empty.
  • Encrypted depots and patch artifacts are not in format 1.