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
catalogreads 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 setandregistry rmchange 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.createprovisions private repositories through Wisent Integrations and keeps each request for--statusand--resume.install,update,status,rollbackandremoveact on one product surface (--surface cli|desktop|service,--hostfor a service).--release-versionwith--source-commitinstalls an exact published release of astado-releaserecipe on thecliorservicesurface: 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--hostwith 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 withexact release coordinates require a stado-release recipe on the cli or service surface.--source-commitalone installs a source build of that exact canonical commit: the checkout exports that commit's tree, never its working tree, onceorigin/mainor the canonical checkout's ownmaincarries it. A commit onmainthat 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'sinstall_decisioncarriessource_on_origin: falseand the build'sprepared.jsonthe same; a commit on neither is refused asneither origin/main nor <checkout>'s main carries <commit>; only canonical source is installed. The real journeytests/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 toorigin/main.- A newer
installorupdateof 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 assuperseding the installation held by pid <pid> …: it has placed nothing yet. One already placing files is waited for, printed aswaiting for <lock>: … (it is placing files), and so is one asking for the same installation (the same arguments after the program path,--jsonaside), printed aswaiting for <lock>: … (the same installation, already under way), because stopping it would only restart the same work.--waitkeeps the older one whatever it is doing and runs after it. A service operation (install,update,status,rollback,removewith--surface service) whose--hostis another registry host runs on that host: Stado sends the same arguments, with--hostin 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.--catalogis 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 asstado product … on <host> failed (exit <code>): <its output>. installandupdatebuild 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 ontomainat<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>).statusnever creates a checkout;synccreates one the same way only with--clone-missing.- A product's catalogue record lists the products it depends on, and
installinstalls those first.--without brama,skarbiecinstalls on a machine that does without them: a dependency on a named product is skipped when the catalogue declares itsalternative, printed as<product>: installed without <dependency>/<surface>: <alternative>and recorded in the installation'sdependencieswith"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 declaresbound_because,…: the catalogue binds it by design: <reason>, and the product is not installed. The choice is kept, soupdateandsyncdo not install a skipped dependency; a later--withoutreplaces 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 withstado product catalog --jsonandstado product dependenciesbelow; 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
alternativefor it: the dependent is installed, standard error says<product>: installed without <dependency>/<surface>, whose installation failed: <cause>; <alternative>, and the installation'sdependenciesrecord it with"without": trueand"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
servicesurface 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--hostchecks no service dependency and records it as"checked": falsewithreason: "no --host was given". dependencies [PRODUCT] [--json]reads, from the canonical checkout of every repository a product's record names (owner_repositoryand 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 declaredbinaries, or its id when none is declared; a quoted path ending in it, unless the path lies undertests/, a manifest's test target; a shell line starting with it),resolver(forwards/<product>…or<product>.local),http(<product>.wisent.com/…, a/docslink 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-levelrelease/directory (any case) and the releasemanifesteach catalogue installation of that repository declares: how the product is built, signed and published is its installation recipe's, not what the product runs.--jsonadds every line as{repository, file, line, channel}. A product reached isindependentonly when every catalogue dependency on it declares analternativeand analternative_source(<repository>/<path>[:<lines>], the code that implements the alternative) that is a file in the workspace; otherwise it isundeclared,bound(a dependency with no alternative; withbecausewhen the catalogue declaresbound_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 noalternative_source) orsource-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 asthe 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 analternative_sourcewithout analternative, an emptyalternative,alternative_sourceorbound_because, abound_becausebeside analternative(a need is bound by design or has a way around it, not both), a field outsideproduct, 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 sameproduct,surfaceandfor_surface, and--remove-dependency OTHERremoves every declared need ofOTHER. A key outsideproduct, surface, for_surface, alternative, alternative_source, bound_becauseis 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 asno 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 nodependencieslist. Stado Desktop → Products → Update a catalog product carries the same two fields, beside the rival, roadmap and benchmark fields of the same command. syncreinstalls every catalogued surface whose canonical checkout moved past its installed revision;--dry-rundecides without fetching. A product whose repository the workspace holds no checkout of is held asno-checkout, unless--clone-missingclones it from its GitHub origin first.- The catalog
dependenciesjudges against is a declaration, not a call: when that file lies in a checkout being read (Stado's owncatalog/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--catalogoutside every checkout is read only as the catalog. Real journey:tests/dependencies/independence.mjsin Stado. signinginspects, signs, reports and reconciles native code identities; see Native macOS signatures.pathsreports executable ownership:collisions, the names in~/.local/binthat another directory onPATHanswers differently, andunowned, every executable in~/.stado/binand~/.local/binthat no installed product's receipt owns (itsinstalled_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 journeytests/product/paths/unowned.mjs.cargoandswiftbuild canonicalmaincheckouts with an isolated lockfile or multi-root workspace.swift indexwrites.bsp/wisent-native.json, whose editor command isstado product editor serve. The editor server reads the canonical package's published settings; it does not compile or hold the janitor lock.editor statusreports 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 withgit archiveintostado-build/<commit>/of the checkout's build area (~/.stado/products/builds/<the checkout's path below the home>;stado-buildcarriesCACHEDIR.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,--lockedand compiler cache as a working-tree build; the target directory is the export's owntarget/. The invocation'ssourcesrecord the export aspinned: truewith 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_COMMITandWISENT_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 ascommit <commit> of <owner>/<name> holds no <path>; the manifest is not committed there.stagetakes--revisionthe same way. An installation needs no flag for this:installandupdatealready build the checkout's committedHEADthrough the same export, never its uncommitted edits;--source-commitinstalls a commitorigin/mainor the canonical checkout's ownmaincarries.documentation cli-pagesverifies each catalogued CLI's published command pages;documentation markdown-policyreports 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, elseWISENT_WORKSPACE, else~/Documents/CodingProjects/Wisent) and finds, outermost only, each tree that is rebuildable for one of three reasons, itsdeclared_by:cachedir_tag: the directory carries aCACHEDIR.TAGits owner wrote whose first line isSignature: 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/buildand.build/stado-build. A quality check an older Stado is still running holds no lock there and is not kept;desktop_build_tree: a.buildthat sits beside aPackage.swift, anywhere in a checkout whose origin is a repository the catalog builds a desktop surface from. A checkout's own.buildwith noPackage.swiftbeside it is its scratch area (Stado's checkout, whose package isdesktop/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,
.gitis never walked, and a directory on another volume or owned by another account is never entered. A directory the walk cannot read is named inerrorsand the walk goes on. - A tree a build is writing is kept as
in_use, with the lock inlock: Stado's run marker.in-use, SwiftPM's.lockor Cargo's.cargo-lock, at the tree's top, in a directory below it or one level further, held by a process. listchanges nothing; every tree isreclaimableorin_use.removedeletes everyreclaimabletree and marks itremoved, orfailedwith the operating system's reason inerrors.- Each tree reports
checkout,path,declared_by,bytes(the lengths of its files),stateandlock. The report addscheckouts,bytes,in_use_bytes,removed_bytes, and the volume'sfree_bytes_beforeandfree_bytes_after. Without--jsonit 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 answersOperation 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 thanlistorremove, 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 --jsonthrough the selected Stado API, shows each tree's state, size and reason and the summary, and Remove rebuildable trees… asks first, then runsremovewith theRUN_MUTATIONconfirmation. 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
rivalsis a list of{id, name, url, evidence}. Eachidis a slug and is the id of the contender that runs the rival in Probierz;urlmust behttps;nameandevidencemust not be empty; a product is not its own rival and no id is declared twice.benchmarkis{app, suites}: the Probierz application whose manifest declares the suites, and at least one suite id.roadmapitems 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 --jsonserves 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, elseWISENT_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_DIRselects 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 namesstado_versionandstado_source_revisionof 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>andlocal-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) andsource-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 dostado product cargo'scargo/<invocation>evidence andcargo-scratch/<invocation>,stado product swift'snative/<run>evidence and thestado-build/<commit>exports ofcargo --revision. A run is tagged withCACHEDIR.TAGwhen 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.buildis kept asswiftpmthere 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 asbuild_tree_left. A failed installation's refusal records are kept ininstall-failures/of the build area. stado quality check, and the admission check ofstado release submitandstado release changes submit, export the revision and run its gates inquality/<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 answersESRCH, 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 asstado quality check: cannot remove the abandoned run <path>: <operating-system error>and the check goes on.stado product swift build|test|indexbuilds a package intoswift/buildof the package's build area (--scratch-path), tagged withCACHEDIR.TAGso the janitor reclaims it, and keeps its multi-root.xcworkspace, its compiler's temporary directory and its writer lock beside it inswift/; nothing of the build is written into the package. The editor settingsindexpublishes 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 asgenerated: true, unless git places it in a repository whose git directory lies inside the tree — one of SwiftPM's dependency clones — which is refused ascompiler 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 ascompiler source is outside the canonical package graph and the build tree <tree>: <source>.stado product install,update,cargoand everyswiftoperation run only while the operator's rulings allow building, as Oko's one admission decision answers it: before anything is exported or compiled Stado asksoko admit build --json(with--product <name>when the operation names one) and reads Oko's verdict,allowedand thereasonquoting what decided it. A standing withdrawal (no-build,no-build:<product>) refuses the operation withbuild 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. Anokothat gives no verdict at all, because it cannot be started or is an Oko without theadmitverb, 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 carriesadmit(see Cross-product verb prerequisites). The release loop asksoko 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/okoand the rest of its candidates) there is no register of rulings, and the operation is admitted with the reasonno Oko is installed on this machine (oko is installed at none of its declared paths: <paths>), so no ruling governs <action> of <product>, kept inSTADO_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 whoseHOMEis 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 whichokothe 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,cargoand everyswiftoperation 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 serveonly 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 aczekam:lock wait (listed bystado waits) and runs once the pass lets go. While the janitor has asked running work for its turn, an operation given--waitwaits, as aczekam: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--waitit 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--waittakes 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 surfacebuilds the candidate and the released tag insurface/of the checkout's build area, tagged withCACHEDIR.TAG, and its report names that directory asscratch.createreadsSTADO_PRODUCT_INTEGRATION_URLand the owner-only token file named bySTADO_PRODUCT_INTEGRATION_TOKEN_FILE, and keeps requests inSTADO_PRODUCT_STATE_DIR, default~/.local/state/stado/product. Requests kept by the former program in~/.local/state/wisent-productsmove there on first use.- Product operations call Stado's own release and service commands with the executable that is running, not the first
stadoon PATH. - A source installation fetches each input the product's
.wisent-release.jsonpins (inputs.<name>.uriandsha256) withstado storage get, refuses it when its SHA-256 differs from the declaration, and mounts it at the path the build reads asWISENT_INPUT_<NAME>_DIR. An input is unpacked unless it declares"extract": false. Plain and gzip-compressed tar archives are accepted; a pax global header, whichgit archivewrites 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 ininputs.jsonbeside 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, orstadoitself, 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 runsstado 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 taggedstado:role:brama-console. - The recipe an installation executes,
after_installincluded, is read from a committed catalog, never from the checkout's working tree: an installation of Stado from--source-commit Xreadscatalog/products.ymlat 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 undercatalog: itssource, therevisionand why it was chosen (installed-revision,committed-headorembedded).
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