On this page
Committed-source quality checks
stado quality check checks the committed source a source installation would use, not uncommitted changes. It exports that revision into the checkout's separate product build area, resolves committed Cargo locks, stages pinned inputs and runs the declared gates. For a catalogued CLI it then builds the candidate executable from that export and reads its real --help command tree. The Brama judgment refuses one-off product/tool/host/incident commands, a second spelling of an operation and an object without logical counterpart verbs. A missing artifact or unreadable help is a refusal with the failed operation and cause. When no judgment can be obtained — Oko's model call does not run or Brama does not answer it — the tree stands on the checked commit's attestation instead, read word for word by the installed Tama (tama attestation check --commit <revision> --repository <checkout>): the check says <product> CLI command tree was not judged (<cause>); it stands on the attestation <revision> carries, and a commit whose attestation does not stand is refused with the judgment's failure, the claims it lacks and the review's findings. Without an installed Tama the refusal says the attestation cannot be read either. A successful check is not installation or behavioral release qualification.
Because the check reads the committed revision, that revision must hold only what its author wrote. Several sessions work in one checkout, so a file can carry another session's uncommitted hunks that call code only the working tree defines; a commit that takes the whole file stops main compiling for everyone who builds from it. A session commits its own hunks only: tama stage --session <session> --repo <checkout> stages exactly the deltas of that session's recorded edits and refuses an edit that overlaps another session's change, and the commit then takes the index without paths or --all. Tama's commit guard refuses git commit -- <path>, --all, --include and an index holding any change that is not the session's own, before history changes (Tama: staging). No compiler runs at commit; the main check of stado ci run finds a commit that does not build and records it against its authors.
Check a product before handing it off
After installing Stado, point the command at the product's existing checkout:
stado quality check --root /path/to/product
Use this when a change works in a checkout but its committed release source is refused. The product's .wisent-release.json supplies its gates, build recipe and immutable inputs; Stado does not invent a formatter or a command list. The source handoff command stado release changes submit uses the same committed-source check. It builds CLI candidates, so do not run it while the applicable build or test gate forbids it.
A checkout with no .wisent-release.json, a manifest declaring no quality gate for the selected platform, or releases:false with no gate has no gates to run. An explicit check refuses and names the missing declaration or the manifest's reason. A product that publishes no release ("releases": false, with its reason) still declares the gates that check its source, beside those fields, in a top-level quality list, each a name, an argv and a kind, once for every host, since a formatter reads the same source everywhere:
"quality": [
{ "name": "fmt", "argv": ["cargo", "fmt", "--all", "--", "--check"], "kind": "format" }
]
The formatting check at push (stado quality check --pre-push) and stado quality format run its format gates; an installation check refuses with <manifest> declares releases:false, so no installation builds it; its declared quality gates run in the formatting check at push, and a list with no format gate is answered <manifest> declares releases:false and no gate of kind format in its top-level "quality" list. Tama's pre-push reader, on a machine without Stado, runs the same format gates. A caller that checks every checkout it meets, such as the Git pre-push dispatcher Tama installs, asks with --if-declared instead:
stado quality check --root /path/to/product --if-declared
The selected committed revision and the reason are printed with ; nothing to check and exit 0. The declaration is read from the committed export, never the working tree; no Cargo resolution, input staging or gate runs for an undeclared checkout. A manifest that exists but cannot be read or parsed is still refused: that is a failure to read a declared recipe, not absence of a declaration. Stado selects this host's platform when declared and otherwise the first declared platform recipe, so a formatting gate can check source for a different build host. An explicit Desktop Releases check likewise refuses a missing manifest with its path, rather than reporting a successful gate.
A committed tree that holds no .wisent-release.json declares no gate. With --if-declared, alone or with --pre-push, it is answered stado quality check: <revision>: <exported tree>/.wisent-release.json is absent, so it declares no quality gate; nothing to check and exits 0; a check without --if-declared refuses with <exported tree>/.wisent-release.json is absent, so it declares no quality gate, the path being the committed export the check read. A path there that is not a readable file (a directory, a file without permission) is refused with cannot read <path>: <cause> either way. The real journey cargo test --manifest-path stado-rs/Cargo.toml --test quality-absent-manifest commits a manifest-less repository and checks the three answers, then commits a directory where the manifest belongs and checks it is refused.
The check runs in a fixed order, and each refusal names its own step: the lock resolution first, then every pinned input confirmed stored (inputs.<name> of the manifest, asked of the store by its pin) and the inputs a selected gate declares under its own inputs staged exactly as the release worker stages them (fetched from the store, hashed against the pin, extracted or mounted beside the exported tree), and only then the gates the installation runs, each kind, with the same WISENT_INPUT_<NAME>_DIR variables. An input whose object the store does not hold is refused as absent, naming the pin; it is not a stale lock and not a formatting difference, and no later gate ran. An input no selected gate declares stays in the store, reported as release input <name> is stored at <uri>; no selected gate declares reading it, so it stays in the store. Storing bytes under that pin satisfies this step only; whether the recipe consumes the input is a property of the recipe, read in the release contract and the input publication command's own receipt.
A gate also receives the secret environment its platform recipe declares under secret_env, NAME to role#field, as the release worker would set it: the check reads each value from the operator's credential store by role, the read stado credentials get --role makes, so a web install whose lockfile pins private dependencies finds its GITHUB_TOKEN at push as it does in a release build. A reference that is not role#field, a role no item plays, or an item without a non-empty string field of that name is refused as secret_env <NAME> (<reference>) cannot be read for the quality gates, with the cause, before any gate runs.
The progress report names the operation before it can wait: reclaiming the run
area, exporting the committed source, reading its declared gates, then for
each Cargo workspace running cargo metadata --no-deps --offline to read its
path dependencies, reading the committed lock, running cargo fetch and
cargo metadata --locked --offline, then checking source modules, confirming
each pinned object in the store, and staging only inputs selected gates read.
If a check stops without a verdict, the last progress line
identifies the operation still in flight, not a passed gate. In particular,
confirming release input <name> ... is stored at <uri> means the store has
been asked for that object; it does not mean the object was found. The
subsequent stored-input line or an explicit refusal establishes that outcome.
With --json, progress remains on stderr and the result document stays on
stdout.
Checking source modules covers Rust and Swift. Each mod name; a Rust crate
reaches must name a file the tree holds. In a Swift package, a target is each
directory under Sources/ beside Package.swift, and it must not hold two
Swift sources of the same file name, compared case-insensitively. SwiftPM
names object files after their sources, so two Models.swift in different
folders of one target are two producers of one object. A commit like that is
refused before any gate runs:
<revision> of <checkout> holds Swift sources of one file name in one Swift package target, so SwiftPM has two producers of one object file and the target does not build: target <dir> compiles models.swift from <a> and <b>.
Rename one of the sources after its own subject, for example
FleetExpansionModels.swift.
For a CLI product, the check builds the release recipe into its disposable run area, inspects executable stage members, recursively invokes their --help, and judges the observed tree against the general-object rules. A refusal names the command, the primitive it duplicates or fragments, and the model's reason: command `<command>` duplicates or fragments primitive `<primitive>`: <reason>. If the candidate cannot be built, an advertised child repeats its parent's help, or Brama does not answer, the check refuses instead of treating an incomplete tree as clean. The Desktop Releases gate action shows the same report and refusal.
For Clap executables, the advertised Commands: section supplies direct children structurally. A Clap leaf has no such section; its Usage: line and generated -h, --help flag establish that it has no children, so Stado does not ask the model to invent any. Other help grammars use Brama to extract direct commands and reject malformed, repeated or unadvertised children. The complete observed tree still requires Brama's semantic judgment: recognizing the help grammar is not a clean-command verdict.
Pin the source of a push
For a known commit, including one other than HEAD, select it explicitly:
stado quality check --root /path/to/product --revision COMMIT
Stado resolves the supplied revision to a commit before exporting it and names that full identity in the check and any refusal. It does not fetch or select origin/main in this mode. Uncommitted source, formatter configuration and release declarations are not inputs. Without --revision or --pre-push, the existing installation selection applies: a tracked dirty checkout selects HEAD; otherwise Stado fetches origin/main and selects it when it descends from HEAD.
A Git pre-push dispatcher forwards Git's original stdin to:
stado quality check --root /path/to/product --pre-push --if-declared
Each Git update supplies the local ref, local object identity, remote ref and remote object identity. Stado checks the local commit being pushed, not the current branch or remote tip. Several refs pointing to the same commit share one check. Deleted refs have no source tree to check and are reported as deletions. An update of a remote ref under refs/notes/ carries Git notes about commits, not a source tree; it is reported as stado quality check: <remote-ref> holds notes, not a source tree; nothing to check, and the commits those notes annotate are checked where their own branch is pushed. Empty input means no updates. Malformed update records, non-hexadecimal object identities and local objects that cannot resolve to a commit are refused before any gate runs. --pre-push and --revision are mutually exclusive.
The dispatcher must preserve and forward the same stdin to every consumer; reading it once and invoking Stado with exhausted stdin does not check a push. It must not decide whether to invoke Stado by reading the working-tree manifest, and a formatter preceding Stado must also inspect the pushed export, not reject unrelated working-tree changes. These are dispatcher integration requirements, not a reason to disable Git hooks or modify another writer's files.
In Stado Desktop, Releases → Check committed gates and the built CLI command tree takes the checkout and revision on the selected host. It uses the native operator API and retains the same command output, source identity and refusal as the CLI. The host's normal mutation confirmation applies because declared gates and the candidate build execute programs.
The real regression in tests/quality/push/ creates an isolated product checkout and bare remote, installs its tracked test hook in that fixture only, and performs real Git pushes through the real Stado executable and a real rustfmt gate. A clean non-HEAD commit must push despite a bad HEAD and dirty release declaration; an unformatted committed revision must be refused even when its working-tree source is fixed. Deleting a published branch must still succeed with that bad HEAD, and so must pushing a refs/notes/attestation note on the clean commit, which is reported as notes and lands on the remote; selecting an existing Git blob as --revision must refuse rather than select a different tree. The remote refs and both dirty files are inspected afterward. It is part of quality-gate-kind:
cargo test --locked --manifest-path stado-rs/Cargo.toml --test quality-gate-kind pushed_revisions::
The journey requires Git, rustfmt, the account's configured Git identity and the built Stado test executable. It retains the source revision and diff, binary digest, exact commands, exit statuses and both output streams under .build/quality-push/. This fixture proves Stado's push contract; qualification of an installed dispatcher additionally requires its normal push journey with the same preserved update stream.
The real command-tree journey in tests/quality/command_tree/ runs through the registered quality-gate-kind suite. It checks the current Stado commit's built CLI and a historical committed tree containing the duplicate janitor verb, retaining revisions, invocations, statuses and both streams under .build/quality-command-tree/. The historical refusal must name disk-cleanup and space reclaim; the current candidate must pass. It needs a working Brama judgment through Oko and the real release build toolchain; an unavailable dependency is a failed journey, not a simulated pass. Run cargo test --locked --manifest-path stado-rs/Cargo.toml --test quality-gate-kind command_tree::.
Its unjudged journey leaves Oko off PATH, so no model judgment can be obtained, and makes two commits of HEAD's tree that no ref reaches: one whose message carries every claim and required trailer of the installed Tama's commit standard, one that carries none. The first must pass saying its tree was not judged and stands on its attestation; the second must be refused naming the judgment's failure and that its attestation does not stand. It needs an installed Tama at ~/.stado/bin/tama and the release build toolchain, and retains its evidence beside the other journey's.
List the one-off commands of a CLI
The check refuses a revision whose CLI holds a one-off command. To read what is left to fold into a general primitive without refusing anything, list it:
stado quality command-tree [--root <checkout>] [--revision <commit>] [--json]
It exports the committed revision (by default the one an installation of the checkout builds), checks its locks and module declarations, stages its pinned inputs, builds the CLI its release recipe builds and reads the whole command tree from the built executable's help, as the check does, and has a model judge the tree through Oko and Brama. No quality gate runs, and a product declaring none still has its CLI read. It exits 0 whenever the tree was judged, whether or not any command is left:
stado at <commit>: <n> command(s) to fold into a general primitive
command `<command>` duplicates or fragments primitive `<primitive>`: <reason>
With --json the progress lines go to stderr and stdout carries
{"product", "revision", "cli", "remaining"}, remaining holding one such
sentence per command, so /remaining is an inventory a completion probe
can read. A product whose catalog record declares no CLI surface answers
"cli": false and an empty list (<product> declares no CLI surface, so it has no command tree to inventory); a releases:false product is refused,
since no installation builds it. A tree no model judged is refused, non-zero:
the CLI command tree of <product> at <commit> was not judged (<cause>), so no inventory of its one-off commands can be given. The check stands such a
tree on its attestation; an inventory nobody judged lists nothing. A
revision that names no commit, a locked dependency or module the commit
does not hold, and a CLI that does not build are refused as the check
refuses them. With --json every refusal is the error document on stdout.
In Stado Desktop, Releases → Available operations… → List the one-off
commands of a revision's built CLI runs the same command with --json
for a repository and an optional revision on the selected host.
The real journey cargo test --locked --manifest-path stado-rs/Cargo.toml --test quality-command-tree-inventory lists Stado's own HEAD (exit 0,
product stado, cli true, every remaining entry naming a command and
its primitive), refuses the same revision with Oko off PATH naming the
missing judgment, and refuses HEAD's tree object as no commit. It retains
invocations, statuses and both streams under
.build/quality-command-tree-inventory/ and needs the release build
toolchain and a working Brama judgment.
Which gates a push runs and which an installation runs
Every gate in platforms.<platform>.quality declares its kind, and the kind decides which gates a step runs:
"quality": [
{ "name": "fmt", "kind": "format", "argv": ["cargo", "fmt", "--all", "--", "--check"] },
{ "name": "clippy", "kind": "lint", "argv": ["cargo", "clippy", "--locked", "--all-targets", "--", "-D", "warnings"] }
]
kind |
What the gate checks | stado quality check --revision and without --pre-push, stado release changes submit, stado release submit --source, the release build and stado product install/update |
stado quality check --pre-push (the pre-push check) |
stado quality format |
|---|---|---|---|---|
format |
whether the committed bytes pass the declared gate (cargo fmt --check, Biome, or a web product's stado web quality, which installs locked dependencies and runs declared typecheck and lint scripts) |
runs | runs | writes with a declared formatter; refuses to write web source |
lint |
a static check that compiles or analyses the source (cargo clippy, a type check, a compile for the simulator) |
runs | does not run | does not run |
test |
a test the source carries, run before the build | runs | does not run | does not run |
A push checks committed lock resolution, module declarations and gates declared as format; it does not run separately declared lint or test gates or build a candidate CLI. A web product may declare stado web quality as its format gate, and that gate runs the product's package scripts, including a typecheck or lint script when declared. The kind selects the gate; it does not turn a TypeScript diagnostic into a formatting defect. Full qualification runs the remaining gates before installation. A kind other than these three, or a gate without one, is refused when the manifest is read, naming the field.
Full qualification of the staged Stado CLI also reads the native installed
unit files for its platform and parses each Stado unit's argv with that
candidate (stado quality units --candidate <binary> --program stado --platform <platform>). A rejected unit refuses publication with its host,
path, argv, candidate, parser exit and stderr. An unreadable host is a refusal,
not an empty set of compatible units. stado quality parse -- <argv> exposes
the parser check without executing the requested command.
A gate that reads a release input names it under inputs, by the manifest's own key: { "name": "web-quality", "kind": "format", "argv": ["stado", "web", "quality", "--git-input", "wisent-errors-bundle=wisent-ai/wisent-errors.git"], "inputs": ["wisent-errors-bundle"] }. The pre-push check stages exactly the inputs the selected gates name and leaves every other pinned input in its store after confirming it is there, because a gate's script reads WISENT_INPUT_<NAME>_DIR where no reader of the manifest can see it and staging every pin fetched a desktop's whole SwiftPM resolution on every push for a formatter that reads the source alone. The release build stages every input whatever the gates declare. A gate naming an input the manifest does not pin, or naming one twice, is refused when the manifest is read: <platform>: quality gate "<gate>" reads release input "<name>", which the manifest does not pin under inputs. The real journey tests/quality/release_inputs.rs runs a gate without inputs over a stored pin and requires the input to stay in the store, a gate with inputs and requires it to read the staged bytes, and a gate naming an unpinned input and requires the refusal by name.
A gate's refusal names the gate and its kind: stado quality check: gate "<name>" (<kind>) of <product> refuses <revision> of <checkout>: <the gate's own report>. A non-web format gate ends with `stado quality format` writes what it reads. A web format gate ends with web gate: inspect the failed command and output; `stado quality format` does not write web source: inspect the command and diagnostics above it, whether an npm ci refusal or npm run typecheck naming a TypeScript file, and repair the cause with the product's own tools. A lint or test gate ends with a <kind> gate's refusal is repaired in the source it names; `stado quality format` changes nothing for it. A typecheck or clippy finding is never answered with a reformat. The real journeys tests/quality/web_typecheck.rs and tests/quality/gate_kind.rs exercise those refusals, a repaired committed source and a passing gate.
The web journey's isolated checkout locks a real TypeScript compiler and runs tsc --noEmit against an incompatible type before committing the corrected source. It checks the separate missing-script, failed-install and failed-typecheck refusals rather than treating a JavaScript syntax check as type qualification.
cargo test --locked --manifest-path stado-rs/Cargo.toml --test quality-web-typecheck
The gate-kind journey commits Rust sources whose formatter passes but compiler fails on a missing name, whose compiler succeeds but executed test assertion fails, and whose compiler and test both pass. It runs these through separately declared lint and test gates and retains actual commands, exit statuses and output; it never substitutes a canned nonzero command for compilation or a test failure.
It requires the real rustup toolchain with rustc and rustfmt available.
A missing compiler or formatter fails the journey at that prerequisite;
it is not a successful gate verdict.
Both journeys retain the source HEAD and any uncommitted source changes alongside recorded Stado, Git and dependency-command exit statuses and output. The report path is printed at fixture start, including on a failed prerequisite. A dirty checkout is recorded as such, not labelled an exact clean source revision.
cargo test --locked --manifest-path stado-rs/Cargo.toml --test quality-gate-kind
The check exports committed source into Stado's managed run area and removes its own export when finished. On a later check it reclaims an abandoned area only when its owning process is gone. An unreadable run directory, a name without a valid process ID and full hexadecimal commit revision, or a failed process inspection is reported with its path and cause on stderr and left intact; this diagnostic does not turn a quality-gate refusal into a formatting problem. The isolated tests/quality/link_input journey checks the preserved unrecognized area and repeated cleanup.
In Stado Desktop, Releases → Available operations… → Check committed gates and the built CLI command tree takes a repository path and exact revision on the selected Stado API host. Its Operation failed panel shows the CLI result message; Complete operation receipt retains stderr, including the failed script output and the web-specific refusal. The screen does not reinterpret the failure as a formatter request.
A gate that compiles with Cargo (every kind but format, and the build of the candidate CLI) runs with --message-format=json added before its --, and its diagnostics are read from the records Cargo prints, not from the words of its report. Each compiler diagnostic with a primary span is one finding: its location <file>:<line>:<column>, the lint or error code the record carries (clippy::needless_return, E0425) and its level and message. The rendered text of every diagnostic is printed where the check's lines go, as the compiler wrote it. The refusal then reads <argv> exited <status>; <n> finding(s): followed by one line per finding, <file>:<line>:<column> [<lint>] <message>. A gate that names no finding — a format gate, a gate that is not Cargo, a failure that is no compiler diagnostic — carries its whole report instead. A gate that sets --message-format itself is refused, naming the flag, since the records the check reads are the ones it asks for, and a line that begins as a Cargo record but does not read as one is refused quoting it. With --json the progress lines and the gate's report go to stderr and stdout carries one document: {"status": "passed", "revisions": [...]}, or the failure envelope whose details hold argv, exit, report and findings (location, lint, message), so a caller acts on the finding without parsing the sentence:
stado quality check --root /path/to/product --revision COMMIT --json
The main check of stado ci run records that same refusal on the red commit and in the Oko defect it files for the commit's authors. To read one lint on the revision without running every gate, ask stado product cargo for the operation the gate runs: besides its own operations (build, check, test, run, metadata, stage) it runs every Cargo subcommand a compiling quality gate of the built tree's .wisent-release.json declares, read from the exported revision under --revision. Every operation but metadata runs with --message-format=json; the record's findings are read from Cargo's records, their rendered text goes to stderr, cargo.stdout in the evidence keeps the records as Cargo printed them, and the operation's stdout is what remains, the output of what Cargo ran:
stado product cargo --manifest-path /path/to/product/Cargo.toml --revision COMMIT --json clippy -- --all-targets -- -D warnings
A format gate's operation (cargo fmt) compiles nothing and is refused there, naming stado quality check --revision as what reads it; any other operation is refused naming the operations the command runs and those the tree's gates declare. Desktop Releases' "Check committed gates" operation reads the JSON envelope, and the Products "cargo" operation takes the gate's operation in its operation field. The real journey tests/quality/findings/lint.mjs commits a clippy lint in an isolated canonical checkout and reads its file:line and lint through both commands.
A manifest gains the field without breaking an older Stado: an installed Stado that predates kind ignores it. Push the manifests first, then install the Stado that reads kind; the other order makes the new Stado refuse every manifest that has not gained the field yet.
Interpret Cargo failures
The lock is asked about in two steps whose failures mean different things, and each refusal names the command, exit status, exported workspace, original checkout manifest and source revision, followed by Cargo's actual stderr. cargo fetch runs first: a failure there is a source that could not be fetched, reported as cargo fetch --manifest-path Cargo.toml failed with <status> in <exported> for <manifest> at <revision>, so whether its Cargo.lock resolves was not asked: <Cargo's cause>; it is not a verdict on the lock. The committed Cargo.lock is put back byte for byte after the fetch, because a fetch can write entries the committed lock lacks. cargo metadata --locked --offline then asks that committed lock. Its nonzero result is reported as cargo metadata --locked --offline --format-version 1 --manifest-path Cargo.toml failed with <status> in <exported> for <manifest> at <revision>: <Cargo's cause>; Cargo's cause, not the wrapper, says whether the lock needs updating or a different dependency operation failed. The exported path and checkout path serve different purposes: the former is what Cargo consumed; the latter identifies the source to repair. Capture the full refusal, not only its first line.
When a sibling checkout also holds a pinned Git commit absent from its local remote-tracking branches, the failed-fetch refusal names that observation and suggests publishing the commit if intended. It still carries Cargo's fetch cause; the local refs alone do not prove what the remote currently holds.
Before the lock is asked about at all, the check reads each committed workspace's own manifests (cargo metadata --no-deps) for a path dependency that leaves the exported tree. The tree is one repository's commit exported alone, on this check and on a release worker, so a sibling checkout reached with path = "../<repo>/…" is not in it, and Cargo's own answer to that is a missing-manifest error. The check refuses the dependency by name instead: stado quality check: <checkout>/Cargo.toml (package <name>) declares dependency <dep> at path <path>, which is outside the committed tree of <revision>: a source install and a release build carry only this repository's commit, so that path is absent there and no Cargo.lock change creates it. Declare it as a git dependency on its canonical origin - stado product cargo resolves it to the canonical checkout here and to the release's declared private source input on a worker - or pin the sibling as a release input in .wisent-release.json (inputs.<name>) and reach it there. Further dependencies outside the tree are listed after the first. A path dependency inside the tree is not refused by this reading; the lock resolution below then runs. The real test tests/quality/path_dependency.rs drives both.
Next, still compiling nothing, the check reads every Rust crate of the exported tree (a directory with a Cargo.toml beside a src/) from its roots (src/lib.rs, src/main.rs and each [[bin]] path) through its mod declarations, and refuses a mod name; that names no file the commit holds — neither <name>.rs nor <name>/mod.rs where the declaration resolves, nor the file a #[path = "…"] attribute on the line before gives it: <revision> of <checkout> declares module files it does not hold, so no build of it compiles: <file> declares mod , and the tree holds none of <name>.rs or <name>/mod.rs; …, every such declaration named. A commit that deleted a module file another still declares, or declared one it never added, is refused on the push that carries it and in the delivery queue's gates step — which runs this check, with the lock check, for a product that declares no format gate — instead of by the first installation that builds on it.
A declaration inside an inline module resolves where rustc resolves it, one directory deeper under the inline module's name: mod outer { mod inner; } in a/mod.rs names a/outer/inner.rs or a/outer/inner/mod.rs, and is refused only when neither is committed. Braces inside comments, string and character literals do not end the inline module.
The same reading follows every crate::a::b path written in a module the crate reaches, segment by segment through the declared modules. A segment that names a module file the commit holds — <segment>.rs or <segment>/mod.rs where the walk stands — which no mod declaration reaches is refused: that file is not compiled, so the path resolves to nothing. <revision> of <checkout> uses modules no declaration reaches, so no build of it compiles: <file> uses crate::, and the tree holds <module file>, which no mod reaches; …, every such path named. A commit that added a module's file and the code calling it but not its mod line is refused here. Without name resolution the check leaves to the build a segment that names an item, or a module the commit holds no file for.
On case-insensitive filesystems, an item path can open a differently cased module file by filesystem alias. The check compares the committed directory-entry spelling before treating that file as a module; item names remain for Rust name resolution rather than an undeclared-module refusal.
An installation that reaches its build without this check, and whose build step fails, reads the committed tree it built the same way: each workspace's declared dependencies (cargo metadata --no-deps) are compared with the packages its committed Cargo.lock names. A dependency the lock lacks turns the build's failure into a refusal of the committed source, never could not attribute the failure: <product> at <revision>: <manifest> declares <dependencies> that its committed <lock> does not lock, so cargo build --locked refuses this revision; commit <lock> updated for <manifest> (cargo update -w --manifest-path <manifest>) in <product> and install again. The build said: <the build's own failure>, exit class refused. When the locks cover their manifests the build's own failure stands; when a lock cannot be read, the failure stands with why.
| What the check reports | What the result establishes |
|---|---|
cargo fetch ... failed, with a registry, Git, authentication or transport cause |
The named dependency source could not be fetched with the credentials this host holds; whether the lock resolves was not asked. Preserve the provider cause; a lockfile rewrite does not repair access. |
cargo metadata --locked --offline ... failed |
Cargo could not complete that operation against the committed lock and fetched sources. A lock update is justified only when Cargo's own cause says the lock needs updating; preserve other causes as reported. |
| A dependency manifest cannot be read | The source graph available to that operation is incomplete or inaccessible. It does not establish that the lockfile is stale. |
cannot run cargo ... |
No resolution verdict exists. The launch failure is distinct from Cargo returning a nonzero status. |
cannot read <exported>/Cargo.lock or cannot put the committed Cargo.lock back after cargo fetch |
The exported workspace could not be read or restored; the check stopped before or between the two Cargo steps and names the file and the operating system's cause. |
Do not infer a stale lock from a nonzero metadata status alone or substitute cargo tree for the failed operation's stderr. In particular, copying a dependency into an exported directory once does not repair the product's source handoff: admission and release workers must consume the same declared immutable source graph.
A clearer refusal does not make an incomplete graph buildable. Commit and push the repaired source, rerun the quality check for that revision, and inspect the retained release handoff before treating the repair as delivered. Read builds and releases and the release contract for publication, inputs and final-state evidence.
Build success is not behavioral qualification
stado release changes list --id CHANGE_ID --json distinguishes a passing build from passing task qualification. Every required platform must declare post-build tests and retain exactly one passing receipt for each declared test, with its actual command and successful exit status. A platform with no declared tests leaves the handoff awaiting_tests, with no complete passing post-build test evidence; it does not turn an otherwise passing build into a failed build.
Receipts name qualification_policy: required-behavioral-tests-v1. The policy-specific observation is cached separately from historical build-only observations, so an old passed cache cannot supply the new verdict. An unreadable or malformed current observation is an error, not fabricated empty evidence. Oko refreshes receipts that predate this policy and keeps a task open while behavioral qualification is pending or its last read failed. CLI and native task reports consume that same task status; publication alone proves neither a particular defect's replay nor its graphical behavior.
The real read-only regression is tests/release/behavioral-qualification.mjs in the Stado repository:
node tests/release/behavioral-qualification.mjs
Supply STADO_BIN, STADO_TEST_SOURCE_REVISION, STADO_TEST_BUILD_ONLY_CHANGE and STADO_TEST_BEHAVIORAL_CHANGE. The two change IDs must name distinct real isolated fleet handoffs: one passing build without post-build tests and one with successful declared tests. The journey does not create a fake receipt, queue a build or alter its fixtures. It checks actual commands, statuses, receipt identities and the refusal reason, retaining command streams, source revision, binary and journey digests under .build/behavioral-qualification/. Missing fleet access or fixture evidence fails the journey; it is not a pass.
Source: this website