Skip to Content
InfrastructureRelease Registry

Release Registry

The releases/ directory at the repo root is the platform’s per-component registry of release records. For every released version of every component built in this repo (aurora-webapp, orbit-webapp, maxq-orbit-agent, maxq-orbit-agent-webapp, and the registry services customer-service, tenant-service, solution-service), there is one small release.yaml that pins the immutable source (git tag + commit) and a reference to the built output — a container image in Azure Container Registry.

Reference, not bytes

A release record never holds build artifacts. It points at them:

  • Source is pinned by an immutable git tag (<component>/v<semver>) and the commit SHA it resolves to. Git already stores the exact source snapshot.
  • Output is pinned by a container image reference — registry / repository / tag (and optionally a sha256: digest). ACR retains every pushed image.

Committing build output to the repo would duplicate what git and ACR already retain immutably, and would bloat history. So the record is a thin, validated YAML pointer connecting the two — the authoritative “what was released, from which commit, as which image” ledger that deployment tooling and (eventually) the Aurora portfolio view can read.

Directory layout

One directory per component, one directory per version, one record per version. Version folders use the v<semver> form (e.g. v1.3.0), echoing the methodology repo’s releases/v1.8/ convention.

releases/ ├── release.schema.json # JSON Schema (draft 2020-12) for every release.yaml ├── aurora-webapp/ │ ├── v0.1.0/ │ │ ├── release.yaml # the record (managed by build-images.sh) │ │ └── release-notes.md # optional human narrative │ ├── v1.0.0/release.yaml │ ├── v1.1.0/release.yaml │ ├── v1.2.0/release.yaml │ └── v1.3.0/release.yaml ├── orbit-webapp/ │ ├── v1.0.0/release.yaml │ ├── v1.1.0/release.yaml │ └── v1.2.0/release.yaml ├── maxq-orbit-agent/ │ ├── v1.0.0/release.yaml │ └── v1.1.0/release.yaml ├── maxq-orbit-agent-webapp/ │ └── v1.1.0/release.yaml ├── customer-service/v0.1.0/release.yaml ├── tenant-service/v0.1.0/release.yaml └── solution-service/v0.1.0/release.yaml

Alongside release.yaml, a version directory may hold an optional release-notes.md (human narrative). A package/ subfolder is a reserved escape hatch for release-only material (deploy config, env template, migration notes) — it is never created today and must never contain build output or a source copy.

The release.yaml schema

