Submitting and resuming a release
How a product source enters the Stado-native release pipeline: its contract, its version, submission, the ownership chain, resuming a run, and the same operations in Stado Desktop.
Every product carries one strict .wisent-release.json v1 contract. A product
that is not released declares only schema_version, product,
releases:false, and a reason. A releasing product declares its version source,
platform recipes, argv-array quality and build commands, stage mapping,
promotion policy, immutable inputs, and post-publication deliveries. A runtime
contract is required only for products reconciled onto registry hosts.
Repository URLs, branches, hosts, buckets, tokens, signing-key bytes, and
provider credentials are forbidden from that file.
The product version is selected by AutoVersion and passed explicitly. A release is made from a build: either the build of a checkout, made first, or a build that has already passed, named by its id:
stado release submit --source DIR --version V --channel candidate
stado release submit --build BUILD_ID --channel candidate
--build takes the id stado build submit or stado build status prints.
It is refused while that build is waiting or has failed — build <id> is waiting, not passed: a release consumes only a passed build; \stado build
status — and records no run.--sourcerecords the build of the named commit first, then the run;--commitand--versionbelong to--source`, and the two forms are exclusive. The
build and its commands are described in builds.
Stado verifies V against the manifest's checked-in json, regex, or text
version source. By default it requires a clean checkout and captures its full
HEAD commit before reading the manifest, version and archive. It never contacts
a Git remote.
To publish an exact committed source while other work is in progress:
stado release submit --source DIR --commit FULL_GIT_SHA --version V --channel candidate
--commit names a 40-character lowercase Git commit already present in DIR.
It reads Git objects directly: no checkout, clone, worktree, stash or index change
is performed. The manifest, version source and archive all belong to that commit,
even if the current branch, staged edits, working files or untracked files differ.
--version must match that committed source, not the current working file.
Omitting --commit still refuses a dirty checkout with
release source must be a clean committed Git tree.
Malformed commit names are refused with
--commit must be 40 lowercase hexadecimal characters; a tree or blob is refused
with --commit must name a Git commit object. Missing objects and committed files
retain Git's actual read error. These failures occur before any build is queued.
The ownership chain is:
committed tree
-> deterministic source.tar.gz
-> stado://sources/<product>/<sha256>/source.tar.gz
-> build stado://<queue-namespace>/runs/build/<build id>/run.json
-> fleet queue quality/build job per platform, owned by the build
-> status/<job>/output/{receipt.json,release.tar.gz}
-> release run stado://<queue-namespace>/runs/release-pipeline/<id>/run.json
-> signed stado://releases/<product>/<version>/<platform>/
-> registry.release_control desired CAS
-> release_agent reconciliation
-> exact-digest deployment receipt
The source object is create-only and carries metadata for its exact Git commit,
source digest, and pipeline-manifest digest. The build record joins those
identities to the platform jobs, their builders and what their receipts said;
its inputs, manifest copy and per-platform requests are staged under
runs/build/<product>/<build id>/. The durable release run names the build it
consumes (build_id), mirrors that build's platform records, and adds the
published coordinates, delivery jobs, state, and failure. Repeating submit
with the same inputs resumes that run and does not rebuild a platform whose
published output is already recorded; a build's identity carries no channel,
so a stable release of the same commit consumes the same build as its
candidate.
submit ends when the platform builds are queued: it prints the run id with
state waiting and returns. The builds run on the fleet's builders, and the
control host's release agent — the stado release agent on the host that owns
the object store and the signing grant — finishes every run whose builds have
ended: it verifies and signs the outputs, publishes them, queues the
deliveries and records the result on the same run object. A build job has
ended when any of these says so, read in this order by every part of the
pipeline that asks (the agent's pass, publishing, a delivery's verdict, the
build's platform refresh and release status): its terminal queue record;
the receipt its worker wrote with its exit; the outcome the queue's run
reaper retained in the job's run manifest (runs/<run>.json, found through
runs/jobs/<job>) when it retired the record — a cancelled job, or one that
failed before its worker wrote a receipt, has no other witness; or the job's
transition record. A platform whose job ended that way reads failed with
build job <job> ended cancelled (or ended failed) and the job's recorded
error, a required one fails the run, and the agent finishes the run on that
instead of waiting for a job that is over. A job the agent finds in none of
those places and in no queue state is the refusal release job <job> has not reached a terminal state: no queue record, no receipt, no reaped run retains it and no transition record names how it ended; release status reports
such a leg as lost. The agent never blocks on a builder or on a delivery
host: a run whose builds are still running is left for the next tick, and a
run whose deliveries are still queued or running on their hosts stays
delivering until a later pass finds them ended and judges them. submit
therefore returns at once instead of holding the operator's terminal for the
length of every build. stado release status PRODUCT follows a run; stado release resume RUN_ID runs one pass by hand, synchronously, in the terminal
that asks. status lists every recorded run, newest first; --limit N
keeps only the newest N and --limit 0 is refused. --run and --version
read however far back the match is, and a refusal for no match names the
runs it read. A platform whose job failed carries the failure as the run
recorded it, and status prints it whole under the platform line: the
sentence naming the job and how it ended, then the step names and the last
lines the worker wrote, which say which step failed and why (release/fmt.sh: line 21: exec: npx: not found). Nothing is cut to a first line; the same
text is platforms.<platform>.failure in --json. Stado Desktop offers the
same read as Read release runs and what each platform's job did, with
the same fields. The real tests of a reaped platform job and of a failure
printed whole are tests/build_status/, against declared runs of the real
queue.
An unreadable or missing run object whose unfinished index marker remains does
not stop the agent from finishing other runs in that pass. The agent logs the
indexed run ID and the read or parse error (or that the object is absent), then
reports the incomplete pass with the number of failed reads and the IDs it did
finish. The faulty marker is not treated as a successful release or silently
removed; stado release status PRODUCT --run RUN_ID --json remains the
authoritative read of that run's persisted state.
When the checkout has changed, resume the recorded run instead of submitting the new checkout under an already published version:
stado release status PRODUCT --json
stado release resume RUN_ID --json
Use the full run_id from the status JSON. resume reads no local Git tree
or local product manifest. It verifies the stored run identity, manifest
coordinate and manifest digest, then uses the same build, publication and
delivery processing as submit. Queued and running jobs keep their IDs;
published platforms are verified rather than rebuilt.
Failed or cancelled deliveries get an idempotent replacement keyed by their
previous terminal job, so repeating an interrupted retry does not create a
second replacement. A stale delivery summary is checked against the queue.
Malformed IDs are refused with run ID must be 32 lowercase hexadecimal characters.
An unknown ID reports release run <id> does not exist; a changed stored
manifest reports release run manifest digest mismatch. These refusals do
not replace the recorded source. Other failures retain the actual operation's
error in the same run and in command output.
Stado Desktop's Releases screen has Resume selected run. The review
dialog shows the original source revision and exact command before execution;
Release resume result retains stdout and stderr, including a refusal.
The request and read-back use the selected Stado API endpoint through
POST /api/operator/run, with the existing RUN_MUTATION confirmation;
Desktop does not launch a local CLI for resumption.
The native API's ordinary 300-second request bound applies. If that bound
expires, the recorded run and jobs remain resumable with the same command.
Resuming already published platforms needs their verification keys, not a
private signing grant; a platform still requiring publication needs that grant.
Available operations… → Submit a release source on the same Releases screen sends the repository path, optional full commit, version and channel through the selected Stado API. The repository path is on that API host, not implicitly on the desktop computer. Leaving the commit blank requests the clean-HEAD behavior. The form displays the exact command and requires review before submission; its receipt retains the process result, stdout and stderr. The same API request bound applies, and the recorded run can be continued with Resume selected run.
Available operations… → Build a committed source on Releases uses
stado build submit on the selected API host. A required platform without
post-build tests is refused before its source or jobs are written; the receipt
retains the platform, cause and awaiting_tests consequence in stderr. The
release-source operation above applies the same enrollment refusal.
Source: this website