On this page

Product catalog

Stado owns the canonical Wisent product catalog, catalog/products.yml at the root of wisent-ai/stado, and every operation on a product's installed surfaces: stado product. One record is one product; its CLI, desktop, service, web and mobile surfaces belong to it, and a separate repository does not make a separate product.

Stado carries the catalog and the program itself; the code is the stado-product crate in stado-rs/product/. Nothing has to be downloaded before Stado can install or sign a product.

Commands

Every former wisent-products <args> is stado product <args> with the same arguments, JSON and exit status. The global --catalog PATH selects another authority file: stado product --catalog PATH <command>. The global --workspace DIR selects another workspace of canonical checkouts: stado product --workspace DIR <command>; one process works in one workspace, and a second different one is refused with this process already works in <dir>; <dir> cannot also be its workspace.

stado product catalog --json
stado product status jeden --surface cli --json
stado product install weles --surface cli --json
stado product signing inspect ~/.stado/bin/jeden --json
stado product cargo --manifest-path ../stado/stado-rs/Cargo.toml --json metadata
stado product swift --package-path ../oko-desktop --json index --jobs 4
stado product documentation cli-pages --origin https://stado.wisent.com
  • catalog reads the authority, derives the CLI or service lists (--cli-products, --output PATH, --check PATH), compares it with the copy compiled into this Stado (--check-package), and reports records without approval provenance (--unapproved) or organization repositories no record claims (--unclaimed).
  • registry add, registry set and registry rm change one record under a writer lock. Invalid identities, dangling integrations, a unit one product runs while another retires it, and invalid installation declarations are refused before the file is written, and the neighbouring records and comments are kept byte for byte.
  • create provisions private repositories through Wisent Integrations and keeps each request for --status and --resume.
  • install, update, status, rollback and remove act on one product surface (--surface cli|desktop|service, --host for a service). --release-version with --source-commit installs an exact published release of a stado-release recipe on the cli or service surface: the archive that version's signed manifest names for this host's platform is fetched and verified, and a different source, digest or signing identity is refused, not rebuilt. A service installed this way runs on the named host like any other service operation and its unit is ensured as for a source build, so a host can take a product that is not under release control — the vault host's own Skarbiec — without building or signing anything. A CLI given --host with a release coordinate is refused: exact release coordinates install a CLI on the machine that runs this command; --host names a service host; any other recipe or surface is refused with exact release coordinates require a stado-release recipe on the cli or service surface. --source-commit alone installs a source build of that exact canonical commit: the checkout exports that commit's tree, never its working tree, once origin/main or the canonical checkout's own main carries it. A commit on main that is not pushed yet installs — a repair to the push gate can only be pushed once it is installed — and the installation says <commit> is on <checkout>'s main but origin/main does not carry it yet; installing it as canonical source ahead of origin, the plan's install_decision carries source_on_origin: false and the build's prepared.json the same; a commit on neither is refused as neither origin/main nor <checkout>'s main carries <commit>; only canonical source is installed. The real journey tests/install/source_commit_ahead.mjs (STADO_BIN, STADO_PRODUCT_TEST_PRODUCT, STADO_PRODUCT_TEST_SOURCE) installs the head of the checkout's main into an isolated home and has a commit on no branch refused with the receipt unchanged. Without it a source build is of the checkout's head, advanced to origin/main.
  • A newer install or update of a surface supersedes an older one still preparing (building, nothing placed): the older process is told to stop and the newer one takes the surface, printed as superseding the installation held by pid <pid> …: it has placed nothing yet. One already placing files is waited for, printed as waiting for <lock>: … (it is placing files), and so is one asking for the same installation (the same arguments after the program path, --json aside), printed as waiting for <lock>: … (the same installation, already under way), because stopping it would only restart the same work. --wait keeps the older one whatever it is doing and runs after it. A service operation (install, update, status, rollback, remove with --surface service) whose --host is another registry host runs on that host: Stado sends the same arguments, with --host in its canonical name, to the host's own installed Stado over its host channel and prints that Stado's output. The source build, the placed files, the restarted unit and the receipt are all on the named host; the machine you typed on builds nothing. The host needs the toolchain its recipe builds with. --catalog is refused for another host, because it names a file on this machine: --catalog names a file on this machine, and a service install for <host> runs on <host> with that host's own catalog. A failure on the host is reported as stado product … on <host> failed (exit <code>): <its output>.
  • install and update build from the canonical checkout in the workspace (--workspace DIR, else $WISENT_WORKSPACE, default ~/Documents/CodingProjects/Wisent). When none holds the product's repository, or a repository a Cargo or Swift git dependency resolves to, they clone its GitHub origin onto main at <workspace>/<name> and print <owner>/<name>: no canonical checkout was in <workspace>; cloned https://github.com/<owner>/<name>.git into <path>. A directory already at that path that is not the checkout is left alone and refused (… <path> exists and does not identify <owner>/<name>), and a failed clone is reported with git's own error (… cloning <origin> into <path> failed (<status>): <stderr>). status never creates a checkout; sync creates one the same way only with --clone-missing.
  • A product's catalogue record lists the products it depends on, and install installs those first. --without brama,skarbiec installs on a machine that does without them: a dependency on a named product is skipped when the catalogue declares its alternative, printed as <product>: installed without <dependency>/<surface>: <alternative> and recorded in the installation's dependencies with "without": true. A dependency with no declared alternative is refused with <product>/<surface> cannot be installed without <dependency>: the catalogue declares no alternative for it, or, when the catalogue declares bound_because, …: the catalogue binds it by design: <reason>, and the product is not installed. The choice is kept, so update and sync do not install a skipped dependency; a later --without replaces it. Every product is meant to run without every other one: integration with another product is the default when it is installed, and its declared alternative is the product's own way of running alone. Which dependencies each product declares, and which of the products its code reaches still have no alternative, is read with stado product catalog --json and stado product dependencies below; this page does not keep a copy of that list.
  • A dependency whose own installation fails stops only itself when the catalogue declares the dependent's alternative for it: the dependent is installed, standard error says <product>: installed without <dependency>/<surface>, whose installation failed: <cause>; <alternative>, and the installation's dependencies record it with "without": true and "failed". A failed dependency with no declared alternative stops the dependent with the dependency's own refusal. Two products that need each other, such as one whose gate reads the other's newer verb, install one after the other this way.
  • A service installation's dependency on another product's service surface is installed on the host the installation names with --host, beside its dependent (a service installation puts its files on the machine running it, so that host is this one): stado product install brama --surface service --host <this host> first installs or reuses Skarbiec's service there. A dependency on any other surface takes no host. A service installation without --host checks no service dependency and records it as "checked": false with reason: "no --host was given".
  • dependencies [PRODUCT] [--json] reads, from the canonical checkout of every repository a product's record names (owner_repository and each surface), every line of tracked product code that reaches another catalogued product, and prints, per product, the repositories it read and each product reached with its standing and how many lines reach it through each channel: program (a spawn call naming one of the other product's declared binaries, or its id when none is declared; a quoted path ending in it, unless the path lies under tests/, a manifest's test target; a shell line starting with it), resolver (forwards/<product>… or <product>.local), http (<product>.wisent.com/…, a /docs link excepted), files (.<product>/), environment (<PRODUCT>_…). What the product never runs is not product code: comment lines, Markdown, tests/, a git submodule (another repository's commit, which git tracks as a directory), the forge's automation (any .github/ directory), git's metadata (.gitignore, .gitattributes, .gitmodules), an example to copy (*.example, such as .env.example; the variable it shows is read by code that is scanned on its own), package lockfiles (Cargo.lock, package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, bun.lock, Package.resolved, Gemfile.lock, poetry.lock, uv.lock), the release tooling in a top-level release/ directory (any case) and the release manifest each catalogue installation of that repository declares: how the product is built, signed and published is its installation recipe's, not what the product runs. --json adds every line as {repository, file, line, channel}. A product reached is independent only when every catalogue dependency on it declares an alternative and an alternative_source (<repository>/<path>[:<lines>], the code that implements the alternative) that is a file in the workspace; otherwise it is undeclared, bound (a dependency with no alternative; with because when the catalogue declares bound_because, the reason the product does not run without the other by design — a surface of it and not a separate product, or work that is the other's by definition), unproven (an alternative with no alternative_source) or source-missing. Each is refused on stderr as <product> cannot be shown to run without <other>: <reason>; first reached at <repository>/<file>:<line> — a bound need with a reason as the catalog declares that <product> needs the <other> <surface> by design: <because>, one without as … with no alternative and no bound_because saying why — an absent checkout as <product>: checkout <path> of <repository> is absent, so what it calls is unknown, and the exit status is a failure when any examined product is not independent. The catalogue refuses an alternative_source without an alternative, an empty alternative, alternative_source or bound_because, a bound_because beside an alternative (a need is bound by design or has a way around it, not both), a field outside product, surface, for_surface, alternative, alternative_source, bound_because, and a dependency naming an unknown product.
  • A declared need is written with stado product registry set PRODUCT --add-dependency '{"product":"brama","surface":"service","for_surface":"cli","alternative":"<how PRODUCT runs without it>","alternative_source":"<checkout>/<path>"}', which adds it or replaces the need with the same product, surface and for_surface, and --remove-dependency OTHER removes every declared need of OTHER. A key outside product, surface, for_surface, alternative, alternative_source, bound_because is refused as --add-dependency: <key> is not a field; the fields are product, surface, for_surface, alternative, alternative_source, bound_because, a value that is not one JSON object as --add-dependency takes one JSON object with product, surface, for_surface, alternative, alternative_source, bound_because, removing a product no need names as no dependency on <product> is declared; nothing was changed, and the catalogue's own checks above run on every write, so nothing is written when one refuses. A record whose last need is removed carries no dependencies list. Stado Desktop → Products → Update a catalog product carries the same two fields, beside the rival, roadmap and benchmark fields of the same command.
  • sync reinstalls every catalogued surface whose canonical checkout moved past its installed revision; --dry-run decides without fetching. A product whose repository the workspace holds no checkout of is held as no-checkout, unless --clone-missing clones it from its GitHub origin first.
  • The catalog dependencies judges against is a declaration, not a call: when that file lies in a checkout being read (Stado's own catalog/products.yml, which names every product's environment and programs), it is left out of that checkout's product code like a release manifest, so Stado is not reported as reaching every product its catalog describes. A catalog given with --catalog outside every checkout is read only as the catalog. Real journey: tests/dependencies/independence.mjs in Stado.
  • signing inspects, signs, reports and reconciles native code identities; see Native macOS signatures.
  • paths reports executable ownership: collisions, the names in ~/.local/bin that another directory on PATH answers differently, and unowned, every executable in ~/.stado/bin and ~/.local/bin that no installed product's receipt owns (its installed_paths, and the backups of an interrupted installation), each as {name, path, root, resolves_to}. An unowned program is a launcher placed by hand or one a retired surface left behind: no release delivers or updates it, and a product that runs it runs bytes no repository holds. The command reads and changes nothing; a root or entry that cannot be read refuses naming it. Real journey tests/product/paths/unowned.mjs.
  • cargo and swift build canonical main checkouts with an isolated lockfile or multi-root workspace. swift index writes .bsp/wisent-native.json, whose editor command is stado product editor serve. The editor server reads the canonical package's published settings; it does not compile or hold the janitor lock. editor status reports held workspace ownership, parent and connection age; see Editor connections for lifetime, duplicate-instance refusals and Desktop inspection.
  • cargo --revision <commit> builds one committed revision of the manifest's checkout instead of its working tree, so another session's uncommitted edit in the same checkout neither enters the build nor fails it, and a commit not yet pushed can be built. The commit is exported once with git archive into stado-build/<commit>/ of the checkout's build area (~/.stado/products/builds/<the checkout's path below the home>; stado-build carries CACHEDIR.TAG, since every export is reproduced from its commit), marked by .stado-exported.json (commit, archive_sha256), and a later build of the same commit reuses it. Nothing is written into the checkout. Cargo reads the manifest from there and builds with the same private lockfile, --locked and compiler cache as a working-tree build; the target directory is the export's own target/. The invocation's sources record the export as pinned: true with its commit and archive digest. The operation itself is handed the export the way a release worker states a build's source, WISENT_SOURCE_DIR (the export), WISENT_SOURCE_COMMIT and WISENT_SOURCE_SHA256, so a test that binds its evidence to the commit reads it there instead of asking Git, which the export does not carry. A commit the checkout does not hold is refused before anything is exported: <checkout> does not hold commit <commit>; fetch or commit it first; a commit that holds no such manifest is refused as commit <commit> of <owner>/<name> holds no <path>; the manifest is not committed there. stage takes --revision the same way. An installation needs no flag for this: install and update already build the checkout's committed HEAD through the same export, never its uncommitted edits; --source-commit installs a commit origin/main or the canonical checkout's own main carries.
  • documentation cli-pages verifies each catalogued CLI's published command pages; documentation markdown-policy reports repository Markdown outside the root README.

