The one unit a product runs on a host

The one unit is named com.wisent.<product>

Each catalog product uses one canonical service identity: com.wisent.stado, com.wisent.skarbiec, com.wisent.brama, com.wisent.weles, com.wisent.probierz, com.wisent.skrzynka, com.wisent.oko, com.wisent.transcript-lake. The catalog refuses any other service.unit, so stado registry set <product> --service-file <file> and every command that loads the catalog fail with:

<product>.service.unit: <label> is not com.wisent.<product>; a product runs one service named com.wisent.<product>

A service the product no longer runs leaves the catalog with stado product registry set <product> --remove-service, which removes the record's service declaration and nothing else. A product that declares no service is refused (<product> declares no service, so --remove-service would change nothing), and the flag cannot be given with --service-file. Stado Desktop → Products → Update a catalog product has the same switch.

The units a product's process replaced are not declared anywhere: a catalog that carries retired_units or role_units is refused with <product>.service.<key>: the catalog does not list unit names; the units a product's one process replaced are found on each host from the program they run, so remove <key>. On the host, every unit that runs the product's program under another label is its predecessor (a program in the product's own service tree, the exact program the catalog declares, or a file named after the product wherever it was installed). stado service ensure <product> --host <target> retires those it replaces whole before it starts com.wisent.<product>, because the new unit binds the same port the old label holds. Each retired unit is printed on stderr as <target>: <product> retired <unit> before starting: retired (<detail>). If one cannot be retired, the new unit is not started and the command fails with <target>: <product> was not started, because the units it replaced could not all be retired and would run beside it: <unit>: <error>; a host whose units cannot be read refuses with <target>: the units <product> replaces could not be read, so none was retired: <error>. Units whose work is a role of com.wisent.stado (another stado serve unit, or a native timer repeating product sync or a beacon command) are not touched before the start; their roles are read from their command lines and retired only after the live process proves the work is held.

A unit whose work has not moved into the product is not its predecessor, and Stado does not guess: a unit that runs no product's program is never retired as one. An undeclared unit in stado service list --undeclared is not proof that its work has moved; the reconciler retires such a unit only when nothing runs it, and reports a live one as undeclared_live.

Before it retires anything, ensure checks that the unit's program is on the host. When it is not, nothing is retired or started and the command is refused (error_code=refused):

<target>: <product> runs <program>, which is not on the host; deliver the product's qualified service release to <target> before ensuring it. Nothing was retired or started

The current stado product install <product> --surface service --host <target> does not transfer its locally placed files to a remote host. Do not use its local installation receipt as proof that the target has new bytes. Use the qualified release delivery and inspect the target's active image before retrying ensure.

When the host already declared the product under an older label, the registry write that records com.wisent.<product> also points every reference to the old label on that host at the new unit: the host's unit in each placement profile (placement_profiles[].hosts.<target>.units.<service>) and every service-directory entry active on that host whose managed_service named the old label. This keeps placement and routing references consistent with the recorded unit. If recording fails after a host action, the command reports that completed action and the registry failure separately.

Stado's own API listener is taken over by the process itself: once com.wisent.stado has prepared its object store it binds its port, and only when that bind fails (serve: the API listener <bind>:<port> did not bind (<cause>); retiring the units that hold it before binding again) does it retire every unit on its host that runs the Stado program as the API listener under another label (serve --api), provided each one serves the same local root. Predecessors are found from each loaded unit's label, domain and programs only (the image read), not the full unit read with environment keys and script reads, so the scan does not hold the bind on a host with many labels. A scan of the host's units that fails is logged and retires nothing. While the start runs, each awaited step is a registered wait, stado serve start: <step> (API storage and listener, host identity, control plane, this process's resolver serving), so stado waits and the service log name the step a start that has not come up is on; stado serve: target=<host> pid=<pid> components=<list> says it came up. Startup depends only on Stado's own state and the registry: no step waits on a provider's client. The tailnet map (tailscale status --json), which pins MagicDNS names to tailnet addresses, is read beside the roles from the moment serve starts; until the client answers, every reader routes as with no map (the system resolver, the routes that need no tailnet), and the beacon's tailscale_client block says what the client does (beacon). A client that never answers is reported there and is not ended on a timer; it ends with serve, which on SIGTERM, SIGINT or SIGHUP, or on its own end, ends the client's process group and closes its wait before it exits, then ends by the signal it received. The predecessor's root is resolved the way its own process resolves it: WC_STORAGE_BACKEND and WC_LOCAL_STORAGE_PATH from the unit's environment, else the storage section of the config file it starts with (STADO_CONFIG, then ~/.config/stado/config.json and ~/.stado/config.json under its HOME), else local and ~/.stado/local-storage. With no storage variables, the selected configuration and defaults still determine the root. A different root is refused before startup, and the log names both resolved roots:

[stado] predecessor <unit>: failed (it serves backend "local" root "<predecessor-root>" by its config file, this process serves <desired-root>; moving the object store is the storage-root step of `stado repair stado --step storage-root --target <TARGET>`, not a takeover)
serve refusing to start beside predecessors that could not be retired: <unit>: …

A predecessor whose state the init system does not give (a sudo -n the host refuses, a manager that does not answer) is not proven to serve the same root and is refused the same way, naming the command and how it ended:

[stado] predecessor <unit>: failed (its state could not be read, so it is not proven to serve this root: /usr/bin/sudo -n /bin/launchctl print system ended with exit status: <status>: sudo: a password is required; …)

If the init system cannot say which pid runs com.wisent.stado itself, the process retires nothing and logs why.

Moving the store is the storage-root step of stado repair stado --step storage-root --target <TARGET> --apply, never a takeover.

Source: this website