Command surface: capabilities, not one verb per incident.
Skarbiec answers 85 leaf invocations across 46 name families, measured at a named revision. 2 of 11 capabilities have landed and took 13 verbs with them; this page names the capability that replaces each remaining family of verbs, the declaration that capability reads, and the order in which the rest land.
Measured, at a named revision#
Measured 2026-09-10 against skarbiec 0.3.2 built from origin/main at 781b1ec6d763dd033be3b3bb10d2243fcbcddabd, sha256 cfe0b4f30cff725d1d9e60f185214162146bee8881fc5e74df01b6fb3d1debf5. The walk is the product's own contract: read the command and group inventories from `skarbiec help`, then walk only declared groups; prove each group closed with its unknown-command refusal, without executing leaf operations.
No number on this page is copied from another page, from a cached corpus, or from memory. Re-run the command below against a binary rebuilt from that revision and the counts reappear; the measurement is checked in at src/content/command-surface-measurement.mjs and the page reads every count from it.
Skarbiec 0.3.2 names groups in root help. The measurement reads that declaration and never executes leaf operations to discover their shape. Older binaries without group metadata are refused. Add --output <file> to keep a separate measurement artifact; the default still updates the module used by this site.
git -C skarbiec fetch origin
git -C skarbiec worktree add "$WORKTREE" 781b1ec6d763dd033be3b3bb10d2243fcbcddabd
cargo build --release --locked --manifest-path "$WORKTREE/Cargo.toml"SKARBIEC_BIN=$WORKTREE/target/release/skarbiec \
SKARBIEC_REVISION=781b1ec6d763dd033be3b3bb10d2243fcbcddabd \
SKARBIEC_MEASURED_AT=2026-09-10 \
npm run measure:command-surfaceSKARBIEC_BIN=<qualified-binary> npm run check:command-surfaceWhat the walk answered#
The advertised inventory carries 66 top-level commands. 3 of them are groups that own a closed sub-inventory — grant with 6, route with 3 and credential with 13 — so the binary answers 85 leaf invocations in total, spread across 46 name families. Only 15 of those families hold more than one leaf, which is the shape this page is about: what is left is still mostly a list of verbs whose common prefix is the only grouping there is.
63 root leaves are counted from the binary's own inventory and are not executed. This includes process interfaces and host operations such as recover-daemons: positional help is not permission to run an operation. Only the 3 declared groups are queried for their subcommands and checked with an unknown-subcommand refusal.
| Family | Leaves | Members |
|---|---|---|
`credential` | 13 | `credential/seal-directory`, `credential/reseal`, `credential/acquire`, `credential/adopt`, `credential/rotate`, `credential/reset`, `credential/verify`, `credential/remove`, `credential/reauth`, `credential/resume`, `credential/resolve-quarantine`, `credential/status`, `credential/declare-endpoint` |
`grant` | 6 | `grant/issue`, `grant/capability`, `grant/ensure`, `grant/list`, `grant/verify`, `grant/revoke` |
`sync` | 5 | `sync-init`, `sync-push`, `sync-pull`, `sync-daemon`, `sync-status` |
`emergency` | 4 | `emergency-grant`, `emergency-cancel`, `emergency-list`, `emergency-activate` |
`audit` | 3 | `audit`, `audit-query`, `audit-epoch-start` |
`bond` | 3 | `bond-add`, `bond-list`, `bond-remove` |
`policy` | 3 | `policy-set`, `policy-get`, `policy-check-length` |
`route` | 3 | `route/resolve`, `route/declare`, `route/verify` |
`acquisition` | 2 | `acquisition-request`, `acquisition-read` |
`donation` | 2 | `donation-accept`, `donation-reject` |
`migrate` | 2 | `migrate`, `migrate-v2` |
`recovery` | 2 | `recovery-status`, `recovery-drill` |
`restore` | 2 | `restore`, `restore-version` |
`set` | 2 | `set`, `set-json` |
`totp` | 2 | `totp`, `totp-seed-state` |
The 85 leaves are 11 capabilities and 12 primitives#
Counted from the walk, not sorted by hand: 73 of the 85 leaves belong to a family that a declaration already describes, and the remaining 12 are primitives. Every command name in the table below appears in the measured leaf list, and every verb a landed capability deleted is absent from it; the repository check refuses the page otherwise.
| Capability | State | Leaves it accounts for | Reads |
|---|---|---|---|
**Declared consumer grant** (6) | Landed at f582762 | `grant/issue`, `grant/capability`, `grant/ensure`, `grant/list`, `grant/verify`, `grant/revoke` | the capability grammar the v2 validator already enforces — an action, one item, an optional exact field, and the workload public key an acquire grant requires |
**Declared route resolution** (3) | Landed at 781b1ec | `route/resolve`, `route/declare`, `route/verify` | what an item itself declares — the registered brama:provider: and brama:id: tags, an internal-authority item’s own id and agent_auth_secret fields, and a login kind’s declared fields — plus the hand-declared table, for the one resource no item can state |
**Declared credential operation** (13) | Pending | `credential/acquire`, `credential/adopt`, `credential/rotate`, `credential/reset`, `credential/verify`, `credential/remove`, `credential/reauth`, `credential/resume`, `credential/resolve-quarantine`, `credential/seal-directory`, `credential/reseal`, `credential/status`, `credential/declare-endpoint` | the lifecycle's own declaration, which the binary already prints beside this group: the item states, the per-provider effects, and the sealed directory contract's kind |
**Declared replication** (14) | Pending | `bond-add`, `bond-list`, `bond-remove`, `sync-init`, `sync-push`, `sync-pull`, `sync-daemon`, `sync-status`, `pull`, `enroll`, `donate`, `donations`, `donation-accept`, `donation-reject` | the bond declaration each of these verbs otherwise restates by hand — mode, role, channel, peers, donation policy, and interval |
**Declared item write** (8) | Pending | `set`, `set-json`, `retag`, `rename`, `backfill-item-uids`, `import`, `migrate`, `migrate-v2` | the canonical skarbiec.item.v2 kind declaration, the registered tag namespaces the binary refuses an unregistered write against, and the schema version that says which migration an envelope still owes |
**Declared item state** (5) | Pending | `delete`, `restore`, `purge`, `restore-version`, `reclaim` | the envelope's own state, revision history and management authority — the vault already records which transition is legal and who may make it |
**Declared recipient set** (5) | Pending | `add-user`, `rotate-owner`, `share`, `revoke`, `users` | the recipient group every ciphertext already carries: owner, recovery, and each shared user |
**Declared recovery custody** (7) | Pending | `export-key`, `recovery-status`, `recovery-drill`, `emergency-grant`, `emergency-cancel`, `emergency-list`, `emergency-activate` | the recovery recipient every item declares, and the time-delayed grant's own declared moment and trusted user |
**Declared secret policy** (5) | Pending | `policy-set`, `policy-get`, `policy-check-length`, `generate`, `breach-check` | the stored policy declaration, which generation and breach checking each re-state today instead of consuming |
**Declared journal** (4) | Pending | `audit`, `audit-query`, `audit-epoch-start`, `verify-chain` | the journal's own declared shape: epoch, linkage, digest, and the WORM receipt directory |
**Declared second factor** (3) | Pending | `totp`, `totp-seed-state`, `apple-challenge-put` | the login kind's declared totp_secret and recovery_codes fields — the schema already says which items can carry a second factor |
Declared consumer grant and Declared route resolution: landed, with the verbs they replaced deleted#
At f582762 this surface answered 88 leaves from 68 advertised commands across 48 families, with 3 groups. At 781b1ec it answers 85 leaves from 66 advertised commands across 46 families, with 3 groups. The difference is declared route resolution arriving as three leaves and the six verbs it replaced leaving in the same change.
token-mint, token-ensure-read, token-revoke, token-verify, tokens, capability-issue, invite, routes/list, routes/add, routes/reconcile, routes/verify, resolve and expand are gone: not deprecated, not aliased, and not answering with a notice. `skarbiec help` advertises none of them, no dispatch arm carries them, the loopback routes they backed now name the group — `/v1/operator/route/{resolve,verify,declare}` — and the tests, examples and command pages that named them were moved or deleted with them.
Two of the six are not replaced by a leaf at all. `invite` existed only to print an acquisition redemption contract beside a grant it minted anyway, so `grant issue` answers with that contract for every workload-bound coordinate it declares. `routes reconcile` existed only because resolution could not read a declaration: rows were derived from the vault ahead of time and went stale the moment an item was renamed, so verification reported a working credential as missing and reconciling again added nothing, because the resource was already in the table. Resolution reads the declaration when it is asked, so there is nothing left to derive.
| Leaf | Replaces | What it answers |
|---|---|---|
`grant issue` | `token-mint`, `invite` | the declaration, its rotated bearer, and for an acquire grant the redemption contract instead of a bearer |
`grant capability` | `capability-issue` | one bounded, use-counted redemption of a declaration that already exists |
`grant ensure` | `token-ensure-read` | one exact field read added to a direct grant, with the bearer unrotated |
`grant list` | `tokens` | every declaration as metadata, never a bearer or a workload public key |
`grant verify` | `token-verify` | one exact action, item and field question about one presented bearer |
`grant revoke` | `token-revoke` | the whole declaration withdrawn, idempotently |
`route resolve` | `routes list`, `resolve`, `expand`, `routes reconcile` | which item and field a name reaches, read from the item’s own declaration, and with `--emit` or `--template` the values behind it |
`route declare` | `routes add` | the one route a vault item cannot declare for itself, refused for any name an item already declares |
`route verify` | `routes verify` | every route this vault answers, confronted with the vault, non-zero when one cannot serve a usable credential |
The primitives that stay, and why each is irreducible#
A primitive answers one fact, or performs one act, that no declaration can express and no capability above can consume. Each of these would still exist after every capability lands.
- init — creates the vault and its owner and recovery keys; there is nothing to declare against yet
- get — the one read of a decrypted field, and the boundary every capability above is measured against
- list — metadata only, and the only way to see an item that no declaration reaches
- status — vault path and non-sensitive counts, answerable with no grant and no declaration
- doctor — confronts vault, chain, endpoint, receipts and grants with the world; a declaration cannot check itself
- key-doctor — answers whether any key on this machine still opens the vault, reading vault and keyring directly while the service is down
- vaults — enumerates the vaults on this host, which is host state rather than vault state
- acquisition-request — verifies the workload proof and issues the one-use bearer
- acquisition-read — consumes that bearer once; issue and redemption are irreducibly two calls because the bearer crosses a process boundary between them
- capability-serve — serves the capability socket; a served process is a surface, not a verb
- recover-daemons — repairs the gpg agents the cryptography depends on, below every declaration in this product
- version — names the binary a measurement is bound to
Where the published corpus and the binary disagree#
Of 85 leaves, 2 have no prose page: recover-daemons and totp-seed-state. In the other direction, 4 documented command pages carry no leaf. grant, route and credential are the 3 groups' own overview pages, which is correct. onboarding is not: the advertised inventory does not carry it, and the binary's own source calls a dispatchable command absent from that list private, so the corpus publishes a page for a command no caller may be told to rely on.
The loopback broker, the MCP server and the native-messaging bridge are described in the CLI reference's prose and have no command page at all, because the advertised inventory carries no command for any of them. A surface documented in prose but absent from the machine-readable inventory is the same defect as a verb with no page, arriving from the other side.
The removal rule#
A capability lands with the declaration it reads, every caller moves to it, and the verbs it replaces are deleted in the same change. A superseded command left beside its replacement is a second source of truth: it keeps making a promise after the code that honoured it is gone, and the one nobody calls is the one that rots.
Skarbiec has already paid for a half-finished move. On 2026-08-25 the repository deleted 34 documentation files as the delete half of a move to this site; the publish half was reverted four minutes later and nobody undid the delete, so a contract other systems pointed at was destroyed rather than moved, and it took until 2026-08-30 to restore. Same rule, both directions: the replacement and the removal are one change, or neither is.
Migration order, and the reason for that order#
The order follows the cost of being wrong, not the size of the group. The first two steps are done; the numbers above are measured after them.
- Declared consumer grant, first, because every other capability is authorized by it. A grant issued wider than intended is a standing over-permission that no later capability can narrow, and re-issuing a capability set already rotates the bearer before the operator sees the result. Landed at f582762: `grant issue`, grant capability, grant ensure, grant list, grant verify and grant revoke answer, and the seven verbs they replaced are gone from the walk.
- Declared route resolution, next, because a route decides which item and field a resource name resolves to. Wrong here serves the wrong credential under the right name, and that is exactly where meaning was still read out of item id strings: a rename changed behaviour with no error raised. Landed at 781b1ec: route resolve, route declare and route verify answer, resolution reads the brama:provider: and brama:id: tags, an internal-authority item’s own fields and a login kind’s declared fields, and the table it keeps holds only the resource no item can state.
- Declared credential operation, third: thirteen verbs that change state at a real provider, and they run when something is already being repaired. The declaration they should read — item states and per-provider effects — is already printed beside them.
- Declared replication, fourth: fourteen verbs that move ciphertext between vaults, where being wrong diverges two vaults quietly instead of failing loudly.
- Declared item write and declared item state, fifth, the daily surface. It is not first because trash, revision history and the recovery recipient make most mistakes here recoverable.
- Declared recipient set and declared recovery custody, sixth. Rarely run, and each is proved by a drill against a real key rather than by the count of verbs that spell it.
- Declared secret policy, journal and second factor, last: read-mostly, observable, and none of them holds a value another capability needs.
What makes a new verb correct#
A new command is right when no declaration can express the thing and no existing capability can consume it. Every family in the table above fails that test: each names a shape, a schema era, a provider, a transport or an incident, and each would have been a row in a declaration this product already carries and already enforces on write.
Nothing here promises the leaf count drops on its own. It drops when a capability ships and takes its verbs with it, in the same change.