On this page
Release-channel evidence and publication verification
Release-channel evidence and publication verification
The public release channel is an immutable byte-serving boundary, not a Git tree and not the fleet job store. Every released object has an exact coordinate under:
stado://releases/<product>/<version>/<platform>/
STADO_API_URL, otherwise the deployment's api.url, selects the canonical
HTTP origin. It is an endpoint, not a choice of network provider: direct
HTTPS, a reverse proxy, a tunnel or a provider-managed edge can carry the
same release requests. Repository workflows read the STADO_API_URL repository
variable, defaulting to https://stado.wisent.com; they verify served bytes,
not the state of a particular tunnel program.
stado repair stado --step object-api <target> repairs the target's local object
service. It does not enable, replace or reconcile public ingress. The public
connection belongs to its declared web or gateway configuration and is checked
by the release read-back. The repair runs on the target with the target's own
Stado (~/.stado/bin/stado), so before anything runs it asks that Stado for
every host local api verb the recovery program uses. A target whose Stado
predates one is refused before any step: host_stado_lacks_verb <program> does not declare host local api <verb>: <what that Stado said>, quoted whole in
<target>: object API recovery failed, the host's recovery program ran with its own Stado: exit <N>: <every stderr line, joined with " | ">. Deliver the
current Stado to the target (stado release version converge) and repair
again. Any later failure of the program is quoted the same way, and the
release-store repair reports its failure the same way.
The dashboard serves those objects through GET /api/release/object?uri=.... The route answers for one coordinate at a time; there is no release-listing endpoint. A caller must distinguish three states:
- present — the channel served the object;
- absent — the channel answered authoritatively that the coordinate does not exist;
- unreachable — the channel did not answer, so existence is unknown.
stado storage stat preserves that distinction for release URIs. A network or control-origin failure is infra_down; it must never be treated as absence and must never authorize regeneration or publication.
When the public gateway exhausts its upstream attempts, its HTTP 503 body names
the operation, method, logical URI, selected origin, attempt count and cause.
stado storage stat --json retains that body in an unavailable receipt and
exits 69; a failure before an HTTP response is unreachable.
stado storage get retains the cause in its failure diagnostic, exits 69,
and does not save the error response as the requested object.
What the version gate compares
The version gate does not trust a checked-in released-surface.json. A repository file can describe intended publication, but it cannot prove which bytes the channel serves. The gate's steps are commands of the candidate binary itself, run by .github/workflows/version-check.yml and by every product's own version check: stado release gate surface --binary <stado> (or --help-text <saved help>) prints {"surface": [...]}, the top-level commands the binary advertises; stado release gate baseline --stado <stado> [--output <baseline>] derives the published surface from verified channel bytes and prints stado:<version> for the newest whole release, or bootstrap:<version> on an empty channel; stado release gate decide --current <released> --published-surface <baseline> --candidate-surface <candidate> [--breaking] [--json] is the fleet's port of AutoVersion's rule and names the change class and the next version; stado release gate conformance [--fixtures <FIXTURES.md>] proves that port against AutoVersion's shared fixtures, at the pinned tag when none is named; stado release gate semver reaches <actual> <minimum> succeeds when the declared version is a valid SemVer at least the required one, fails on a lower one and refuses an invalid one with the usage status; stado release gate modules unreachable [--crate <dir>] [--known <list>] [--json] fails when a .rs file under src/ is reachable from no crate root, or when a known entry names a file that is gone — it parses each reachable file as Rust and Cargo.toml as TOML, follows #[path] attributes and inline modules as rustc does, and refuses with the file named when a reachable file or the manifest cannot be read or parsed; and stado release gate app surface|baseline|check are the same gate for an application bundle, read from the sources its release step names (app baseline --stdout prints the baseline, app check is the quality step an application's manifest declares). The repository has no scripts/ directory: the job deliberately needs no file out of the tree it is judging, and a checked-in copy of a gate it does not run is a program nothing executes. The baseline generator derives its baseline from the newest complete Stado release for which both required native platforms are present:
- ask the release channel for the
linux-amd64anddarwin-arm64manifests; - download each archive named by those manifests;
- verify every archive against the manifest SHA-256;
- extract the Stado binary from each verified archive;
- execute the extracted binaries to derive their advertised command surface;
- require both native artifacts to describe the same surface.
The resulting document records the immutable manifest coordinate as its source. The version gate (release gate decide, given the candidate surface) asks the release channel to confirm that coordinate before comparing it with the candidate binary. A stale local binary, a prose claim, a Git tag alone, or a committed generated file is not release evidence.
A submitted source snapshot that carries a released-surface.json baseline also carries a provenance record beside it, written with the epoch as its time so the archive's bytes depend on its records alone: the baseline's marker, every tag origin serves, the commit submitted, and under origin_tag_versions each tag's commit with the version its manifest declares there. A tag whose version could not be read carries version: null and version_unread with the cause — the product manifest at the submitted commit is not a release manifest, does not parse, or the version source it names is not readable at that tag — so a missing version is never mistaken for a tag that declares none.
The workflow sets STADO_API_URL to the declared control origin and obtains its read credential from Skarbiec. It does not override Stado storage variables to point at a runner-local service: doing that would make the candidate and the baseline answer questions about different stores.
What publication proves
Before the first publication write, stado release submit ensures the managed stado-object-api dependency is available. Publication then writes each immutable release object to the canonical channel, reads that same coordinate back through Stado, and byte-compares the returned content with the source. A successful write without a successful read-back is a failed publication.
Publication acquires the selected product's token field through the independent release.publisher_skarbiec binding. Configure its url, consumer, and token_file, or the WC_RELEASE_PUBLISHER_SKARBIEC_URL, _CONSUMER, and _TOKEN_FILE overrides. The grant must already permit that exact field. Stado refuses a control-plane, release verifier or signing identity in this role, and never widens a grant after refusal. See release publication credentials.
stado storage put --json reports an authorization refusal as a JSON failure without creating the object. An invalid publication identity is refused; a rejected Skarbiec field read is auth. The diagnostic names the operation and selected consumer rather than suggesting a retry with broader credentials.
The signed release manifest remains the commit marker and is written last; it is at schema_version 1, and a manifest at any other version is refused. Therefore a complete release means all of the following are true:
- every declared object write succeeded;
- every published coordinate was served back;
- every served byte sequence matched its source;
- the manifest was published only after those checks;
- a later version gate can reconstruct the released command surface from the channel bytes alone.
If the control origin is unreachable, the workflow stops with infra_down. It does not replace the canonical channel with local state, reuse an old baseline, or report the release as absent.
One version, one build
An immutable coordinate is bound to exactly one source revision before any artifact byte is written into it. Release objects cannot be rewritten, so a version published twice from two source revisions could never afterwards mean one build, and the only remedy would be publishing the next version.
stado release coordinate claim <PRODUCT> <VERSION> <PLATFORM> --source-commit <REVISION> [--json] performs that binding. It writes two create-only records:
stado://releases/<product>/<version>/source-revision.json
stado://releases/<product>/<version>/<platform>/source-revision.json
The version-scoped record is the arbitration point every publisher and every platform shares; its create-only write closes the race where two publishers each saw no sibling claim and then claimed different platforms from different commits. Each record carries schema_version (1, the only version written or accepted), product, version, the platform where it is platform-scoped, and source_revision, a full 40-character lowercase Git commit name.
The command reports one of three outcomes: claimed, when this revision now owns the coordinate; confirmed, when the coordinate already attests this same revision, so republishing one build is not an error; and a refusal, when the coordinate attests a different revision.
A refusal is a decision this product made, so it carries error_code: refused, retryable: false and failure_point: cli.release.coordinate.claim, and a --json caller receives it as one document. The exact sentences:
<product>/<version> already attests source revision <held>, and this publisher carries <candidate>. Release objects are immutable: publish a new version<product>/<version>/<platform> already attests source revision <held>, and this publisher carries <candidate>. Release objects are immutable, so one version can never mean two builds: publish a new version instead of writing a second build into this coordinate<product>/<version> already carries platform <platform> from source revision <held>; this publisher carries <candidate>. A version's platforms are one build: publish a new version
Because a claim cannot be taken back, stado release submit --source checks the exported commit on the submitting host before it claims anything: the lockfile must resolve, every pinned input must be stored, and every quality gate of kind test the manifest declares for this host's platform must pass — for Stado concurrent-shed, printed as stado quality check: kompilator test --manifest-path stado-rs/product/Cargo.toml --test concurrent-shed. A refusal prints the gate's own report, for example stado quality check: gate "concurrent-shed" (test) of stado refuses <commit> …, and the version stays free for the corrected commit. No gate that only says whether the source is formatted or lints runs, on the submitting host, on a builder or anywhere else: whether code compiles is learned from the tests that compile it. Build steps and the other platform's gates still run only on the builders after the claim. stado quality check --pre-push and change hand-offs check the committed source alone: its locks, module declarations and reach.
stado release promote refuses a channel move whose platforms disagree, with release platforms were not built from one source revision, and stado doctor reports a version whose version-scoped and platform-scoped claims do not attest one revision.
Source: this website