stado product signatures was removed in 0.22.0. Use stado product signing report PRODUCT --surface SURFACE to read and stado product signing reconcile PRODUCT --surface SURFACE to repair.

Skarbiec's source installation recipes for CLI, service and desktop declare a local signing_keychain; stado product install skarbiec --surface cli --json refuses a missing named keychain before building, never reads its own vault for signing, and reports the observed keychain and each native signature in the installed receipt. stado product status skarbiec --surface cli --json reads that result; Products in Stado Desktop shows the declaration, signature and the same refusal. An exact --release-version and --source-commit uses already signed bytes instead. See signing.

Reusing an installation build

A source installation reports each product and surface before it prepares it: build for an explicit source request or a dependency that needs replacing, reuse for a dependency whose completed receipt names its installed revision and whose installed files and signatures verify, whatever revision its checkout head has moved to, and release for a verified published archive. What a product needs of a dependency is a verb its contract requires (below), not the dependency's head: rebuilding every dependency to its head would make products that depend on each other a cycle. A changed recipe or destination, unfinished receipt, missing file or changed bytes does not qualify for dependency reuse. Products may depend on each other: a dependency that is an installation already in progress above this one (Tama depending back on the Oko being installed) is met by that installation, printed as <product>: <dependency> is met: the installation in progress that this one is a dependency of places it and recorded with "enclosing": true, not reported as a cycle. Dependencies are checked before the requested product is built, including the dependencies of a reused product. The JSON receipt keeps install_decision (action, reason, source_revision); each checked dependencies entry keeps its own decision. Repeating an explicit source install still runs its recipe.

