On this page

Owner-local builds

Owner-local source selection and qualification, and canonical Swift sources and editor indexing, for native builds.

Owner-local source selection and qualification

stado product builds owner-local CLI and Desktop installations from the existing canonical checkout on main. It matches the catalogue's repository against Git's GitHub origin, not the directory name. For example, a checkout of wisent-ai/stado named wisent-compute needs no second checkout or alias. WISENT_WORKSPACE selects the workspace parent. Missing or ambiguous origins, symlinks, linked worktrees and non-main build inputs are refused.

stado product status stado --surface cli --json
stado product install stado --surface cli --json

Status includes source_directory and compares the installed revision against that checkout's origin/main without fetching. An unknown source is not ready. Every surface records the actual checkout revision, including local changes as -dirty; Desktop builds create no sibling source export. Automatic updates hold a dirty, divergent or non-main checkout rather than changing its contents.

A local Stado release recipe runs each platform quality command before its build, signing and installation. A refusal names the product, gate and command with both output streams. Failed qualification leaves the installed executable and receipt unchanged. A concurrent change to a signing target also refuses replacement. These owner-local installations do not publish an immutable release or replace the queued release qualification and delivery contract.

stado product supplies the full base Git commit as WISENT_SOURCE_COMMIT. The retained .wisent-output/install/<run>/source.json still names local changes with -dirty, alongside a complete working.patch, without changing the operator's Git index. Stado checks the base commit against its own checkout and keeps the measured dirty state in the executable's revision. An immutable archive does not borrow a parent checkout's Git state; malformed commits and conflicting explicit revisions still refuse the build. Each quality and build step is recorded under ~/.stado/products/output/commands/<run>/: command.json names the program, arguments, directory, source revision, the running Stado's SHA-256, process id, state, exit status and start and finish times, beside the step's complete stdout.log and stderr.log. The first recorded command of each Stado process prunes the directory once: a succeeded command's record goes, and of a failed command's records only the newest of that same program, arguments and directory stays, since its error names it; a record its process still holds is never removed. Stado's Clippy recipes keep --all-targets --keep-going -- -D warnings: independent target failures are collected together and still fail the gate. A failed step names its record as evidence <path> with both output streams; a failed quality gate adds <product> quality <name> failed; build evidence: <run>, and a failed build <product> build failed; evidence: <run>. These records expose what failed; they do not by themselves prove that an installation succeeded.

Each install exports the committed source and builds it under install/<run>/ of the checkout's build area (~/.stado/products/builds/<the checkout's path below the home>), which for a large product is over a gigabyte. Before an install creates its run, it removes all but the newest earlier runs of that checkout. The same retention holds for every other per-run directory a Stado command writes, all of them under the build area or Stado's home and none in the checkout: a local build's local-install/<run>/ and local-install-home/<run>/, a cargo install's cargo-install/<run>/ target directory, stado product cargo's cargo/<invocation>/ and stado product swift's native/<run>/ evidence, and the recorded commands under ~/.stado/products/output/commands/<run>/. A release archive's package and a builder job keep theirs inside the job's tree, which the janitor sweeps with the job. A run whose command is still working holds <run>/.in-use exclusively and is never removed, whatever its age. Every Stado writing under one parent prunes it, so two of them reach the same superseded record, build run or shed tree; a run or tree another Stado removed first, wholly or part way, counts as removed, and any other removal error refuses naming the run or tree (removing the earlier tree <path>: <cause>). The real journey concurrent-shed (cargo test --manifest-path stado-rs/product/Cargo.toml --test concurrent-shed) sheds one run from as many threads as the machine runs at once and requires every one to succeed, then requires a tree the run's permissions keep to be refused by name. An install that fails before its build step passed removes its run's source/ export and build output and its inputs/, keeping the run's logs and receipt; one that fails after the build passed keeps the run whole (below). Before a build run is created, the free space under its parent is compared with what the newest earlier run there holds; with less free, nothing is written and the command refuses with <free> GiB are free under <parent> and the previous run there, <run>, took <size> GiB; a build now would fill the volume. Free space on this host first (stado space reclaim ), then run it again. A parent with no earlier run has no measure yet and is not refused. A quality gate is a function of the committed tree and its toolchain, so a failed gate is recorded under install-failures/ of the checkout's build area (~/.stado/products/builds/<the checkout's path below the home>, never inside the checkout; records an earlier Stado left in .wisent-output/install-failures/ are reclaimed by stado product build-trees remove) for that product, platform, revision and catalog recipe, and installing the same revision again is refused before anything is exported, with <product> <revision> already failed quality <gate> at <time>: <error>; build evidence: <path>. The same revision fails the same gate; commit a repair and install that revision. A failed build step is not recorded: a build can fail for a reason outside the tree, and the next install tries it again. Neither is a gate the host could not run: a gate program that did not start, or a gate script whose shell exited 126 or 127 because it could not find or run a program it calls, says nothing about the revision. Each install step runs with ~/.stado/bin, ~/.local/bin, ~/.cargo/bin, /opt/homebrew/bin and /usr/local/bin ahead of the inherited PATH, so a gate script finds the cargo the step itself would. A record written before Stado told these apart ("kind": "gate-verdict") no longer refuses; current records read "kind": "tree-verdict".

A gate stopped by a product it depends on says nothing about the revision either. Each gate runs with WISENT_DEPENDENCY_REFUSALS naming a file in the install's evidence (dependency-refusals.jsonl), cleared before the gate starts; a product notes there, one JSON line {"product", "refused"} each, every refusal of a product it depends on, such as a gateway that refused or did not answer, or a vault that served no field. A gate that fails with such a note passes when the revision's commit attestation stands, read word for word by Tama (tama attestation check --commit <revision> --repository <checkout> --json, the reading the quality check uses when no model judges a command tree), and standard error says the gate stood on the attestation and names the dependency's refusal. Without a standing attestation the install is refused naming both the dependency's refusal and what the attestation lacks, and no install-failures/ record is written: the tree was never judged.