Every record validates against releases/release.schema.json (JSON Schema draft 2020-12, $id: https://maxqlabs.be/orbit/release.schema.json). The schema sets additionalProperties: false at every level — unknown keys are rejected.

House convention: release records do not carry a $schema key inside the data file (matching solution.yaml). Validation is external — use check-jsonschema if available, else python3 with jsonschema + pyyaml.

Top-level fields

FieldTypeRequiredDescription
componentstringyesComponent slug; matches the implementation/ directory name. Pattern ^[a-z][a-z0-9-]*[a-z0-9]$.
versionstringyesSemantic version, no leading v (e.g. 1.3.0). Pattern allows a pre-release suffix (-…).
statusstringyesOne of draft, preview, production, superseded, rolled-back (see lifecycle below).
released_atstring (date)yesDate the entry was cut, YYYY-MM-DD. Set by the operator/script, not derived.
released_bystring (email)yesPerson who cut/owns the release.
previous_versionstring or nullnoThe version this one supersedes, or null for the first release.
sourceobjectyesThe immutable input: where the code came from.
artifactobjectconditionalReference to the built/deployed output. Required once status is anything other than draft (enforced by an allOf/if rule).

Status lifecycle (from the schema’s own descriptions):

StatusMeaning
draftCut, not yet deployed.
previewDeployed to a preview URL.
productionPromoted to the production domain.
supersededReplaced by a newer production release.
rolled-backWas production, reverted.

source fields

All three are required; additionalProperties: false.

FieldTypeDescription
source.repostringGit repo the component lives in. Records written since the 2026-07-02 repo consolidation say orbit; older aurora-webapp records say aurora — historical, left as written.
source.git_tagstringThe scoped tag component/vX.Y.Z (pattern-enforced), so multiple components in one repo tag independently.
source.commitstringFull or abbreviated commit SHA the tag points at (7–40 hex chars).

artifact fields

artifact.target is required and discriminates the shape; the schema’s conditional rules make the target-specific fields mandatory.

FieldTypeApplies toDescription
artifact.targetstring enumcontainer or vercel. Everything new is container; vercel survives only for historical records.
artifact.registrystringcontainer (required)Registry login server the image was pushed to (e.g. acrorbit.azurecr.io).
artifact.repositorystringcontainer (required)Image repository/name within the registry (e.g. orbit-webapp).
artifact.tagstringcontainer (required)The immutable version tag pushed (e.g. 1.2.0). The image is also pushed as latest.
artifact.digeststringcontainer (optional)Content-addressable image digest (sha256: + 64 hex) — the permanently re-deployable artifact.
artifact.projectstringvercel (required)Vercel project name or id.
artifact.deployment_idstringvercel (required)Immutable Vercel deployment id (dpl_…).
artifact.urlstringvercel (required)Deployment URL returned by Vercel.

Quote the date. released_at must be a string ('2026-06-26'). A bare released_at: 2026-06-26 is parsed by PyYAML as a datetime.date, and schema validation then fails on type: string. The script writes it quoted; keep it quoted when hand-editing.

A real record

releases/aurora-webapp/v1.3.0/release.yaml, verbatim:

releases/aurora-webapp/v1.3.0/release.yaml
# release.yaml — managed by infrastructure/azure/build-images.sh. # Schema: releases/release.schema.json # A `container` artifact: the immutable image pushed to ACR (also tagged :latest), # built from the git tag below. component: aurora-webapp version: 1.3.0 status: draft released_at: '2026-06-26' released_by: [email protected] previous_version: null source: repo: orbit git_tag: aurora-webapp/v1.3.0 commit: 8d214a1a67b4e155241dfea69d68ad9435c3f0b4 artifact: target: container registry: acrorbit.azurecr.io repository: aurora-webapp tag: 1.3.0

The registry-service records look the same shape — e.g. releases/customer-service/v0.1.0/release.yaml records customer-service/v0.1.0 at commit 586b6e9… as acrorbit.azurecr.io/customer-service:0.1.0.

Who writes the records: build-images.sh

Records are written (and reconciled) by infrastructure/azure/build-images.sh — the container build worker. Its release discipline: the image is built from an immutable git tag, not your working tree, so the image, the recorded commit, and the git_tag can never drift.

commit → git tag <component>/v<version> → build-images.sh <target> → deploy

What the script does per target:

  1. Resolves the version — an explicit @<version> pin, or the component’s latest component/v* tag (sorted by -v:refname). Dies with tag-creation instructions if the tag doesn’t exist.
  2. Resolves the tag to a commit (dereferencing annotated tags).
  3. Checks the tag out into a throwaway git worktree — your checkout is left untouched; worktrees are cleaned up on exit.
  4. Builds server-side via az acr build (ACR Tasks — no local Docker): the tagged tree is uploaded to ACR, built in westcentralus, and on success pushed tagged both :<version> and :latest.
  5. Writes/reconciles the record at releases/<component>/v<version>/release.yaml via an embedded Python snippet: a container artifact (registry/repository/tag) plus the tag’s commit, always with status: draft — the image exists in the registry but isn’t yet promoted to a running app; deploy flips the status later. An existing file is patched in place, preserving its comment header and previous_version. The cut date comes from a RELEASED_AT constant in the script (deliberately not shelled out from date), owner from RELEASED_BY.

The script is idempotent: re-building a version re-pushes the image (same tag, same code) and reconciles the existing release.yaml in place.

Build-context details worth knowing (encoded in the script’s image_meta): orbit-webapp, orbit-agent, and the three registry services build with context implementation/ (not their own codebase/) because their Dockerfiles copy shared packages (shared/trajectory-loader, shared/registry-kit); aurora and orbit-agent-webapp build from their own implementation/<name>/codebase context.

Versioning conventions

  • Per-component semver. Each component has its own version line and its own tag cadence — components are not upgraded in lockstep. That’s why tags are scoped (aurora-webapp/v1.3.0, orbit-webapp/v1.2.0) rather than repo-wide.
  • No all target. build-images.sh all deliberately errors: building everything at one shared version doesn’t match independent versioning. A future all should read a platform-version manifest (e.g. releases/platform/v<X>.yaml) that pins each component’s version — not a shared version argument.
  • Per-repo registry. releases/ lives with the code it describes; the YAML files are the authoritative record, build-images.sh the worker that writes them.

Connection to Azure deployment

Azure Container Apps is the sole deployment target, and images are shared per release: one image per component per version in the shared ACR (acrorbit.azurecr.io), consumed by the singleton Aurora app and by every per-solution Orbit stack (reader + agent + Mission Control) that the auto-deploy provisions. After a build, the operator bumps the corresponding images.*_tag in infrastructure/azure/config.local.yaml and runs deploy.sh aurora (or per-solution provisioning simply picks up the new tag). The release record is the ledger tying that deployed tag back to its exact commit.

What was retired: the Vercel path

The Vercel deploy path ended on 2026-07-02: the infrastructure/vercel/release.sh drive tool (with its cut / deploy / promote / rollback / validate subcommands) was retired and deleted along with the Vercel project. Old aurora-webapp records with target: vercel remain in the registry as history, and the schema keeps the vercel enum value so they stay valid. Everything new records a container artifact.

Cutting a release, step by step

Commit and tag

Land the change on the repo, then create and push the scoped, immutable tag:

git tag aurora-webapp/v1.3.0 <commit-ish> git push origin aurora-webapp/v1.3.0

Build, push, and record

Run the build worker with one or more targets (requires an az session — managed identity in ACA, or az login):

infrastructure/azure/build-images.sh aurora # latest aurora-webapp/v* tag infrastructure/azure/build-images.sh [email protected] # or pin explicitly infrastructure/azure/build-images.sh aurora orbit-webapp # multiple, each at its OWN latest tag

Valid targets: aurora, orbit-webapp, orbit-agent, orbit-agent-webapp, customer-service, tenant-service, solution-service. This builds from the tag via az acr build, pushes :<version> + :latest, and writes releases/<component>/v<version>/release.yaml with status: draft.

Commit the record

The new/updated release.yaml is a working-tree change — commit it like any other file.

Deploy

Bump the matching images.*_tag in infrastructure/azure/config.local.yaml and run deploy.sh aurora; per-solution Orbit stacks pick up the new tag at provisioning time.

Status promotion beyond draft (to preview/production, and later superseded/rolled-back) is currently a manual edit of the record — the Vercel-era tool that automated promotion was retired, and the container-path scripts only ever write draft.