Cargo source installations retain compiled dependencies instead of creating a cold target in every disposable run. The checkout's build area holds cargo/<platform>/<lockfiles-sha256>/<revision>/<recipe-key>/source and target. The key hashes the paths and contents of every committed Cargo.lock; the revision and recipe keep concurrent builds from writing the same Cargo target. The install run links its source to this stable source tree. Committed files are refreshed without changing the timestamps of identical inputs, so a second installation of that revision lets Cargo reuse workspace crates as well as registry dependencies. Both CARGO_TARGET_DIR and CARGO_BUILD_BUILD_DIR name the persistent target; an account-level intermediate-directory configuration cannot silently turn each export into a cold build. The declared Kache compiler cache also restores matching compiler outputs between isolated revisions. Cargo still invalidates artifacts when the toolchain, flags or inputs change. Refresh restores the committed node types as well as bytes: a build-created symlink replacing a committed file or directory is removed without following its target. An installation refuses a cache source root that is itself a symlink or another node type, naming the path; it does not traverse or replace that root. CLI and Desktop retain this refusal in the installation result.

cargo_cache in the receipt reports target, source, source_revision, platform, the exact lockfiles, their combined lockfile_sha256, reused and the compiler_cache executable. reused means the source cache existed, not that every compiler invocation was a hit: build-steps.json in the retained installation evidence records each successful quality/build command, its exit status, stdout and stderr. A failed command names its retained command evidence. Cache acquisition or compiler-cache installation failure refuses the operation; it does not silently compile without the cache. The cache has an in-use lock while building and a cache tag so the janitor can reclaim idle trees.

