On this page

Removal: a replacement takes its predecessor with it

Written 2026-09-07, first against wisent-ai/stado at 1647e5ba6 while the collapse was still seven branches, and updated the same day once it landed on main as 3570f05a. Every count below names the revision it was read at.

Command surface says what the surface should become: 382 leaf invocations across 52 groups, host alone carrying 93, collapsed into seven capabilities and nineteen primitives. This page is the other half of that work — what leaves the product when a capability lands, what has already left, and what makes any of it stay gone. It exists because the collapse is mostly a deletion, and the deletion is the step that gets postponed: a superseded command left beside its replacement is a second source of truth, and the one nobody calls is the one that rots.

What one capability deletes

A capability is not finished when it works. It is finished when the verbs it replaces are gone in the same change, and that shape is measurable: against main, the integration branch is 104 files, 11,319 insertions and 9,809 deletions. Nearly one deletion per insertion is what a collapse looks like when nothing is kept for later.

Each capability lands as four things, and each one takes its predecessors with it:

Lands Where it goes
the declaration it reads product data compiled into the binary, beside stado-rs/data/service-catalog.json
one CLI module dispatched from stado-rs/src/cli/mod.rs
one Desktop section the Hosts and Services screens, so the GUI gains the capability in the same change, not later
one test area a directory of the crate's integration tests — and the one thing this page got wrong, so see below
and one removal the modules, test areas and Desktop paths of the verbs it replaced

All seven landed on main in 3570f05a. stado-rs/src/cli/coding.rs and stado-rs/src/cli/precheck_runner.rs are deleted, folded into the workload and runner capabilities; stado-rs/src/deploy/host_recovery_release.rs is deleted into declared repair; and the two test areas whose commands no longer exist, stado-rs/tests/forward_close/main.rs and stado-rs/tests/recovery_release/main.rs, are removed with them.

The seven new areas that replaced those two are gone as well, deleted in 3cf0670f the day after they were written. They spawned the real binary and passed, and every one of them stopped at a tempdir and a seeded registry document: none installed a runner, repaired a service, reclaimed a byte, opened a route or delivered a release. A capability therefore does NOT land as four things — it lands as three, and the fourth is owed. What each capability has on main today is a declaration, a CLI module, a Desktop section, a page, and no test evidence. Test suite carries what a real one requires and the disposable host it needs first.

Seven pull requests carry the work, 543 through 549, one capability each, integrated on one branch. They are not independent: stado-rs/src/cli/mod.rs is the one file all seven touch, which is why they merge in an order rather than at once, and why an integration branch exists at all.

Documentation leaves the repository once, and cannot come back

The product repository carries exactly two Markdown files, README.md and CHANGELOG.md. That is not a convention, it is a test: stado-rs/tests/documentation/main.rs reads the tree and refuses operator documentation carried as repository Markdown, and .wisent-release.json declares that same test as one of the three quality steps — fmt, clippy, documentation — that the release worker runs before it builds and that every pull request runs on the change introducing it. This is not theoretical: a test plan written into the repository as Markdown on 2026-09-07 was refused by that step with the verdict introduced, after it had already been merged. The page it should have been is Test suite.

The matching rule on this side is that a page may not name a file that is not there. src/cli/docs-source-paths.mjs resolves the revision the remote actually serves, lists that commit's tree, and refuses any page naming a path the tree lacks — unless the sentence naming it says the path is gone. Both directions are covered: prose cannot outlive the file it describes, and a file cannot be declared dead from a stale worktree, which is exactly how one page came to report a build script as deleted while the repository still served it.

The scripts directory is gone; its code is not

The scripts/ directory was deleted from the product repository in commit 4c7d524f, and scripts/quality_gate.sh went with it. What did not happen is the part that makes a deletion real. The four programs that directory held now live inside .github/workflows/version-check.yml, written to a runner temporary directory by heredoc at run time: the surface reader, the baseline generator, the version gate and the quality gate. Nothing in the product can call them, no test area drives them, and reading one means reading a workflow.

So this strand is unfinished, and this page says so rather than counting the directory's absence as a result. What closes it: release qualification is product behaviour, so it belongs behind a product command with its own test area, and .github/workflows/version-check.yml should call that command the way .github/workflows/deploy.yml already calls the stado CLI. One half is already right — .wisent-release.json holds the quality steps, and the release worker and the pull-request gate execute exactly those argv, so there is one declaration used twice.

What a removal costs, and what nothing charges for

Removing an advertised command is a version event. The version-check job builds the candidate, reads the command surface out of that binary, derives the baseline from the bytes the release channel actually serves, and refuses the change when the declared version does not match the class of the surface change. A verb cannot be dropped quietly.

The limit of that gate decides what the seven capabilities are actually defended by, so it belongs on this page. The gate reads the top-level command list out of one --help. New top-level groups therefore register as an additive change, while the 93 host subcommands disappearing underneath them are invisible to it. What defends those removals is the rest of the contract: one test area per capability driving the real binary, a CLI reference generated from the binary instead of written by hand, and the two documentation checks above. Reading a subcommand count off a green version gate would be reading a check that measures nothing — see Checks that measure nothing.

The release-quality-gate job runs the declared quality steps against the pull request and attributes a refusal: introduced when the step passes on the base and fails here, inherited when the base already fails it, unattributed when the base cannot be checked out for comparison. Attribution matters during a cleanup, because a large deletion attracts the blame for whatever was already broken.

The order, and where it stands

The order is the cost of being wrong, not the size of the change: workload placement first, because an operator reaches for it daily and a wrong host is a silently wasted run; then declared repair, because it runs when something is already broken; then runners, credentials, host space, routing, and host release state.

It landed the same day. The seven branches were superseded by one pull request, #551, merged as 3570f05a: stado host answered 93 subcommands and now answers 22 primitives, each capability reads a declaration compiled into the binary, and a page per capability is published beside this one. The seven test areas came with it, and the host disk, host cleanup, host reclaim, host disk-cleanup and host build-caches verbs are gone because the space capability took them.

What the collapse dropped, and why this page says so

One removal arrived without its replacement, which is the failure this page exists to name, found in the change this page describes. The runner family took repository-add and every runner-groups call with it: on 3570f05a, git grep for runner-groups, repository-add and reconcile_repository under stado-rs/src returns nothing. So nothing in the fleet can now declare which repositories may schedule on a managed runner group, and the runner capability passes a group name only at registration. The cost is not theoretical: weles-landing has sixteen documentation runs queued for a runner it is not permitted to use, and the identity that could grant that permission does not exist — the vault's three GitHub bearers carry read:org, never admin:org.

A capability that consumes a declaration is the right shape; a capability that consumes a declaration nobody can write is the same second source of truth in a new costume. The repair is a declaration for runner-group membership, read by the capability that registers runners, and it belongs to that capability rather than to a returning verb.

Nothing on this page lowers a command count by itself. The count drops when a capability ships and takes its verbs with it — and it only stays dropped when what replaced it can be declared.

Source: this website