What this deployment is running, and how far back it can be taken.
const url = 'https://example.com/api/v1/system/upgrade';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://example.com/api/v1/system/upgrade \ --header 'Authorization: Bearer <token>'Authorizations
Section titled “Authorizations”Responses
Section titled “Responses”The running build, the applied schema, and the compatibility floor
The state of the upgrade mechanism and of this deployment’s schema.
object
Releases the updater last saw in the registry; null when it has never looked. Carries the
raw list and why it may be empty — offers is the part a page should render.
object
Why the list is empty or stale, when the sidecar could not reach the registry. A closed network has no registry, so this is an ordinary state rather than a fault.
Releases found, newest first.
One release the sidecar found, with the digests it resolved.
object
Resolved image digest for yagra-core at that tag; null when it could not be resolved.
The release tag (v0.2.2).
Unix seconds when the list was refreshed.
Everything this deployment is made of — core and every poller — with what each runs and whether an upgrade can move it (ADR-051 Inc.6).
Replaces the two fields that answered the same question from opposite ends (pollers, which
said who an upgrade carried and who it stranded, and poller_alignment, which said who was
off core’s build). They were views of one table and could disagree about which poller a row
was; a selection dialog needs the table.
There is no “nobody asked” state here. The old pollers field was null until the
central updater named its own compose project, because without that list core cannot tell a
co-located poller from a remote one. That is now a property of a row (co_located), so a
deployment with no central updater still gets a full list — and still gets the button, since
aligning remote sites touches nothing on this host.
One row of the components list: something this deployment is made of, and what it runs.
Deliberately does not say whether the row is already on the target. That depends on which release the operator picked, which this does not know; it is a string comparison the caller makes.
object
It shares core’s compose project, so it follows core’s own checkbox and cannot be unselected on its own.
Sanitized poller id, or core for the core+WebUI row.
Which kind of thing this is.
How many live pollers this row’s pool has, including this one; 0 for core.
Carried so the caller can say which pools go dark for the selection actually made. It
used to be a dark_pools field computed from “everything that could move”, which stops
being true the moment a row can be unchecked — and a warning that does not follow the
checkboxes is worse than none, because it reads the same when it matters.
It is ahead of this core, so moving it to core’s build is a downgrade.
Computed here and not in the WebUI on purpose: ordering versions is semver, where 0.2.10
comes after 0.2.9, and this repository already keeps that comparison in one place because
the other thing deciding from it is whether a rollback is allowed.
Moving this component would carry it to a site that has not said an upgrade is safe there.
A remote site replaces its own poller by running docker compose beside it. A site whose
updater predates the fix for that operation resolves the poller’s certificate mount against
the wrong directory, and Docker creates the missing source empty rather than failing —
so the replacement starts, has nothing to trust the bus with, and never comes back. Both
ends report success, because the command exited 0.
The site declares the opposite when it can, so true covers a site running an older
updater, one whose updater is stopped, and one that has never said. All three call for the
same thing before a press, and none of them may read as safe. false for anything an
upgrade would not move on its own — this deployment’s core, a poller sharing its compose
project, one that is offline, and one with no site updater at all.
Clearing it takes recreating the container that answers, which is not the same as
replacing the file it was created from. An apply installs the site’s new composition and
then recreates poller by name — deliberately, because a bare up -d would destroy
the updater running the apply — so a site can hold a current composition and a stale
updater, and this stays true until that service is recreated. The cheap repair is
therefore up -d --force-recreate yagra-poller-updater at the site; re-issuing the
bundle is for a site whose composition is itself older than the fix. Setting
YAGRA_CERT_DIR to an absolute path makes the next upgrade survivable and clears nothing
here — deliberately, because the site is still on the old updater.
The pool it serves; null for core, which serves none.
Its site updater’s last word, or null when it has none or has never been asked.
object
Which half of the upgrade the site is in.
A sentence for the operator, written at the site.
How it is going.
Where in the command it is: pull, compose, verify, start. Free text on purpose — it
is shown as a detail, never keyed on, so a site updater may add a step without this core
needing a name for it.
Whether an upgrade can move it at all. A false row is still listed — dropping it would
read as “that poller is gone” rather than “that poller cannot come”.
What it is running now, when it has reported a version.
The running build.
object
release or ci-fast (/etc/yagra-build-profile); null outside a container.
The crate version this core was compiled from.
The container’s hostname, which is how a replica identifies itself in the logs.
The commit the image was built from (/etc/yagra-source-ref); null outside a container.
Seconds since this process started.
Whether an upgrade can be requested from here — the updater is deployed, alive, and the
operator’s switch is on. When false, this response describes only what is installed.
The most recent run, finished or in flight; null when none has ever been requested.
object
apply or bundle — the commands that produce a run. (refresh writes no status: it
changes nothing, and a status file of its own would blank the last upgrade’s outcome.)
Unix seconds when it ended; null while it is still running.
The run id core generated when it wrote the request.
Human-readable detail, especially the reason for a failure or refusal.
Who asked for it.
Unix seconds when the run began.
running, succeeded, failed or rejected.
Which phase the run reached — backup, pull, compose, verify.
The release the run targets, when it targets one.
The moves this deployment could make, each with its direction and whether it is allowed. Excludes the running version. Empty when the updater has found nothing.
A release the operator could move to, with the direction and the verdict already decided by the server — the comparison is semver, and it is the same rule that decides whether a rollback is safe.
The poller convergence this core is running, or the last one it ran; null when it has run
none (ADR-051 Inc.6).
The half of an upgrade that had no progress at all. core’s own run reports through
last_run and ends at verify; everything after that — a site pulling over a WAN link, one
container recreated at a time per pool — was invisible, so an operator who pressed the button
watched a page that did not change for as long as half an hour.
Kept after it finishes, which is what puts “this site did not come back” on a screen for the first time: a site killed by its own upgrade drops off the live registry, so it used to vanish from the list and the deployment reported itself aligned.
⚠️ In memory, so a core restart loses it — and under HA only the leader has one.
object
Unix seconds it ended; null while it is still going.
Who asked for it.
The upgrade run this belongs to; the same id every site stamps its own audit line with.
Unix seconds it began.
The release every target is being moved to.
Every poller in the run, in the order the queues take them.
One poller of a convergence, and where it has got to.
object
Sanitized poller id.
The pool whose queue it is in. Pools converge in parallel, so more than one row can be
applying at once — a screen that says “now upgrading
What it is doing about this run.
Applied migrations and the compatibility floor they imply.
object
How many migrations have been applied.
The binding compatibility floor, or null when every applied migration is reversible.
object
Oldest core version that can start against this schema, as bare semver ("0.3.0").
Why that migration narrowed the schema.
The migration that imposed the floor.
The newest applied migration version; null only on a database with none.
The updater container’s own state.
object
Whether it will install an uploaded image archive. Off unless the host’s compose file turns it on — it is the only path that can bring an image this deployment never named onto the host, so it is gated separately from the mechanism as a whole (ADR-050 Increment 3).
The largest archive core will accept, in bytes. Present whenever allow_bundle is, so the
UI can refuse an oversized file before spending an hour uploading it.
How often it re-checks the registry, in seconds.
Whether its last report is recent enough for it to be considered alive.
Whether this deployment has an upgrade mechanism at all — that is, whether the host wired
the hand-off directory in. false describes how the deployment was installed rather than
anything that went wrong: upgrading from the WebUI needs a container composition that
carries the privileged updater alongside core, so a native installation, or a composition
that does not ship it, cannot offer it. The command-line upgrade path is unaffected.
Unix seconds of its last report.
Whether the sidecar has seen the switch turned off. Distinct from upgrade_enabled, which
is what this deployment stored: while the two disagree the change has not reached the
sidecar yet, which takes at most one of its beats.
Whether the updater has ever reported. Meaningful only where installed is true, and there
it means the container is missing or has not finished starting — a fault, not a property of
the deployment.
The image repository it is pinned to. Fixed by the host environment and not settable over this API.
The operator’s switch, as stored by this deployment. Separate from enabled, which also
depends on the updater being alive: this one says what was chosen, and it is what the
toggle in Settings ▸ Upgrade reflects.
Example
{ "components": [ { "kind": "core", "progress": { "command": "prefetch", "state": "running" }, "reason": "no_site_updater" } ], "offers": [ { "blocked": "below_floor", "direction": "upgrade" } ], "poller_convergence": { "targets": [ { "state": "waiting" } ] }}No valid bearer token
The ADR-019 envelope every failure renders as. pub(crate) and schema-bearing so the OpenAPI
document can name one error shape for every endpoint (ADR-035) instead of leaving 4xx/5xx bodies
undescribed — a client that has to guess the failure shape ends up parsing the success shape and
reading undefined.
object
object
Stable machine-readable code. Clients branch on this, never on the message.
Operator-facing sentence. Safe to display; never carries an internal error’s own text.
Examplegenerated
{ "error": { "code": "example", "message": "example" }}Role lacks ManageSystem
The ADR-019 envelope every failure renders as. pub(crate) and schema-bearing so the OpenAPI
document can name one error shape for every endpoint (ADR-035) instead of leaving 4xx/5xx bodies
undescribed — a client that has to guess the failure shape ends up parsing the success shape and
reading undefined.
object
object
Stable machine-readable code. Clients branch on this, never on the message.
Operator-facing sentence. Safe to display; never carries an internal error’s own text.
Examplegenerated
{ "error": { "code": "example", "message": "example" }}This deployment has no metadata store
The ADR-019 envelope every failure renders as. pub(crate) and schema-bearing so the OpenAPI
document can name one error shape for every endpoint (ADR-035) instead of leaving 4xx/5xx bodies
undescribed — a client that has to guess the failure shape ends up parsing the success shape and
reading undefined.
object
object
Stable machine-readable code. Clients branch on this, never on the message.
Operator-facing sentence. Safe to display; never carries an internal error’s own text.
Examplegenerated
{ "error": { "code": "example", "message": "example" }}