Stado Desktop's Products inspector presents the same build/reuse reason for the product and its dependencies, plus the Cargo target, lockfile identity and revision. These values come from the API's structured receipt, not parsed logs.

An unfinished installation belongs to its receipt

A different --host cannot reuse an installation still marked installing. The refusal names its product id, surface, recorded host, started_at (the receipt's installed_at while installing), full receipt path and the requested host. A missing host is printed as None, not guessed from the request. The CLI classifies this transition as refused, not unknown; Desktop's Products operation displays the same refusal and recovery command.

The command in the refusal acts on the machine that holds the receipt, with its exact HOME, workspace and catalog. It rolls back the incomplete installation to its retained predecessor. For a service with no predecessor it instead names product remove, because rollback cannot establish that service's absence. Both operations change owned files and may change the service on the receipt's recorded host. They retain the normal ownership and backup-integrity checks. They are not run automatically. Do not add --host to this recovery command: that would forward it to another machine, leaving this receipt unfinished. After recovery succeeds, repeat the installation originally requested.

This also covers a receipt left on the wrong machine by an older installation: finishing an installation on its recorded remote host does not clear the local receipt. Do not delete or rewrite the receipt to evade its ownership checks.

Build trees in the checkouts

The janitor cannot read a checkout in ~/Documents unless macOS lets its process read the folder, so a tree built there before the build area existed, or by a build Stado does not run — a Cargo target/, an old install run, a desktop .build — stays through any disk pressure. stado product build-trees reads and removes those trees from the process that runs it, which is the one macOS asks about: run it from a terminal that may read the folder.

stado product build-trees list
stado product build-trees remove --json
stado product --workspace ~/Documents/CodingProjects/Wisent build-trees list --json
  • It reads every git checkout directly in the workspace (--workspace, else WISENT_WORKSPACE, else ~/Documents/CodingProjects/Wisent) and finds, outermost only, each tree that is rebuildable for one of three reasons, its declared_by:
    • cachedir_tag: the directory carries a CACHEDIR.TAG its owner wrote whose first line is Signature: 8a477f597d28d172789f06886806bc55, as Cargo writes into every target;
    • stado_run_area: one of the areas older Stado versions wrote into the checkout, .wisent-output/install, .wisent-output/local-install, .wisent-output/cargo, .wisent-output/cargo-install, .wisent-output/commands, .wisent-output/install-failures, .wisent-output/native, .wisent-output/quality, .build/local-install, .build/package-install, .build/wisent-source, .build/wisent-surface, .build/wisent-native/build and .build/stado-build. A quality check an older Stado is still running holds no lock there and is not kept;
    • desktop_build_tree: a .build that sits beside a Package.swift, anywhere in a checkout whose origin is a repository the catalog builds a desktop surface from. A checkout's own .build with no Package.swift beside it is its scratch area (Stado's checkout, whose package is desktop/StadoDesktop, keeps bootstrap builds and test runs there) and is not one; the trees inside it are judged by the other two reasons.
  • Symbolic links are never followed, .git is never walked, and a directory on another volume or owned by another account is never entered. A directory the walk cannot read is named in errors and the walk goes on.
  • A tree a build is writing is kept as in_use, with the lock in lock: Stado's run marker .in-use, SwiftPM's .lock or Cargo's .cargo-lock, at the tree's top, in a directory below it or one level further, held by a process.
  • list changes nothing; every tree is reclaimable or in_use. remove deletes every reclaimable tree and marks it removed, or failed with the operating system's reason in errors.
  • Each tree reports checkout, path, declared_by, bytes (the lengths of its files), state and lock. The report adds checkouts, bytes, in_use_bytes, removed_bytes, and the volume's free_bytes_before and free_bytes_after. Without --json it prints one line per tree and a summary in GiB.
  • Refusals: a workspace that cannot be read, reading the workspace <dir>: <operating-system error> (a folder macOS withholds from this process answers Operation not permitted); a tree that could not be removed, after the report is printed, build-tree removal incomplete: <n> tree(s) under <workspace> could not be removed; each is named with the operating system's reason in errors; an operation other than list or remove, or none, is a usage error (exit 2).
  • Settings in Stado Desktop has Rebuildable trees in the API host's checkouts: List rebuildable trees runs stado product build-trees list --json through the selected Stado API, shows each tree's state, size and reason and the summary, and Remove rebuildable trees… asks first, then runs remove with the RUN_MUTATION confirmation. The API's process reaches the checkouts only where macOS lets it read their folder; its refusal and the complete receipt are shown as the command wrote them.