A build that passed its quality gates and its build step is a function of the committed tree, the catalog recipe and the platform, while signing, staging, placement and activation after it can fail for a reason outside the tree: a vault that cannot be reached, a keychain without the identity, a unit that does not start. So an install that fails after its build keeps the run whole — <run>/.kept beside the evidence, and install-built/<key>.json in the checkout's build area naming the run, the product, the revision and the time it was built, keyed like the quality failures by product, platform, revision and recipe. The next install of the same revision and recipe does not export, check or compile again: it holds that run in use, resumes at the step after the build, and says so — <product>: resumed at signing from the build kept at <run> — with resumed_from in the run's prepared.json and in the installation's receipt and report (prepared.resumed_from, absent when the install built). The kept run is released when an installation of that revision completes: settling the installation sheds the run's directories as for any build, which removes .kept, and the record that then names a shed tree is removed the next time it is read. Until then the kept run is also a cache: it carries CACHEDIR.TAG, so stado space reclaim <host> (its tagged_build_caches stage) and the janitor's build-cache cleaner take it under disk pressure, after which the next install builds from the export again. A kept run is never shed or superseded by a later install's fresh run, and a kept-build record that cannot be read or names no run refuses the install naming the record. STADO_BIN=<built stado> STADO_PRODUCT_TEST_PRODUCT=<product with a stado-release service recipe> STADO_PRODUCT_TEST_SOURCE=<its checkout> node tests/product/resume/kept_build.mjs runs that journey on a Mac in an isolated home under .build/product-resume-kept-build-*: it signs with an empty keychain it creates, so the first install builds and fails at signing and must leave one kept, cache-tagged run and its record, and the second install must say it resumed from that run and export nothing new; the commands, exit statuses and refusals are kept in report.json. On a host that is not a Mac it ends blocked, never passed.

An installation of the Stado that runs it does not sign, stage, place or activate with itself: those steps are code too, and the installed Stado is the old code, so a repair to any of them would never reach the host it repairs. Once the build of the running product passed and is kept (or a kept build was found), the installation lets go of the surface's writer lock and runs the program that build made — the stage entry bin/<the running program's name> in the build output — as <built stado> product finish stado --surface <surface> --source-commit <revision>, carrying --signing-keychain and --signing-identity when they were named. stado product finish finishes a kept build with the binary that runs it: it resumes from the kept run, and a revision with no kept build is refused with <product>: no build of <revision> is retained here, so stado product finish has nothing to finish; stado product update <product> --source-commit <revision> builds it. The receipt and the report of the installation name the program that finished it under finished_by (binary and verb). A finish that fails leaves the build kept, so repeating the update resumes at the step that failed: <built stado> product finish … exited <status>; the build stays retained, so repeating the installation resumes at the step that failed: <its output>.

A Stado installed before this hand-off existed still signs with its own code. To install a repaired Stado over it, run the repair's own build: build the pushed commit in the checkout and let that binary update Stado, so the build, the hand-off and every step after it are the new code:

cd ~/Documents/CodingProjects/Wisent/stado
cargo build --locked --release --manifest-path stado-rs/Cargo.toml --bin stado
stado-rs/target/release/stado product update stado --surface cli \
  --source-commit "$(git rev-parse origin/main)" \
  --signing-keychain ~/Library/Keychains/login.keychain-db \
  --signing-identity '<identity name or SHA-1>'

Canonical Swift sources and editor indexing

stado product swift also builds Swift applications against the existing canonical dependency checkouts:

stado product swift --package-path ../oko-desktop build --build-tests
stado product swift --package-path ../oko-desktop run oko-desktop-bundle --install
stado product swift --package-path ../oko-desktop --json index --jobs 4

Public dependency tags do not select a second source tree for these owner-local commands. Stado resolves each repository's canonical main checkout, creates a multi-root workspace under .build/wisent-native/, and passes that workspace to SwiftPM. Nested bundle builders use WISENT_SWIFT_WORKSPACE as their --multiroot-data-file. Missing, ambiguous, non-main and unsupported inputs are refused before building; Stado never creates or switches a checkout. test requires an explicit --test-product NAME, so it cannot accidentally run every dependency's tests. Swift calls multi-root data unsupported; a toolchain that no longer accepts it fails rather than falling back to dependency clones. Source resolution disables Git transports and SwiftPM Keychain credential lookup. The swift build, test or run that follows passes --skip-update, so it updates no dependency, and leaves Git transports as the caller has them: a test or program that uses Git itself (a fixture repository over file://, a remote over https) runs as it would outside Stado.

Each run retains revisions, working patches, actual commands, exit statuses and output in .wisent-output/native/<run>/. Patches include nonignored untracked source. A private index preserves staged work, and Git writes the patch bytes directly. Compiler scratch and temporary files stay in the ignored build tree. A successful build is not a signing, installation or editor verdict.

index compiles the canonical graph and its test sources without running tests. It writes the actual compiler settings and index store under .build/wisent-native/index/, plus .bsp/wisent-native.json and .sourcekit-lsp/config.json. Editors launch stado product editor serve to read those settings; the provider never resolves dependencies. Unreadable or damaged settings report the failed path, cause and regeneration command. The persistent BSP declaration prevents a missing compiler cache from silently selecting SwiftPM and creating dependency copies.

--editor-workspace DIR puts those declarations in a separate editor workspace. Unrelated SourceKit options survive; another build server or symlinked configuration is refused. Keep the declarations with the workspace. Existing editor sessions keep their loaded build system; index does not restart them.

Source: this website