Rivals, benchmark and roadmap

A record may name the products it competes with and the benchmark that measures it against them. The two are declared together or not at all: a rival nothing measures is a promise, and a benchmark with no rival measures the product against nobody.

stado product registry set weles \
  --add-rival '{"id":"stagehand","name":"Stagehand","url":"https://www.stagehand.dev","evidence":"https://github.com/browserbase/stagehand"}' \
  --benchmark '{"app":"weles","suites":["web-agent-v1"]}'
stado product registry set weles --add-roadmap '{"title":"<case lost>","status":"planned","outcome":"<what winning it looks like>","source":"<run id>"}'
stado product registry set weles --remove-roadmap '<title>'
stado product registry set weles --remove-rival stagehand
  • rivals is a list of {id, name, url, evidence}. Each id is a slug and is the id of the contender that runs the rival in Probierz; url must be https; name and evidence must not be empty; a product is not its own rival and no id is declared twice.
  • benchmark is {app, suites}: the Probierz application whose manifest declares the suites, and at least one suite id.
  • roadmap items are {title, status, outcome, source}. probierz benchmark roadmap <app> --suite <id> writes one item per case the newest run lost and withdraws it once ours wins that case.
  • stado product catalog --json serves every record whole, these fields included; probierz benchmark rivals <app> reads the rivals and the benchmark from it.

Refusals, before the file is written: <id>.rivals: declared rivals need a benchmark that measures them; add benchmark: {app, suites}, <id>.benchmark: a benchmark needs at least one declared rival to measure against, <id>.rivals.<rival>: url must be https, got <url>, <id>.rivals: <rival> is declared twice, <id>.benchmark.suites: at least one suite is required, and --add-rival: <key> is not a field; the fields are id, name, url, evidence for an unknown key.

Keeping installed surfaces current

A host's one Stado process keeps its installed surfaces current, refuses to replace a program other products still call, resumes an installation that stopped part way, and settles installations that wait on one another: Keeping installed surfaces current.

Inputs and state

  • --workspace DIR, else WISENT_WORKSPACE, selects the parent of canonical checkouts, default ~/Documents/CodingProjects/Wisent; the host process's product sync uses ~/.stado/products/workspace. Without --catalog, the authority is <workspace>/stado/catalog/products.yml; when that file is absent, read-only commands use the catalog compiled into Stado.
  • WISENT_OUTPUT_DIR selects retained command and compiler evidence, default ~/.stado/products/output, never a checkout: the checkouts sit in ~/Documents, which macOS keeps from the janitor. Every record names stado_version and stado_source_revision of the build that ran it.
  • Installed state is ~/.stado/products/PRODUCT/SURFACE.json.
  • A checkout's build area is ~/.stado/products/builds/<the checkout's path below the home>, or ~/.stado/products/builds/outside-home/<its absolute path> for a checkout that is not below the home. Every run an installation writes — install/<run> (the committed-source export and its build output), local-install/<run> and local-install-home/<run> (a local build's evidence and isolated home), cargo-install/<run> (a Cargo recipe's target), package-install/<id> (an npm or pip install's scratch, removed when the install ends) and source-index (the private index a source snapshot is taken with) — lives there, never in the checkout, because the checkouts sit in ~/Documents, which macOS keeps from the janitor. So do stado product cargo's cargo/<invocation> evidence and cargo-scratch/<invocation>, stado product swift's native/<run> evidence and the stado-build/<commit> exports of cargo --revision. A run is tagged with CACHEDIR.TAG when it is shed — once its installation is recorded installed, its build is abandoned, or a later build supersedes it — so the janitor's build-cache cleaner takes it under disk pressure; a run still being written has no tag. A desktop product's SwiftPM .build is kept as swiftpm there between builds: the build moves it back into the checkout, because the product's own build script names <checkout>/.build, and once the installation is recorded installed, or the build failed, moves it out again and tags it. A move that fails leaves the tree in the checkout and is recorded in the installation's state as build_tree_left. A failed installation's refusal records are kept in install-failures/ of the build area.
  • stado quality check, and the admission check of stado release submit and stado release changes submit, export the revision and run its gates in quality/<pid>-<revision> of the same build area, where <pid> is the checking process. A check removes its own area when it ends. A check killed before that leaves its area behind, so each check first asks the kernel about the pid in every other area's name (a signal-0 probe): an area whose pid answers ESRCH, no such process, is removed; an area whose pid answers anything else — a running check, or a later process that reused the number — is left alone until that process is gone. A removal that fails is printed as stado quality check: cannot remove the abandoned run <path>: <operating-system error> and the check goes on.
  • stado product swift build|test|index builds a package into swift/build of the package's build area (--scratch-path), tagged with CACHEDIR.TAG so the janitor reclaims it, and keeps its multi-root .xcworkspace, its compiler's temporary directory and its writer lock beside it in swift/; nothing of the build is written into the package. The editor settings index publishes stay in the package, .build/wisent-native/index/build-settings.json, and the index reads SwiftPM's compiler manifest from the relocated tree. A source in that tree is published as generated: true, unless git places it in a repository whose git directory lies inside the tree — one of SwiftPM's dependency clones — which is refused as compiler source belongs to a dependency clone <git dir>: <source>. A source outside the tree must belong to one of the package graph's canonical checkouts, or the index is refused as compiler source is outside the canonical package graph and the build tree <tree>: <source>.
  • stado product install, update, cargo and every swift operation run only while the operator's rulings allow building, as Oko's one admission decision answers it: before anything is exported or compiled Stado asks oko admit build --json (with --product <name> when the operation names one) and reads Oko's verdict, allowed and the reason quoting what decided it. A standing withdrawal (no-build, no-build:<product>) refuses the operation with build of <product> is not run: the operator's rulings do not allow it now (<Oko's verdict quoting the ruling, its session, message and words>); nothing was exported or built; a later consent of his lifts it. Stado keeps no copy of the rule. An oko that gives no verdict at all, because it cannot be started or is an Oko without the admit verb, is not a ruling and is not said as one: build of <product> is not run: Oko gave no admission decision, so whether the operator's rulings allow it was never read (oko admit build … exited <status> without a verdict: <its words>); install an Oko that carries admit (see Cross-product verb prerequisites). The release loop asks oko admit release --product <name> the same way before a delivery publishes, and a refused step names the ruling.
  • Stado runs without Oko as every product runs without the others. On a machine with no Oko installed at any of its declared paths (~/.stado/bin/oko and the rest of its candidates) there is no register of rulings, and the operation is admitted with the reason no Oko is installed on this machine (oko is installed at none of its declared paths: <paths>), so no ruling governs <action> of <product>, kept in STADO_ADMITTED_<ACTION> like Oko's own answer. An Oko that is installed and gives no verdict is refused as above: its rulings exist and were not read. A process whose HOME is unset never looked where Oko would be and is refused the same way.
  • An admitted operation is one operation with everything it runs. Stado keeps Oko's answer in its environment as STADO_ADMITTED_<ACTION> (STADO_ADMITTED_BUILD: the admitted process's pid and Oko's words), so the quality gates of an admitted build, the real journeys those gates start and the dependency installs it makes are admitted for the same action without asking Oko again: a gate does not depend on which oko the machine has installed. Only an admission of a process that started this one is inherited; a process asks Oko for each of its own operations, as a delivery plan asks for every step. A value Stado did not write admits nothing, and Oko is asked.
  • stado product install, update, cargo and every swift operation hold the disk janitor's cleanup lock in shared mode while they run, so a cleanup pass cannot delete their build area mid-compile. stado product editor serve only serves existing editor settings and holds no cleanup lock for its lifetime. An operation started while a pass holds the lock exclusively waits for it as a czekam: lock wait (listed by stado waits) and runs once the pass lets go. While the janitor has asked running work for its turn, an operation given --wait waits, as a czekam: lock wait naming the running workloads, until none holds the lock, runs the janitor's pass itself (which answers the turn request) and then takes its hold. The pass walks the build caches before it takes the exclusive lock, so a workload that takes a hold meanwhile turns the pass away and the janitor asks for its turn again; the operation then says so on stderr with the pass's report and the holders it now waits on, and waits for that turn too, round after round, until a pass has run and its own hold is taken. Without --wait it is refused before export or compilation, and the refusal names each recorded holder's operation, PID, acquisition timestamp (Unix seconds) and elapsed hold, plus the janitor's recorded owner and turn request where available, and says that --wait takes the turn. A turn request, lock or hold record that cannot be read refuses the operation with its path and cause; missing records say ownership was not established. Desktop Products retains the same refusal; Hosts → Space shows the private pass's recorded holders and acquisition times. See Space: Locks, including the distinction between a recorded observation and current kernel ownership.
  • A release archive's package (WISENT_SOURCE_DIR, with no repository of its own) is built inside itself, .build/wisent-native/, in the job tree the janitor sweeps with the job: a build area named after a job tree would outlive the job.
  • stado product surface builds the candidate and the released tag in surface/ of the checkout's build area, tagged with CACHEDIR.TAG, and its report names that directory as scratch.
  • create reads STADO_PRODUCT_INTEGRATION_URL and the owner-only token file named by STADO_PRODUCT_INTEGRATION_TOKEN_FILE, and keeps requests in STADO_PRODUCT_STATE_DIR, default ~/.local/state/stado/product. Requests kept by the former program in ~/.local/state/wisent-products move there on first use.
  • Product operations call Stado's own release and service commands with the executable that is running, not the first stado on PATH.
  • A source installation fetches each input the product's .wisent-release.json pins (inputs.<name>.uri and sha256) with stado storage get, refuses it when its SHA-256 differs from the declaration, and mounts it at the path the build reads as WISENT_INPUT_<NAME>_DIR. An input is unpacked unless it declares "extract": false. Plain and gzip-compressed tar archives are accepted; a pax global header, which git archive writes to record the commit, is metadata and is skipped. A member with an absolute path, a .. component, a link that leaves the archive, or a special file such as a device or FIFO is refused. The receipt for every input is kept in inputs.json beside the build.
  • An installation recipe may declare after_install: argv arrays run, in order, after the files are placed and before readiness is read. A step runs a binary this installation produced, or stado itself, so an installation can declare what it needs in Stado; any other program is refused naming it. {release_archive} and {release_archive_sha256} are replaced with the verified release archive the installation came from and its digest; a recipe that names them for an installation that came from no release is refused. {host} is replaced, anywhere in a word, with the registry name of the host the installation is for. {install_id:NAME} is replaced with a UUID derived from the product, the surface, that host and NAME: the same on every installation of that surface on that host, so a step that declares lasting state under it (stado schedule create --id {install_id:maintain}) changes nothing when it runs again. Stado's own CLI recipe runs stado release local converge --archive {release_archive} --sha256 {release_archive_sha256}, which restarts every unit on the host still executing the Stado binary the install replaced, so a CLI install does not leave the object API or the janitor on the old image. Brama's service recipe declares the gateway's maintenance pass, brama maintain --gateway-consumer brama-desktop --bearer-role brama-console, as a schedule pinned to its host every minute; the console bearer is the item tagged stado:role:brama-console.
  • The recipe an installation executes, after_install included, is read from a committed catalog, never from the checkout's working tree: an installation of Stado from --source-commit X reads catalog/products.yml at X, so a verb being renamed in the checkout cannot hand the installed binary a step it does not have; every other installation, and product sync, reads the committed head of the catalog's checkout; a machine without the checkout reads the catalog compiled into Stado. The report names what was read under catalog: its source, the revision and why it was chosen (installed-revision, committed-head or embedded).

Restarting a desktop application

stado product restart [--if-running] [--json] APP.app restarts a macOS application bundle after its installed bytes change, for a bundle placed outside an installation. It reads CFBundleIdentifier from the bundle's Contents/Info.plist, asks LaunchServices (lsappinfo) whether that application runs and with which process id, asks LaunchServices to end it, and waits for that process to exit with no deadline: the wait is registered (stado waits names <bundle id> (pid <pid>) to exit). It then opens the bundle in the background and reads it running again. It prints restarted <bundle> (<bundle id>), or started … when the application was not running; with --if-running a stopped application is left stopped and the answer is not-running. --json prints {"bundle", "bundle_id", "state"}. Refusals name what failed: application bundle not found: <path>, application Info.plist not found: <path>, CFBundleIdentifier is empty in <plist>, LaunchServices lists <id> as running but reports no process id: …, open accepted <bundle>, but LaunchServices lists no running <id>, and on any other system restarting a desktop application requires macOS LaunchServices. Stado Desktop's Products screen runs the same verb as Restart a local macOS application bundle after its installed bytes change.

stado product install and update of a desktop surface do the same once the bundle is placed and the receipt says installed: each placed .app whose application is running is restarted from the new bundle, one that is not running stays stopped, and the answers are kept on the receipt as restarted. A desktop build script therefore restarts nothing itself; a restart that fails ends the installation with the refusal above, its files already placed.

Stado Desktop's Products screen offers Restart beside each installed application bundle of a product's desktop surface (each .app path the installation state names). It asks for confirmation, showing the command, and runs stado product restart <bundle> --if-running --json: a running application is restarted from that bundle and a stopped one is left stopped, and the answer names the state, bundle and bundle identifier the command printed. A refusal is shown as the command wrote it.

The service catalog

Stado's shipped service catalog, the services stado service ensure NAME knows by name, is each product's service declaration in the compiled product catalog. There is no second generated file: changing a record in catalog/products.yml changes what the next Stado build ships. stado product catalog --output PATH writes the same derived list for inspection.

The name register

stado product catalog --names [ORG] writes the workspace name register as Markdown: one row per active repository of the organization (default wisent-ai) as gh repo list reports it, with the catalog product that claims it and what it is. --output PATH writes it (~/NAMES.md is the register agents read), --check PATH refuses a file that differs: <path>: differs from the register of <org>; regenerate it with --names --output <path>.

What a repository is comes from its GitHub description. When GitHub has none and a catalog product claims the repository, the row says what the catalog says it is: the product's owner repository carries the product's description from catalog/products.yml, and any other repository the product claims as a surface carries its surface kind and the product before that description, such as desktop surface of `weles`: …, so a desktop app, web app or client library never reads as the product itself. A repository claimed as several surfaces of one product names each kind (desktop and mobile surface of …). The from column says which source each row read, GitHub or catalog; a repository described by neither reads (no description on GitHub or in the catalog) with an empty from, so it is still listed. A repository gh repo list gives no nameWithOwner is refused: gh repo list gave repository <name> no nameWithOwner, so the catalog claim on it cannot be looked up. To describe a repository, set its GitHub description, or the claiming product's record with stado product registry set <product> --description TEXT, then regenerate.

Desktop

Stado Desktop's Products screen reads stado product catalog --json and runs status, install, update, rollback and remove for a product surface through the selected Stado API host. Under the product table it runs the rest of stado product on that host: Register a product in the catalog, Update a catalog product and Remove a product from the catalog (registry add|set|rm), provisioning with Provision a preview product and its private repositories and reading or resuming a request (create --request|--status|--resume, with --allow-create as an explicit checkbox), Show what reconciling installed surfaces would do (sync --dry-run) and Reconcile installed surfaces (sync), paths, cargo and swift with the arguments they forward, and both documentation checks. The API asks for confirmation of every operation that writes, and a refusal shows the CLI's own sentence — provisioning without the checkbox reads authority_required: --allow-create is required; request content cannot grant repository creation. Settings → Native code signatures on the Stado API host runs stado product signing report and, after review, stado product signing reconcile. A host running a Stado older than 0.22.0 has no catalog of its own and shows an empty Products screen.

The same list carries Read which products each product's code needs, and refuse one with no way to run without them, which runs stado product dependencies [PRODUCT] --json on the selected host and shows its answer and refusals as the CLI prints them; a product not shown running alone ends the operation as refused.

It also carries the name register, sending the Markdown command without --json, which --names refuses: Read the name register: what each repository of the organization is, and where that was read runs stado product catalog --names [ORG] (a blank organization takes the CLI's default) and shows the register with its from column as the CLI prints it, Write the name register to a file (~/NAMES.md) adds --output PATH, and Check that a name register file is the one the organization and the catalog give now adds --check PATH and shows the CLI's refusal of a file that differs.

A committed manifest missing from its Cargo lock

When a source build fails, Stado compares the exported Cargo workspace's declared dependencies and version requirements with its committed lock. Missing packages, including path dependencies, and incompatible locked versions name their declaring manifests. The refusal names the product, exact source revision, workspace manifest and Cargo.lock, retains the original compiler error and command evidence, and reports a source configuration failure (config), not credentials.

For example, a missing package is reported as committed source configuration: <product> at <revision>: <manifest> declares <package> <requirement> (declared in <member-manifest>) that its committed <lock> does not lock. For a version mismatch it also gives locked <versions>, so a package already present by name does not hide a requirement that none of its locked versions meets. Update the lock for that product with cargo update -w --manifest-path <manifest>, commit the corrected Cargo.lock, then install the corrected canonical revision. Stado does not alter the exported lock or apply another session's working-tree edits. Desktop Products displays the same refusal in the installation result, including the revision and remedy.

Cross-product verb prerequisites

A committed contracts/verbs.json beside each product's strict .wisent-release.json declares {"product": "oko", "provides": [{"verb": "admit"}]} or {"product": "stado", "requires": [{"product": "oko", "surface": "cli", "verb": "admit"}]}. A preceding Stado installer ignores the separate file and can still parse the release manifest. The new installer reads each contract at the exact consumer, installed provider and qualified provider revisions; an older revision without a contract promises no verbs. It finds the first provider revision declaring the verb in the contract's committed history, checks the installed receipt and files, and selects the last-green provider revision the delivery routine's main check recorded (~/.stado/ci-main.json, written by stado ci run) when the installed one does not suffice. A product with no such requirement installs as before.

stado product install stado --surface cli --source-commit <commit> --plan --json
stado product install stado --surface cli --source-commit <commit> --with-prerequisites --json

The plan orders required provider verbs before the consumer and names minimum, installed and qualified revisions with the decision. Without --with-prerequisites, installation refuses a missing verb before building the consumer, naming the provider, verb, revisions and the flag to use. An installed revision that still provides a verb remains valid even if checkout HEAD no longer declares it. A pass of the delivery routine with the provider's step in its plan (stado ci plan set, then stado ci run) checks the provider's main and records its last green commit, and stado ci delivery names each step's last checked and last green commit and why; an installed provider that does not serve the verb is replaced by that qualified revision. An unqualified checkout head is never substituted. A failed provider installation stops the consumer; inspect each product with stado product status <product> --surface cli --json.

A verb requirement binds only a provider that can serve it. Every product runs without the others: with the provider not installed, or named in --without, the consumer installs without that verb, its plan names no step for it, and the consumer's own use of the verb says at run time that the provider is absent (Stado admits an action with no Oko installed, saying no ruling governs it). An installed provider that does not serve the verb, with no qualified revision to replace it (no CI record, or none green), binds nothing either: the consumer installs without the verb and standard error says stado: <consumer> installs without verb <verb> of <provider> <surface>: installed revision <rev> does not serve it (<reason>) and no revision of <provider> is qualified to replace it (<cause>); a pass of the delivery routine … qualifies one. Two products that each need the other's new verb install one after the other this way, the second with the verb the first now serves.

A dependency whose verb requirement names the product being installed is met by the revision that installation builds, when that revision's contracts/verbs.json declares the verb: installing Oko builds its Stado dependency first, and that Stado's requirement of oko admit is met by the Oko revision about to be placed, not refused for the older Oko still installed. The decision reads the installation of <product>/<surface> at <revision> this one is a dependency of provides the verb and places it once this one is done. A dependency Stado builds for itself is finished by the binary it built (stado product finish); the installations in progress are handed to it in STADO_PRODUCT_ENCLOSING, and a value there that is not that list is refused naming the variable. A revision that does not declare the verb is judged as before.

Stado Desktop → Products shows Plan prerequisites per surface, the ordered revisions or exact refusal, and Install with prerequisites through the normal confirmation dialog. The confirmation lists the same CLI arguments. Real product installation tests include the older-provider journey and lock recovery.

Source: this website