Diataxis category: reference.
This document is the command contract for the single user-facing
d2b entry point. It covers the CLI surfaces that are fully
owned in Rust, including the read-only and daemon-backed commands
that go through d2bd.
Examples use the smoke topology from the Layer-1 fixtures: one workload
VM (corp-vm) and one auto-declared net VM (sys-work-net) in the
work environment. Human examples are representative snapshots rather
than literal byte-for-byte goldens unless the corresponding
tests/golden/cli-output/* fixture has landed.
There is no bash fallback. The Rust CLI never executes bash, and the no-bash invariant is enforced by
tests/no-bash-exec-eval.sh. Verbs that used to degrade to bash onnot-yet-implementedordaemon-downnow surface typed envelopes (not-yet-implementedexit 78,daemon-downexit 1) — seeerror-codes.md§ "Remediation rendering conventions" for the multi-line block format used on those envelopes.d2b up/down/restart/listare first-class top-level aliases forvm start/stop/restart/listand route through the same daemon path. Stop-like aliases accept the same--force/-fflag as the correspondingvmcommand.
- Command headings use the dispatched leaf form (
keys list,audio mic,status --check-bridges) because disposition is assigned at that granularity. --jsonalways means: emit one newline-terminated JSON document on stdout and keep progress or warnings on stderr.- Non-zero rows link into
error-codes.mdonly when the failure is part of the stable typed-error model. Success and raw POSIX signal exits are listed inline without a docs anchor. - There is no bash CLI fallback. Commands marked
retired-bashreturn a typed exit-78 envelope when invoked; commands markedrust-native shimown help / argument parsing in Rust but still return the same typed exit-78 envelope where their daemon-native backends are not yet shipped; commands markedrust-nativestay on the daemon/public-socket or native planner path.
Realm-aware target strings use the canonical form
<workload>.<realm>[.<ancestor>...].d2b. Bare workload names are aliases only
when a caller supplies an alias table or default realm, and old node-qualified
forms are diagnostics that point to the realm-native target with the node label
removed. See Realm access resolver contract for
the target grammar, direct host-local socket binding, capability preflight, and
typed denial shapes.
VM lifecycle and arbitrary exec remain VM-only. A trusted unsafe-local target is
rejected before those handlers can coerce its workload id to a VM name.
Provider-neutral list, status, and configured launch use the workload operation
family on the local d2bd public socket.
The provider-neutral command is:
d2b launch <canonical-target> [--item <item-id>]
The public request carries only the canonical target, configured item id, and
an idempotency operation id. It never carries argv, uid, environment, cwd,
display paths, process ids, or unit names. An exec item dispatches through the
selected provider. A local-VM shell item dispatches existing persistent-shell
semantics. An unsafe-local shell item requires unsafe-local-shell-v1 and
invokes d2b shell with the workload's canonical target; there is no host-shell
or SSH fallback. When --item is omitted, the CLI selects defaultItem, then
an only item, otherwise returns the available item ids and names.
For local-VM exec items, d2bd derives an opaque guest exec id from the authenticated requester, operation id, target, and item id. Guestd persists that id with the detached exec record, so replay after a daemon restart returns the existing exec instead of spawning a duplicate. A replay whose trusted argv hash does not match fails closed.
The DTOs remain protocol version 3 and are gated by configured-launch-v1.
Unsafe-local additionally requires unsafe-local-provider-v1; shell items also
require unsafe-local-shell-v1. Unsupported peers return an update remediation
and never fall back.
Exit codes
| Code | Meaning |
|---|---|
0 |
Launch committed or was already committed for the operation id. |
2 |
Target/item not found, or omitted item is ambiguous. |
31 / 75 |
Caller lacks launcher/admin authority or the operation is temporarily busy. |
69 |
Provider prerequisite or transport unavailable. |
70 |
Capability unavailable, provider mismatch, or required feature/version skew. |
76 |
Protocol response or operation-id conflict. |
Synopsis: d2b launch <TARGET> [--item <ITEM>] [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--item <ITEM> |
string | unset | Select the configured launcher item id. When omitted, use defaultItem, then a sole item, otherwise return the available ids and names. |
--json |
boolean | false |
Emit the stable machine-readable launch result on stdout. |
--human |
boolean | false |
Force the human launch confirmation on stdout. |
Arguments
| Argument | Semantics |
|---|---|
TARGET |
Canonical workload target or an unambiguous workload id. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Launch committed or was already committed. | — |
2 |
Target/item not found, or omitted item is ambiguous. | usage |
31 / 75 |
Caller lacks launcher/admin authority or the operation is temporarily busy. | workload launch error |
69 |
Provider prerequisite or transport unavailable. | workload launch error |
70 |
Capability unavailable, provider mismatch, or required feature/version skew. | workload launch error |
76 |
Protocol response or operation-id conflict. | workload launch error |
Human example
$ d2b launch tools.host.d2b --item browser
launched tools.host.d2b item browser (committed)
--json example — schema: launch.schema.json.
{
"command": "launch",
"target": "tools.host.d2b",
"itemId": "browser",
"operationId": "launch-1234-5678",
"disposition": "committed"
}Synopsis: d2b list [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the stable machine-readable inventory document on stdout instead of the human table. |
--human |
boolean | false |
Force the human inventory table on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | The inventory query is always global; it does not accept a VM selector. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
Unexpected daemon reply, local probe, or manifest-read failure. | generic |
2 |
Unknown flag or unsupported invocation shape. | usage |
Human example
$ d2b list
NAME ENV GRAPHICS TPM USBIP STATIC_IP STATUS
corp-vm work false false false 10.20.0.10 running
sys-work-net work false false false 192.0.2.1 running (net-vm)
--json example — schema: list.schema.json; prose companion: list.md.
[
{
"name": "corp-vm",
"env": "work",
"graphics": false,
"tpm": false,
"usbip": false,
"staticIp": "10.20.0.10",
"status": "running",
"isNetVm": false,
"guestClosureOutPath": "/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-nixos-system-corp-vm"
},
{
"name": "sys-work-net",
"env": "work",
"graphics": false,
"tpm": false,
"usbip": false,
"staticIp": "192.0.2.1",
"status": "running",
"isNetVm": true,
"guestClosureOutPath": "/nix/store/ffffffffffffffffffffffffffffffff-nixos-system-sys-work-net"
}
]Status
list is a daemon-native, read-only inventory query. When d2bd is
reachable, the CLI queries the public socket and reports declared VM
metadata, daemon-derived lifecycle state, and the VM guest closure out
path when closure metadata is available. For a running VM with
status = "pending-restart", guestClosureOutPath points at the
booted closure so scanners inspect the running guest generation. If the
public socket is unavailable or does not support the request, the CLI
falls back to the static manifest/local status path and may still
populate guestClosureOutPath when the caller can read local closure
metadata.
Native
- Pure read-only public-socket inventory query; no broker op and no guest contact. The static manifest/local status path is fallback only.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Rows are ordered by VM name because the historical bash implementation iterated jq keys[]; the current daemon-native path keeps that ordering contract.
Synopsis: d2b clipboard arm [--human | --json]
Opens the d2b clipboard picker for the current host-focused Niri target.
After the operator selects an item, d2b-clipd publishes that item as a
d2b-owned host selection and triggers paste replay for the focused target.
If the picker cannot be launched or its handshake fails, d2b-clipd reports a
typed failure instead of silently writing clipboard data.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit one JSON response on stdout. Failures are structured as { "ok": false, "error": "<bounded-message>" }. |
--human |
boolean | false |
Force human text on stdout. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
The picker opened, or picker launch/handshake failed and d2b-clipd successfully armed the native paste fallback. |
— |
2 |
The control socket was unavailable, malformed, timed out, or returned a daemon error. | usage |
The CLI connects to $XDG_RUNTIME_DIR/d2b-clipd/clipd.sock, sends one bounded
arm request, and applies five-second read and write deadlines to the
control socket so a wedged d2b-clipd cannot hang the terminal. The
daemon owns all clipboard state and transfer FDs; this command only asks
the daemon to open the picker for d2b-owned paste replay.
Human examples
$ d2b clipboard arm
picker opened
$ d2b clipboard arm
picker_not_configured
--json examples
{ "ok": true, "message": "picker opened" }{ "ok": false, "error": "failed to connect to clipboard daemon: No such file or directory (os error 2)" }Native
- Rust-native local user-session control-socket request to
d2b-clipd. No broker op, no guest contact, no shell-out, no synthetic input, and no clipboard payload or transfer FD crosses the CLI boundary.
Synopsis: d2b realm enter <realm>
Enters the local gateway VM for a gateway-backed realm by opening an
interactive vm exec session to the gateway workload. The host resolves
the realm through the generated realm entrypoint table and verifies the
gateway VM is declared and running before attempting the exec. Realm
relay/provider credentials remain inside the gateway guest.
Synopsis: d2b realm list [--human | --json]
Lists rendered local realm entrypoints. The output reports each realm's mode
(host-resident or gateway-backed), gateway VM when present, local gateway
lifecycle state, credential boundary, and default-deny cross-realm policy.
Synopsis: d2b realm inspect <realm> [--human | --json]
Inspects one realm entrypoint using the same bounded fields as realm list.
Unknown realms fail closed with the same actionable missing-entrypoint envelope
used by routed VM targets.
Synopsis: d2b op inspect [--trace-id <id> --span-id <id>] [--human | --json]
Inspects current local constellation operation state without making the host a global telemetry owner. The command reports bounded local VM/gateway counts, configured realm states, optional trace context, and degraded partial results for unavailable gateways or sinks. It never falls back to SSH, host-held realm credentials, or generic tunnels.
Synopsis: d2b realm run <realm> -- <argv...> [--human | --json]
Runs a one-shot command inside the local gateway VM for a
gateway-backed realm. This is the low-level escape hatch for scripts that
need to issue an exact command in the realm trust boundary. Daily
workload operations should continue to use d2b vm ...; realm
targets route through the configured gateway entrypoint when supported.
The realm target grammar is
<workload>.<realm>[.<ancestor>...].d2b. Bare local VM names stay on the
existing host fast path until the runtime/Nix cutover lands. Fully qualified
realm targets must resolve through the realm access layer; missing entrypoints
fail closed with an actionable missing-realm-entrypoint error rather than
falling back to SSH or a generic tunnel.
d2b vm list --realm <realm> runs d2b vm list inside the
realm gateway through the same local guest-control exec path. It does not
make the host persist a remote node/workload registry.
Synopsis:
d2b vm display list [--target <workload>.<realm>.d2b] [--human | --json]d2b vm display close <session-id> [--human | --json]
vm display manages active gateway display sessions. It requires the
gateway daemon's public socket and does not fall back to SSH or host-side
Wayland setup. list returns only bounded non-secret session metadata:
session id, realm target, lifecycle state, authorizing operation id, and
principal. For launcher-role callers, list returns only sessions owned by
the caller's local socket uid and close can tear down only those sessions;
admin callers can inspect or close any active display session. close asks
the gateway daemon to tear down the listener plus the provider-side display
agent when the session is still active, and reports closed = false for an
already-absent session.
--json for list emits:
{
"command": "vm display list",
"target": "demo.work.d2b",
"sessions": [
{
"sessionId": "s0",
"target": "demo.work.d2b",
"state": "running",
"operationId": "gw-exec-1",
"principal": "uid-1000"
}
]
}The response never contains app argv, socket paths, relay endpoints, credentials, file descriptors, pidfds, cgroup paths, namespace ids, or process output. See display and virtual I/O capabilities for the capability boundary.
Synopsis: d2b vm start <vm> [--dry-run | --apply] [--human | --json]
Status
vm start is a daemon-native headless-lifecycle verb. If neither
mutation flag is set, stderr emits d2b: NOTICE: defaulting to --dry-run and the CLI defaults to --dry-run; --apply routes
through the daemon.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the 5-node per-VM DAG without spawning any role. |
--apply |
boolean | false |
Perform the lifecycle mutation. |
--json |
boolean | false |
Emit the dry-run DAG or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Typed broker-error or not-yet-implemented. |
broker-error, not-yet-implemented |
There is no bash fallback. Daemon-unreachable returns daemon-down
(exit 1), and the old d2b up exit table is preserved in this
file as history.
Human example
$ d2b vm start corp-vm --apply
vm start corp-vm: spawned pid=4242 start_time_ticks=123456789
Native
--apply: routes throughd2bd→ broker. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78.D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1are unrecognised. Broker failures surface on stderr with the redacted public-safe remediation and exit78.- Live path:
d2bd→ brokerSpawnRunner.
Bash
- There is no bash execution path for this verb.
Synopsis: d2b vm stop <vm> [--force | -f] [--dry-run | --apply] [--human | --json]
Status
vm stop is a daemon-native headless-lifecycle verb. If neither
mutation flag is set, stderr emits d2b: NOTICE: defaulting to --dry-run and the CLI defaults to --dry-run; --apply routes
through the daemon.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the 5-node per-VM DAG without spawning any role. |
--apply |
boolean | false |
Perform the lifecycle mutation. |
--force, -f |
boolean | false |
Skip provider-aware graceful guest shutdown and begin the standard SIGTERM/SIGKILL VMM cleanup path. This is not an immediate SIGKILL shortcut. |
--json |
boolean | false |
Emit the dry-run DAG or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Typed broker-error or not-yet-implemented. |
broker-error, not-yet-implemented |
There is no bash fallback. Daemon-unreachable returns daemon-down
(exit 1), and the old d2b down exit table is preserved in this
file as history.
Normal vm stop --apply asks supported local providers to shut the guest
down before host-side VMM cleanup: Cloud Hypervisor receives
PUT /api/v1/vm.shutdown, and qemu-media receives broker-mediated QMP
system_powerdown. The wait is bounded by
d2b.daemon.lifecycle.gracefulShutdown.timeoutSeconds or the per-VM
manifest override. Human output prints progress such as
Waiting for guest to shut down (up to 90s)...; the maximum command wait is
that graceful timeout plus the standard forced-cleanup signal windows.
--force / -f bypasses only the graceful wait.
Pidfd EPERM while stopping a per-VM-UID runner used to surface as
typed broker-error exit 78. Current --apply recovers that specific
case by asking the broker to run SignalRunner; if the broker reports
signaled=true, vm stop exits 0. True broker failures — unreachable
broker, dispatch errors, unexpected responses, or signaled=false —
still surface as broker-error / exit 78.
Human example
$ d2b vm stop corp-vm --apply
Waiting for guest to shut down (up to 90s)...
vm stop corp-vm: clean guest shutdown
Native
--apply: routes throughd2bd→ provider graceful shutdown (when enabled) → broker cleanup as needed. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78.--force/-f: available on bothvm stopand the top-leveldownalias. It is an explicit stop override, not a shortcut around the existing forced cleanup policy.D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1are unrecognised. Broker failures surface on stderr with the redacted public-safe remediation and exit78.- Live path:
d2bd→ CH API or broker-mediated QMP for guest shutdown → brokerSignalRunner/ cgroup cleanup fallback.
Bash
- There is no bash execution path for this verb.
Synopsis: d2b vm restart <vm> [--force | -f] [--dry-run | --apply] [--human | --json]
Status
vm restart is a daemon-native headless-lifecycle verb. If neither
mutation flag is set, stderr emits d2b: NOTICE: defaulting to --dry-run and the CLI defaults to --dry-run; --apply routes
through the daemon.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the 5-node per-VM DAG without spawning any role. |
--apply |
boolean | false |
Perform the lifecycle mutation. |
--force, -f |
boolean | false |
Apply force only to the stop phase: skip graceful guest shutdown, then run the usual cleanup before the unchanged start phase. |
--json |
boolean | false |
Emit the dry-run DAG or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Typed broker-error or not-yet-implemented. |
broker-error, not-yet-implemented |
There is no bash fallback. Daemon-unreachable returns daemon-down
(exit 1), and the old d2b restart exit table is preserved in
this file as history.
Human example
$ d2b vm restart corp-vm --apply
vm restart corp-vm: vm stop corp-vm: clean guest shutdown; vm start corp-vm: spawned pid=4242 start_time_ticks=123456789
Native
--apply: routes throughd2bd→ broker. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78.--force/-f: available on bothvm restartand the top-levelrestartalias. It affects only the stop phase.D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1are unrecognised. Broker failures surface on stderr with the redacted public-safe remediation and exit78.- Live path: same as
vm stopfor the stop phase, then brokerSpawnRunnerfor the start phase.
Bash
- There is no bash execution path for this verb.
Synopsis: d2b vm list [--human] [--json]
Status: vm list is the daemon-side runtime inventory surface. It
queries d2bd's public socket and returns the same live lifecycle/runtime
entries the daemon exposes to desktop clients. If the public socket is not
available, the command exits successfully with an empty entries array plus
a note explaining that d2bd must be started or restarted.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the daemon runtime inventory document on stdout. |
--human |
boolean | false |
Force the human runtime inventory table on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Inventory is global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
Unexpected local JSON serialization failure. | generic |
2 |
Unknown flag or unsupported invocation shape. | usage |
Human example
$ d2b vm list
VM STATE RUNTIME
corp-vm running running
--json example
{
"command": "vm list",
"entries": [
{
"vm": "corp-vm",
"name": "corp-vm",
"env": "work",
"graphics": false,
"isNetVm": false,
"lifecycle": {
"pendingRestart": false,
"state": "Running"
},
"runtime": {
"detail": "running"
},
"services": {
"gpu": null,
"microvm": "running",
"d2b": "active",
"snd": null,
"swtpm": null,
"video": null,
"virtiofsd": "running"
},
"sshUser": "alice",
"staticIp": "10.20.0.10",
"tpm": false,
"usbip": false
}
]
}When d2bd's public socket is unavailable, --json returns:
{
"command": "vm list",
"entries": [],
"notes": "vm list requires d2bd's public socket; start or restart d2bd and retry."
}Current disposition: rust-native — the Rust CLI owns the stable
daemon-side runtime-view contract and reads it from d2bd's public socket.
Synopsis: d2b status [<vm>] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the structured status document on stdout. |
--human |
boolean | false |
Force the human status view on stdout. |
--vm |
string | null |
Long-form VM selector equivalent to passing <vm> positionally. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Optional VM name. When omitted the human command falls back to the global inventory view and appends the bridge-health table. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
Unexpected probe failure. | generic |
2 |
Unknown flag, unsupported --json shape, or unknown VM. |
usage |
Human example
$ d2b status corp-vm
=== corp-vm ===
daemon: inactive
backend-runner: inactive
virtiofsd: inactive
gpu-runner: stopped
sshd@10.20.0.10: unreachable
pending-restart: no
=== Bridge health ===
BRIDGE STATE ADMIN EXPECTED RESULT
br-work-lan DOWN up NO-CARRIER no-carrier (no workloads up)
br-work-up DOWN up NO-CARRIER no-carrier (net VM stopped)
--json example — schema: status.schema.json; prose companion: status.md.
{
"name": "corp-vm",
"env": "work",
"services": {
"d2b": "inactive",
"microvm": "inactive",
"virtiofsd": "inactive",
"gpu": null,
"video": null,
"snd": null,
"swtpm": null
},
"current": null,
"booted": null,
"pendingRestart": false,
"runtime": "unknown",
"declaredRoles": [
"host-reconcile",
"store-virtiofs-preflight"
],
"readiness": []
}Status
status is a read-only daemon RPC, including the frozen per-VM JSON
shape. A negotiated guest-control state field is reserved for a future
release and is not present in the current frozen shape; it must never
appear as an ad hoc unversioned key.
Native
- Read-only daemon query; renders the human view or the frozen
--jsondocument.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b status --check-bridges
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--check-bridges |
boolean | false |
Switch status into bridge-only mode. This form rejects --json and does not accept a VM argument. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Bridge-only mode is always global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Every declared bridge is in the expected healthy state for the current VM topology. | — |
2 |
Unsupported combination such as --json, extra arguments, or an unknown flag. |
usage |
4 |
A bridge is missing, administratively down, or lacks carrier when carrier is required. | bridge-unhealthy |
Human example
$ d2b status --check-bridges
=== Bridge health ===
BRIDGE STATE ADMIN EXPECTED RESULT
br-work-lan DOWN up NO-CARRIER no-carrier (no workloads up)
br-work-up DOWN up NO-CARRIER no-carrier (net VM stopped)
Status
The bridge-health probe is part of the read-only status surface, even though reconcile remains deferred.
Native
- Read-only bridge-health probe; rejects
--jsonand a VM selector.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b usb attach <vm> <busid> [--dry-run | --apply] [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | false |
Print the daemon → broker USBIP attach plan plus the authenticated guestd import step without mutating host or guest state. |
--apply |
boolean | false |
Ask d2bd to run three fail-closed pre-flight checks (sysfs presence, USB-capable gate, active claim exclusivity), then dispatch the appropriate broker path: declared path (when a static bundle intent exists for the busid — UsbipBind + firewall carve-out + UsbipProxyReconcile), or explicit path (when no declared intent exists — UsbipExplicitFirewallRule + UsbipExplicitBind per-device ops), then ask guestd over authenticated guest-control to import the selected busid. |
--json |
boolean | false |
Emit the dry-run summary as structured JSON. |
--human |
boolean | false |
Force the human dry-run summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. |
busid |
Required host USB busid in the canonical B-P[.P...] form (for example 1-2 or 2-1.4). Does not require the busid to be pre-declared in the NixOS bundle configuration. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
d2bd is unreachable, or the daemon returned a non-typed USBIP failure. |
daemon-down |
2 |
Missing VM / busid or another usage error. | usage |
67 |
The USB device busid is not present in sysfs (usbip-busid-not-present), or another VM already holds an active claim on this busid (usbip-explicit-claim-conflict). |
usbip-busid-not-present, usbip-explicit-claim-conflict |
78 |
The daemon reached the broker but the native USBIP apply path was refused. | broker-error |
Human example
$ d2b usb attach corp-vm 1-2 --dry-run
d2b usb attach --dry-run: would bind and lock, apply the USBIP firewall carve-out, ensure the per-env backend/proxy for busid '1-2' for vm 'corp-vm', reconcile the USBIP proxy, and ask guestd to import the device
Explicit attach (present-busid, no static allowlist required)
d2b usb attach accepts any USB device that is physically present in sysfs
without requiring a static busid or vendor allowlist in the NixOS configuration.
The daemon selects the explicit path when no declared bundle intent exists for
the requested busid. Three fail-closed checks run before any broker call:
- Sysfs presence — the daemon checks
/sys/bus/usb/devices/<busid>/idVendor. If absent, the attach fails withUsbipBusidNotPresent(exit 67) and guides the operator to plug in the device before retrying. - USB-capable gate — the VM must have
RuntimeCapabilityGate::UsbHotplugdeclared in its manifest. Non-USB-capable VMs fail with a typedRuntimeCapabilityUnsupportederror. - Active claim exclusivity — the daemon reads the OFD lock under
/run/d2b/locks/usbip/<busid>. If another VM already holds the claim, the attach fails withUsbipExplicitClaimConflict(exit 67) naming the owner VM and guiding the operator to detach from the owner first.
The explicit path dispatches UsbipExplicitFirewallRule (env-scoped nftables
rule keyed on the per-env uplink IPs) and UsbipExplicitBind (per-device backend
setup). Both ops are currently typed stubs; the live per-device backend handler
is deferred to later implementation. The declared path (static bundle intents) is
unaffected.
Status
The native CLI sends one intent to d2bd; the daemon drives broker host
USBIP state and authenticated guestd import cleanup/attach over guest-control.
If the target VM is stopped, --apply fails before host mutation with an
actionable usage error: start the VM with
d2b vm start <vm> --apply, wait until it is running, then retry
d2b usb attach <vm> <busid> --apply. This preflight does not create a
degraded USB state. If an earlier failed apply left a stale or bound USBIP
session claim, start the VM and rerun the attach or run
d2b usb detach <vm> <busid> --apply; the attach/detach paths and
d2b usb probe all run the USBIP proxy reconcile pass, and usb probe
shows the session claim as cleared once the lock/proxy state is consistent.
Prerequisites for --apply are: the target VM is running and guest-control
advertises USBIP status/import, the bundle declares a USBIP bind/firewall intent
for the VM/busid, policy/topology checks allow the physical device, the
usbip-host module and per-env backend/proxy carrier can be prepared, and no
other owner holds the busid session claim. Failing prerequisites surface as
typed errors or as d2b usb probe degraded reasons with exact remediation
commands.
Native
--applyroutes throughd2bd→ broker + guestd. There is no SSH fallback for USBIP.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b usb detach <vm> <busid> [--dry-run | --apply] [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | false |
Print the daemon → broker USBIP unbind plan without mutating host state. |
--apply |
boolean | false |
Ask d2bd to run UsbipUnbind followed by UsbipProxyReconcile for the selected VM/busid pair. |
--json |
boolean | false |
Emit the dry-run summary as structured JSON. |
--human |
boolean | false |
Force the human dry-run summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. |
busid |
Required host USB busid in the canonical B-P[.P...] form. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
d2bd is unreachable, or the daemon returned a non-typed USBIP failure. |
daemon-down |
2 |
Missing VM / busid or another usage error. | usage |
78 |
The daemon reached the broker but the native USBIP apply path was refused. | broker-error |
Human example
$ d2b usb detach corp-vm 1-2 --dry-run
d2b usb detach --dry-run: would ask guestd to detach busid '1-2' for vm 'corp-vm', unbind it on the host, and reconcile the USBIP proxy
Status
The native CLI first asks guestd to detach matching imports, then drives the
daemon → broker UsbipUnbind / UsbipProxyReconcile path. Explicit detach is
the only normal path that releases a USBIP session claim. VM stop/restart keeps
the claim for the same VM within the current host boot/session so restart
reconciliation can re-import the device; a host reboot clears the /run lock.
Single-busid detach never stops the shared per-env proxy. If the daemon cannot
prove firewall-withdrawal-before-flow-kill ordering plus an exact VM/proxy
cleanup tuple, --apply fails closed with usbip-revocation-not-isolated and
preserves the session claim for manual drain/recovery. The public error names the
target busid. The safe next step is to stop the VM so the stream drains, then
rerun d2b usb detach <vm> <busid> --apply.
Native
--applyroutes throughd2bd→ brokerUsbipUnbindthenUsbipProxyReconcile.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b usb probe [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the structured USBIP probe inventory instead of the human table. |
--human |
boolean | false |
Force the human USBIP probe table on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | The probe always lists every daemon-declared USBIP busid session claim. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
d2bd is unreachable or does not expose the native USBIP probe request. |
daemon-down |
2 |
Unsupported invocation shape. | usage |
78 |
The daemon reached the broker but the UsbipProxyReconcile pass failed. |
broker-error |
Human example
$ d2b usb probe
VM ENV BUSID STATUS SESSION-CLAIM HOST-BIND CARRIER PROXY GUEST POLICY
corp-vm work 1-2 degraded held-by-desired-owner unknown unknown unknown detached allowed
degraded guest-import-unavailable: the guest USBIP import has not converged
remediation: Run `d2b usb attach corp-vm 1-2 --apply` after the VM is running.
command: d2b usb attach corp-vm 1-2 --apply
--json example — schema: usb-probe.schema.json; prose companion: usb-probe.md.
{
"command": "usb probe",
"entries": [
{
"vm": "corp-vm",
"env": "work",
"busId": "1-2",
"lockPath": "/run/d2b/locks/usbip/1-2",
"status": "degraded",
"ownerVm": "corp-vm",
"durableClaim": {
"state": "held-by-desired-owner",
"ownerVm": "corp-vm"
},
"host": {
"bind": "unknown",
"carrier": "unknown",
"proxy": "unknown"
},
"guest": {
"import": "detached"
},
"topologyPolicy": {
"topology": "unknown",
"policy": "allowed"
},
"degradedReasons": [
{
"code": "guest-import-unavailable",
"summary": "the guest USBIP import has not converged",
"remediation": "Run `d2b usb attach corp-vm 1-2 --apply` after the VM is running."
}
],
"remediationCommands": [
"d2b usb attach corp-vm 1-2 --apply"
]
}
]
}Status
Probe is a read-only daemon RPC backed by the broker's
UsbipProxyReconcile validation pass. The JSON and human forms split:
- the session claim (
missing,held-by-desired-owner,held-by-other-owner,stale-owner,corrupt, ornot-applicable); - active host carrier state for module/device/backend readiness
(
absent,unavailable,withheld-for-owner,ready,departed-during-probe,unknown, ornot-applicable); - host driver bind, per-env proxy listener, guest import, and redacted topology/policy state;
- closed degraded reasons with human remediation; and
- copy-paste lifecycle commands such as
d2b usb attach corp-vm 1-2 --applyord2b usb detach <owner> 1-2 --applywhen the daemon can name a safe next command.
A persisted lock by itself is not reported as healthy bound; stale or
incomplete lock-only state is degraded until host and guest state are
reconciled. Public output uses redacted state labels and summaries rather
than raw sysfs paths, raw serials, stderr, or policy internals.
VM restart reconciliation preserves same-VM session claims for the current host boot/session, detaches guest imports, and only runs host unbind after firewall withdrawal plus targeted stream cleanup is proven. It then replays host bind/proxy and guest import after guest-control readiness. Runtime absence, proxy unavailability, or guest import unavailability surfaces as degraded USB state. Required policy/topology failures fail before device exposure and must be remediated by fixing the declaration, rebuilding, and rerunning the lifecycle command.
Native
- Read-only daemon query enumerating every declared USBIP busid session claim.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b usb security-key status [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the structured security-key proxy status as JSON. |
--human |
boolean | false |
Force human-readable output on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Always returns the full proxy status: configured keys, per-VM virtual-device health, current lease, and USBIP conflict state. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
78 |
The daemon handler for this command has not shipped yet. | not-yet-implemented |
Human example
$ d2b usb security-key status
# not yet available — exits 78 with not-yet-implemented envelope
Status
This command is defined and CLI-stable. The live (non-dry-run) path exits 78
with a not-yet-implemented envelope until the security-key proxy daemon
handler ships. Use d2b usb security-key test <vm> --dry-run to preview
the planned checks.
Use the user-facing term "security key" in user-visible text; FIDO/CTAP terminology is reserved for diagnostics and technical documentation.
Synopsis: d2b usb security-key sessions [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the session list as JSON. |
--human |
boolean | false |
Force human-readable output. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Returns all recent and active security-key request sessions. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
78 |
The daemon handler for this command has not shipped yet. | not-yet-implemented |
Human example
$ d2b usb security-key sessions
# not yet available — exits 78 with not-yet-implemented envelope
Status
CLI-stable. Exits 78 with a not-yet-implemented envelope until the
security-key proxy daemon handler ships.
Synopsis: d2b usb security-key cancel {<session-id> | --current} [--dry-run | --apply] [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--current |
boolean | false |
Cancel the currently active session. Mutually exclusive with <session-id>. |
--dry-run |
boolean | false |
Print the planned broker op without dispatching it. |
--apply |
boolean | false |
Dispatch the cancel through the daemon → broker path. |
--json |
boolean | false |
Emit structured output as JSON. |
--human |
boolean | false |
Force human-readable output on stdout. |
Arguments
| Argument | Semantics |
|---|---|
<session-id> |
Optional. Opaque session ID returned by sessions. Mutually exclusive with --current. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success (--dry-run or successful cancel). |
— |
2 |
Neither <session-id> nor --current was provided; or neither --dry-run nor --apply was provided. |
usage |
78 |
--apply: the daemon handler has not shipped yet. |
not-yet-implemented |
Human example
$ d2b usb security-key cancel --current --dry-run
d2b usb security-key cancel --dry-run: would send CancelSession(current) to the security-key proxy broker
--json example
{
"command": "usb security-key cancel",
"mode": "dry-run",
"notes": "Dry-run preview; --apply dispatches the cancel through the daemon → broker SecurityKeyProxyCancelSession path.",
"planned": [
"SecurityKeyProxyCancelSession"
],
"target": "current"
}Status
--dry-run is fully implemented and golden-stable. --apply exits 78 until
the daemon handler ships.
Synopsis: d2b usb security-key test <vm> [--dry-run] [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | false |
Print the planned checks without contacting the daemon. |
--json |
boolean | false |
Emit structured output as JSON. |
--human |
boolean | false |
Force human-readable output on stdout. |
Arguments
| Argument | Semantics |
|---|---|
<vm> |
Required. VM name with the per-VM USB security-key option enabled. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success (--dry-run or all checks passed). |
— |
78 |
Live path: the daemon handler has not shipped yet. | not-yet-implemented |
Human example
$ d2b usb security-key test corp-vm --dry-run
d2b usb security-key test --dry-run: would check virtual HID device presence in 'corp-vm' and confirm host broker sees the physical security key
--json example
{
"command": "usb security-key test",
"mode": "dry-run",
"notes": "Dry-run preview; the live path queries the daemon for virtual-HID presence in the guest and physical-key visibility on the host broker.",
"planned": [
"CheckGuestVirtualHidDevice",
"CheckHostBrokerPhysicalKeyVisibility"
],
"vm": "corp-vm"
}Status
--dry-run is fully implemented and golden-stable. The live path exits 78
until the daemon handler ships.
Synopsis: d2b console <vm>
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
| (none) | — | — | Serial console access has no command-line flags. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required headless VM name. Graphics VMs are rejected and must be launched with d2b vm start <vm> --apply. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
Console launch or output read failure. | generic |
2 |
Unknown VM, missing argument, or graphics VM selected. | usage |
80 |
provider-misconfigured: ACA sandbox without an active guestd-compatible console transport; see ACA console — provider misconfiguration. |
provider-misconfigured |
Human example
$ d2b console corp-vm
Connected to console for VM 'corp-vm' (LocalHypervisor). Press Ctrl-] to detach.
Console control messages are emitted on stderr. Stdout is reserved for the raw
guest UART byte stream so d2b console <vm> > console.log captures only guest
output.
Status
The Rust CLI dispatches ConsoleOp to d2bd over the public socket. The daemon owns a persistent ring-buffer drainer per VM and hands the attached operator session reads from that buffer. Provider-capability resolution runs before attach; ACA targets without an active guestd-compatible terminal transport surface a typed provider-misconfigured error (exit 80) rather than falling back to any shell channel. See provider capability matrix for the per-provider transport model; the design is governed by ADR 0041 and ADR 0015.
Native
- Parses and validates arguments natively, then dispatches
ConsoleOptod2bdover the public socket; surfaces typed error envelopes on failure.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b audio status [<vm>]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
| (none) | — | — | Audio status has no flags in the compatibility contract. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Optional VM name. When omitted, the command prints one block per audio-enabled VM. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. Per-target enforcement: unsupported (e.g. qemu-media guest-side) is reported in the output body, not as an error exit. |
— |
1 |
Unexpected filesystem or state probe failure. | generic |
2 |
Unknown VM or unsupported invocation shape. | usage |
80 |
provider-misconfigured: ACA sandbox without an active guestd audio transport; see provider capability matrix. |
provider-misconfigured |
Human example
$ d2b audio status corp-vm
audio: enabled
mic: off
speaker: off
sidecar: inactive
device: detached
Status
The Rust CLI dispatches AudioOp::GetState to d2bd over the public socket. Provider capability resolution runs before any state access; Cloud Hypervisor NixOS VMs read OFD-locked state from /run/d2b/audio/<vm>.json, qemu-media VMs report enforcement: unsupported for guest-side, and ACA sandbox VMs route through provider guestd. Multi-target queries return per-target errors so one misconfigured provider does not fail the entire response. See provider capability matrix for the per-provider enforcement model.
Native
- Parses and validates arguments natively, then dispatches
AudioOp::GetStatetod2bdover the public socket; surfaces typed error envelopes on failure.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b audio mic on|off <vm>
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
| (none) | — | — | The direction and state are positional arguments, not flags. |
Arguments
| Argument | Semantics |
|---|---|
state |
Required literal on or off. Controls the microphone grant only. |
vm |
Required VM name. The VM must declare audio.enable = true. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. State is persisted and enforcement applied; applied: host-only is reported only for providers such as qemu-media where guest enforcement is unsupported. |
— |
1 |
Audio state write, sidecar, or hotplug failure. | generic |
2 |
Bad state literal, unknown VM, or audio not enabled for the VM. | usage |
80 |
provider-misconfigured: ACA sandbox without an active guestd audio transport. |
provider-misconfigured |
Human example
$ d2b audio mic on corp-vm
d2b audio: state -> mic=on, speaker=off
audio: enabled
mic: on
speaker: off
sidecar: active
device: will-attach-on-next-up
Status
The Rust CLI dispatches AudioOp::SetMic to d2bd over the public socket. The daemon writes OFD-locked state atomically and, where guest enforcement is supported, applies the guest mic grant via guestd before reporting host-and-guest. Providers without guest enforcement, such as qemu-media, report the explicit host-only posture. See provider capability matrix for the per-provider enforcement model.
Native
- Parses and validates arguments natively, then dispatches
AudioOp::SetMictod2bdover the public socket; surfaces typed error envelopes on failure.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b audio speaker on|off <vm>
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
| (none) | — | — | The direction and state are positional arguments, not flags. |
Arguments
| Argument | Semantics |
|---|---|
state |
Required literal on or off. Controls the speaker grant only. |
vm |
Required VM name. The VM must declare audio.enable = true. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. State is persisted and enforcement applied; applied: host-only is reported only for providers such as qemu-media where guest enforcement is unsupported. |
— |
1 |
Audio state write, sidecar, or hotplug failure. | generic |
2 |
Bad state literal, unknown VM, or audio not enabled for the VM. | usage |
80 |
provider-misconfigured: ACA sandbox without an active guestd audio transport. |
provider-misconfigured |
Human example
$ d2b audio speaker on corp-vm
d2b audio: state -> mic=off, speaker=on
audio: enabled
mic: off
speaker: on
sidecar: active
device: will-attach-on-next-up
Status
The Rust CLI dispatches AudioOp::SetSpeaker to d2bd over the public socket. Behavior mirrors audio mic: state is persisted atomically under OFD lock and guest enforcement is applied where available. See provider capability matrix for the per-provider enforcement model.
Native
- Parses and validates arguments natively, then dispatches
AudioOp::SetSpeakertod2bdover the public socket; surfaces typed error envelopes on failure.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b audio off <vm>
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
| (none) | — | — | The command revokes both directions; there are no flags. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. Calling the command against a VM that never had audio enabled is an idempotent no-op. applied: host-only is reported only for providers such as qemu-media where guest enforcement is unsupported. |
— |
1 |
Audio state write or sidecar failure. | generic |
2 |
Missing or unknown VM name. | usage |
80 |
provider-misconfigured: ACA sandbox without an active guestd audio transport. |
provider-misconfigured |
Human example
$ d2b audio off corp-vm
d2b audio: state -> mic=off, speaker=off
audio: enabled
mic: off
speaker: off
sidecar: inactive
device: detached
Status
The Rust CLI dispatches AudioOp::Mute to d2bd over the public socket. The daemon atomically revokes both mic and speaker grants, persisting state under OFD lock and applying guest enforcement where available. See provider capability matrix for the per-provider enforcement model.
Native
- Parses and validates arguments natively, then dispatches
AudioOp::Mutetod2bdover the public socket; surfaces typed error envelopes on failure.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b build <vm>
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
| (none) | — | — | Build does not take command-line flags in v0.4.0 or v1.0. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
Nix evaluation/build failure or missing flake context. | generic |
2 |
Missing or unknown VM name. | usage |
Human example
$ d2b build corp-vm
d2b: building corp-vm closure...
d2b: corp-vm closure → /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-nixos-system-corp-vm
GC root: /var/lib/d2b/vms/corp-vm/result
Status
Build is a native non-destructive planner that renders the eval/build preview without falling back to bash.
Native
- Native eval/build planner; renders the closure preview and GC-root path.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b switch <vm> [--dry-run | --apply] [--human | --json]
Status
switch is a daemon-native live guest activation verb. If neither
mutation flag is set, the CLI prints the parity notice and defaults to
--dry-run; --apply requires the VM to be running and reachable over
guest-control.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the activation without mutating the guest. |
--apply |
boolean | false |
Publish the prepared VM closure into the VM's live store pool, ask guestd to activate that prepared toplevel inside the running guest, poll guest activation status, then commit the successful generation through the broker. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
1 |
d2bd is unreachable or guest-control transport fails before a typed activation result is available. |
daemon-down, generic |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Host store publication, broker commit, guest activation capability, or guest activation status failed closed. | broker-error, not-yet-implemented |
Stopped/offline VMs fail closed: switch --apply never asks the host
broker to execute the guest activation program, never mutates host
systemd for the VM, and never falls back to SSH or a host shell. Start
the VM first with d2b vm start <vm> --apply, wait for guest-control
readiness, then rerun d2b switch <vm> --apply.
The live guest activation wait is bounded by
d2b.daemon.lifecycle.liveActivation.timeoutSeconds or the per-VM
d2b.vms.<vm>.lifecycle.liveActivation.timeoutSeconds override. If
activation times out in an identity-bound guest, the typed error points at
the guest activation unit. Complete the in-guest provider flow (for example
an Entra/Himmelblau hello/PIN prompt) and retry, or use d2b boot <vm> --apply followed by a VM restart when live user-session activation is
expected to block.
Human example
$ d2b switch corp-vm --apply
d2b switch --apply activated in guest via guest-control (vm=corp-vm, mode=switch, summary=activated, generationNumber=42)
Native
--apply: routes throughd2bd, which prepares/publishes the closure, opens the authenticated guest-control activation flow, waits for guestd status, and only then asks the broker to commit host-side generation metadata. Successful commits publish both legacy activation metadata and split store-viewstate/current/meta/currentpointers. Daemon-unreachable surfacesdaemon-downexit 1; guest capability/readiness and broker failures surface typed non-zero envelopes. There is no host-side execution of guest activation scripts.- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired; the daemon-only contract is the only path.
Bash
- There is no live bash fallback for this verb.
Synopsis: d2b boot <vm> [--dry-run | --apply] [--human | --json]
Status
boot is the daemon-native offline staging activation verb. If neither
mutation flag is set, the CLI prints the parity notice and defaults to
--dry-run; --apply does not require a running guest.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the offline staging update without mutating guest or host generation state. |
--apply |
boolean | false |
Publish the prepared VM closure into the VM's live store pool and commit it as the toplevel to use on the next VM start; no guest-control activation is attempted. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
1 |
d2bd is unreachable. |
daemon-down |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Host store publication, broker commit, or native handler support failed closed. | broker-error, not-yet-implemented |
boot --apply is the explicit way to stage a new toplevel while the VM
is stopped/offline. It only changes the host-side generation selected
for the next start; it does not run guest activation in the current
guest and does not require guest-control capability.
Human example
$ d2b boot corp-vm --apply
d2b boot --apply staged next-boot toplevel (vm=corp-vm, mode=boot, summary=staged for next boot, generationNumber=42)
Native
--apply: routes throughd2bdand the broker-backed store publication/commit path only. It intentionally skips guest-control activation because there may be no running guest.- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired; the daemon-only contract is the only path.
Bash
- There is no live bash fallback for this verb.
Synopsis: d2b test <vm> [--dry-run | --apply] [--human | --json]
Status
test is a daemon-native live guest activation verb for temporary
activation until reboot. If neither mutation flag is set, the CLI prints
the parity notice and defaults to --dry-run; --apply requires the VM
to be running and reachable over guest-control.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the activation without mutating the guest. |
--apply |
boolean | false |
Publish the prepared VM closure into the VM's live store pool, ask guestd to run a test activation of that prepared toplevel inside the running guest, poll guest activation status, then commit the successful host-side metadata needed to observe the temporary generation. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
1 |
d2bd is unreachable or guest-control transport fails before a typed activation result is available. |
daemon-down, generic |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Host store publication, broker commit, guest activation capability, or guest activation status failed closed. | broker-error, not-yet-implemented |
Stopped/offline VMs fail closed for test --apply. Use boot --apply
for offline staging, or start the VM and retry once guest-control
advertises activation support.
Human example
$ d2b test corp-vm --apply
d2b test --apply activated in guest via guest-control (vm=corp-vm, mode=test, summary=activated until reboot, generationNumber=42)
Native
--apply: routes throughd2bd, guest-control activation, and a broker commit after guestd reports success. There is no host-side execution of guest activation scripts.- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired; the daemon-only contract is the only path.
Bash
- There is no live bash fallback for this verb.
Synopsis: d2b rollback <vm> [--dry-run | --apply] [--human | --json]
Status
rollback is a daemon-native live guest rollback verb. If neither
mutation flag is set, the CLI prints the parity notice and defaults to
--dry-run; live --apply requires the VM to be running and reachable
over guest-control.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the activation without mutating the guest. |
--apply |
boolean | false |
Select the previous prepared VM toplevel, ensure it is published into the VM's live store pool, ask guestd to activate it inside the running guest, poll guest activation status, then commit the successful rollback generation through the broker. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
1 |
d2bd is unreachable or guest-control transport fails before a typed activation result is available. |
daemon-down, generic |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Host store publication, broker commit, guest activation capability, or guest activation status failed closed. | broker-error, not-yet-implemented |
Live rollback of a stopped/offline VM fails closed. Offline rollback is
not inferred from host-side metadata, and boot --apply stages only the
currently declared toplevel for the next start; rollback itself does not
run guest activation from the host.
Human example
$ d2b rollback corp-vm --apply
d2b rollback --apply activated previous toplevel in guest via guest-control (vm=corp-vm, mode=rollback, summary=rolled back, generationNumber=41)
Native
--apply: routes throughd2bd, guest-control activation, and a broker commit after guestd reports success. There is no host-side execution of guest activation scripts.- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired; the daemon-only contract is the only path.
Bash
- There is no live bash fallback for this verb.
Synopsis: d2b generations <vm>
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
| (none) | — | — | Generation listing has no flags. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
2 |
Missing or unknown VM name. | usage |
Human example
$ d2b generations corp-vm
=== Host-side per-VM store generations (/var/lib/d2b/vms/corp-vm/store-meta/generations) ===
(none yet — run 'd2b build corp-vm')
=== In-VM nix-profile generations ===
(corp-vm is not running — start it and try again)
Status
Generations is a native introspection surface that reports current/booted symlink targets without falling back to bash.
Native
- Native introspection of host-side and in-VM nix-profile generations.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b gc [--dry-run | --apply] [--human | --json]
Status
gc is a daemon-native host-store maintenance verb. If neither mutation flag is set, the CLI prints the v0.4 parity notice and defaults to --dry-run; --apply is daemon-native.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan host-side garbage collection without deleting store paths. |
--apply |
boolean | false |
Perform host-side garbage collection. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Garbage collection is global; the current native surface does not take a VM argument. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
78 |
Typed broker-error or not-yet-implemented (v1.0 daemon-only per ADR 0015; no bash fallback). |
broker-error, not-yet-implemented |
The historical bash fallback was retired in v1.0 (ADR 0015); v1.0 daemon-unreachable returns exit-78. The legacy d2b gc exit table is preserved in this file as history.
Human example
$ d2b gc --apply
d2b gc --apply executed via the native daemon → broker path (retainedStorePaths=12, keepGenerations=None, summary=pruned d2b-managed store roots)
Native
--apply: routes throughd2bd→ broker. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78. The historical bash fallback was retired in v1.0 (per ADR 0015).- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired in v1.0; in v1.0 (ADR 0015) the daemon-only contract is the only path. Broker failures surface on stderr with the redacted public-safe remediation from security fix4dde2b9and exit78. - LiveNative: wired through
d2bd→ brokerRunGc(commit7de9194).
Bash
- In v1.0 daemon-only,
exec_legacy_passthroughalways returns the typednot-yet-implementedenvelope (exit 78 per ADR 0015); the historical bash-fallback shim was retired in v1.0.
Synopsis: d2b store verify <vm> [--repair] [--human | --json]
Status
store verify is a daemon-native, broker-backed live-pool integrity
surface for the ADR 0027 split store-view. The CLI is thin: it never reads
store-view/live or store-view/state directly.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--repair |
boolean | false |
Request repair through a forced StoreSync republish, followed by a second verify before reporting success. |
--json |
boolean | false |
Emit the signed store-verify JSON envelope. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Live pool is clean, or repair completed successfully. | — |
1 |
Daemon is unreachable. | daemon-down |
2 |
Unknown flag or unsupported invocation shape. | usage |
4 |
Drift found or integrity remains unknown. | drift |
70 |
The named VM is not declared or not visible to the caller. | not-found |
78 |
Broker/system failure while verifying. | broker-error |
--json example — schema: store-verify.schema.json; prose companion: store-verify.md.
{
"vm": "corp-vm",
"status": "ok",
"checked": 42,
"drifted": 0,
"repaired": 0,
"unknown_reason": null,
"audit_ref": null,
"remediation": null
}Native
- Routes through
d2bd→ brokerStoreVerify. - Verifies the current marker/manifest and top-level
live/basenames and writes host-only integrity records. --repairnever claims success from the StoreSync attempt alone; it returnsrepairedonly after the post-repair verification is clean.
Human example
$ d2b store verify corp-vm
store verify corp-vm: status=ok checked=42 drifted=0 repaired=0
Bash
- There is no bash execution path for this verb.
Synopsis: d2b trust <vm> [--dry-run | --apply] [--human | --json]
Status
trust is a daemon-native host-key TOFU mutation. If neither mutation flag is set, the CLI prints the v0.4 parity notice and defaults to --dry-run; --apply is daemon-native.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the known-host trust update without mutating the managed entry. |
--apply |
boolean | false |
Perform the TOFU trust mutation. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. The live apply path expects the VM to publish a manifest staticIp so the host-key operation can target the guest. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Typed broker-error or not-yet-implemented (v1.0 daemon-only per ADR 0015; no bash fallback). |
broker-error, not-yet-implemented |
The historical bash fallback was retired in v1.0 (ADR 0015); v1.0 daemon-unreachable returns exit-78. The legacy d2b trust exit table is preserved in this file as history.
Human example
$ d2b trust corp-vm --apply
d2b trust --apply executed via the native daemon → broker path (vm=corp-vm, staticIp=10.20.0.10, knownHostsPath=/var/lib/d2b/known_hosts.d2b, updated=true)
Native
--apply: routes throughd2bd→ broker. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78. The historical bash fallback was retired in v1.0 (per ADR 0015).- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired in v1.0; in v1.0 (ADR 0015) the daemon-only contract is the only path. Broker failures surface on stderr with the redacted public-safe remediation from security fix4dde2b9and exit78. - LiveNative: wired through
d2bd→ brokerRunHostKeyTrust(commit7de9194).
Bash
- In v1.0 daemon-only,
exec_legacy_passthroughalways returns the typednot-yet-implementedenvelope (exit 78 per ADR 0015); the historical bash-fallback shim was retired in v1.0.
Synopsis: d2b rotate-known-host <vm> [--dry-run | --apply] [--human | --json]
Status
rotate-known-host is a daemon-native host-key rotation mutation. If neither mutation flag is set, the CLI prints the v0.4 parity notice and defaults to --dry-run; --apply is daemon-native.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan removal and re-pin of the managed known-host entry without mutating the file. |
--apply |
boolean | false |
Perform the known-host rotation mutation. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. The live apply path expects the VM to publish a manifest staticIp so the host-key operation can target the guest. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Typed broker-error or not-yet-implemented (v1.0 daemon-only per ADR 0015; no bash fallback). |
broker-error, not-yet-implemented |
In v1.0 daemon-only (per ADR 0015) the historical bash fallback was retired in v1.0; the verb surfaces typed envelopes (broker-error exit 78, daemon-down exit 1) instead.
Human example
$ d2b rotate-known-host corp-vm --apply
d2b rotate-known-host --apply executed via the native daemon → broker path (vm=corp-vm, staticIp=10.20.0.10, knownHostsPath=/var/lib/d2b/known_hosts.d2b, removed=true)
Native
--apply: routes throughd2bd→ broker. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78. The historical bash fallback was retired in v1.0 (per ADR 0015).- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired in v1.0; in v1.0 (ADR 0015) the daemon-only contract is the only path. Broker failures surface on stderr with the redacted public-safe remediation from security fix4dde2b9and exit78. - LiveNative: wired through
d2bd→ brokerRunRotateKnownHost(commit7de9194).
Bash
- In v1.0 daemon-only,
exec_legacy_passthroughalways returns the typednot-yet-implementedenvelope (exit 78 per ADR 0015); the historical bash-fallback shim was retired in v1.0.
Synopsis: d2b keys list [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the structured managed-key inventory instead of the human table. |
--human |
boolean | false |
Force the human managed-key table on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | The list form is always global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
d2bd is unreachable; the typed #daemon-down envelope is emitted (the v1.0 daemon-only contract — there is no bash fallback; the v1.0 clean-break per ADR 0015 retired the legacy fallback in v1.0). The multi-line Remediation: block per error-codes.md "Remediation rendering conventions" (Category 2 — daemon-down rendering pointer) points operators at the daemon-startup runbook. |
daemon-down |
2 |
Unsupported invocation shape inherited from the keys subcommand dispatcher. |
usage |
Human example
$ d2b keys list
VM ENV FINGERPRINT MANAGED KEY
corp-vm work SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA /var/lib/d2b/keys/corp-vm_ed25519
--json example
{
"command": "keys list",
"entries": [
{
"vm": "corp-vm",
"env": "work",
"managedKeyPath": "/var/lib/d2b/keys/corp-vm_ed25519",
"fingerprint": "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"knownHostsEntry": "10.42.0.11 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMockedExampleKeyForDocsOnly corp-vm"
}
]
}Status
Keys list is a native inventory preview that reports the managed-key resolution placeholders without falling back to bash.
Native
- Native managed-key inventory query over the daemon public socket.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b keys show <vm> [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the structured managed-key record instead of the public-key line. |
--human |
boolean | false |
Force the raw public-key line on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
d2bd is unreachable (typed #daemon-down envelope; multi-line Remediation: block per error-codes.md "Remediation rendering conventions" points operators at the daemon-startup runbook) — OR the daemon returned the request but the key material was unreadable (typed #generic envelope; rare). |
daemon-down / generic |
2 |
Unknown VM, missing VM argument, or unreadable key material reported by daemon as an unknown subject. | usage |
Human example
$ d2b keys show corp-vm
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMockedExampleKeyForDocsOnly corp-vm_ed25519.pub
Status
Keys show is a native preview that reports daemon-resolved key metadata placeholders without falling back to bash.
Native
- Native per-VM managed-key lookup over the daemon public socket.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b keys rotate <vm> [--dry-run | --apply] [--human | --json]
Status
keys rotate is a daemon-native managed-key mutation. If neither mutation flag is set, the CLI prints the v0.4 parity notice and defaults to --dry-run; --apply is daemon-native.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the key rotation without changing managed host keys or guest auth state. |
--apply |
boolean | false |
Perform the managed-key rotation. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered or --apply completed successfully. |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
70 |
The named VM is not declared in the active manifest. | not-found |
78 |
Typed broker-error or not-yet-implemented (v1.0 daemon-only per ADR 0015; no bash fallback). |
broker-error, not-yet-implemented |
In v1.0 daemon-only (per ADR 0015) the historical bash fallback was retired in v1.0; the verb surfaces typed envelopes (broker-error exit 78, daemon-down exit 1) instead.
Human example
$ d2b keys rotate corp-vm --apply
d2b keys rotate --apply executed via the native daemon → broker path (vm=corp-vm, fingerprint=SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, keyPath=/var/lib/d2b/keys/corp-vm_ed25519)
Native
--apply: routes throughd2bd→ broker. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78. The historical bash fallback was retired in v1.0 (per ADR 0015).- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired in v1.0; in v1.0 (ADR 0015) the daemon-only contract is the only path. Broker failures surface on stderr with the redacted public-safe remediation from security fix4dde2b9and exit78. - LiveNative: wired through
d2bd→ brokerRunKeysRotate(commit7de9194).
Bash
- In v1.0 daemon-only,
exec_legacy_passthroughalways returns the typednot-yet-implementedenvelope (exit 78 per ADR 0015); the historical bash-fallback shim was retired in v1.0.
Synopsis: d2b audit [--strict] [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--strict |
boolean | false |
Preserve the strict-audit semantics (extra invariants; warns become errors). v1.0+ never falls back to bash regardless of flag; the historical bash-strict path was retired in v1.0 per ADR 0015. |
--human |
boolean | false when stdout is not a TTY; otherwise effectively true unless --json is present |
Force the human summary format. |
--json |
boolean | false |
Force the JSON document on stdout even on a TTY. The JSON document shape is stable across v1.0 → v1.1 unless a schema bump is annotated in the audit schema (./cli-output/audit.schema.json); v1.0 baseline preserves the audit object shape. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Audit is always global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
78 |
--strict flag arm only — d2b audit --strict emits typed #not-yet-implemented envelope unconditionally regardless of daemon state per ADR 0017 § "Migration target table" line 91 (the strict-audit surface is queued for v1.2+ (unscheduled; v1.1 only delivers the typed-envelope rendering + remediation per ADR 0017) implementation). The multi-line Remediation: block per error-codes.md "Remediation rendering conventions" points operators at the migration runbook. |
not-yet-implemented |
0 |
Success (non---strict arm only — --strict returns exit 78 unconditionally per above). |
— |
1 |
(Non---strict arm only) d2bd is unreachable; typed #daemon-down envelope emitted. The multi-line Remediation: block per error-codes.md "Remediation rendering conventions" points operators at the daemon-startup runbook. |
daemon-down |
2 |
Unknown flag or unexpected positional argument. | usage |
Human example
$ d2b audit --human
=== d2b security audit ===
kvm_dev_mode: 660 ✓
wayland_user_in_kvm: false ✓
store_delivery:
corp-vm: virtiofs
sys-work-net: erofs
--json example — schema: audit.schema.json; prose companion: audit.md.
{
"kvm_dev_mode": "660",
"wayland_user_in_kvm": false,
"store_delivery": {
"corp-vm": "virtiofs",
"sys-work-net": "erofs"
},
"virtiofsd": {},
"ssh": {
"host": {
"PasswordAuthentication": false
},
"corp-vm": {
"PasswordAuthentication": false
}
},
"bridge_isolation": {},
"autoUpgrade_commits_lock": false,
"ch_version": "52.0",
"crosvm_rev": "deadbeefdead",
"seccomp_rev": "feedfacefeed",
"ch_crosvm_pair_ok": true,
"fail2ban_active": true,
"sidecars_per_vm": {},
"usbipd_per_env_isolation": {}
}Status
Audit is part of the read-only daemon surface and keeps both human and JSON output contracts. --strict surfaces a typed not-yet-implemented envelope (exit 78) pending its daemon-native implementation.
Native
- Read-only daemon query;
--jsonemits the stable audit document.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b host check [--strict] [--read-only] [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--strict |
boolean | false |
Promote advisory runner-parity and prerequisite warnings to the failure exit code. |
--read-only |
boolean | true |
Compatibility alias that makes the no-mutation posture explicit. The command is always read-only, so the flag is accepted but does not widen capability. |
--human |
boolean | false |
Force the human host-check summary on stdout. |
--json |
boolean | false |
Emit the stable host-check report document on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Host check is always global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
All required checks passed. | — |
1 |
At least one advisory warning was reported. | host-check-warning |
2 |
At least one required check failed. | host-check-failure |
3 |
Unknown flag or other usage error. | usage |
Human example
$ d2b host check
PASS
- kernel-version: running kernel 6.8.0 satisfies >= 6.6
- cgroup-v2: /sys/fs/cgroup/cgroup.controllers is present
WARN
- firewalld-coexistence: firewalld is active; coexistence is reported but host rules are not mutated
--json example — schema: host-check.schema.json; prose companion: host-check.md.
{
"summary": {
"pass": 3,
"warn": 1,
"fail": 0
},
"checks": [
{
"id": "kernel-version",
"severity": "pass",
"required": true,
"message": "Kernel 6.8.0 satisfies >= 6.6",
"remediation": null
},
{
"id": "firewalld-coexistence",
"severity": "warn",
"required": false,
"message": "firewalld is active; keep the host ruleset unchanged",
"remediation": "Use host prepare for automated firewall reconcile."
}
],
"runnerParity": [
{
"vm": "corp-vm",
"declaredRunner": "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-microvm-cloud-hypervisor-corp-vm",
"runnerParityPath": "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-microvm-cloud-hypervisor-corp-vm",
"runnerParityOk": true
}
]
}Status
Host check is a read-only daemon RPC by design; mutation is explicitly handled by host prepare.
Native
- Read-only host-posture inventory; never mutates nftables, cgroups, users, or runtime directories.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
The command never mutates nftables, cgroups, users, or runtime directories. --read-only is therefore part of the compatibility surface, not a capability toggle.
Synopsis: d2b host prepare [--dry-run | --apply] [--human | --json]
Status
host prepare is a daemon-native host-reconcile verb. If neither mutation flag is set, stderr emits "d2b: NOTICE: defaulting to --dry-run; d2b 1.0 will require explicit --dry-run or --apply" and the CLI defaults to --dry-run. --dry-run is wired live; --apply is not yet wired — the daemon-side typed-intent dispatch and bundle resolver that back it are still pending, so it returns the typed daemon-down envelope (exit 1) today (use --dry-run for now). On a Tier-0 legacy/mixed host, --apply is refused with tier-0-legacy-uses-nixos-module / single-writer-conflict (exit 78).
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan the host reconcile without mutating host state. |
--apply |
boolean | false |
Perform the host-reconcile mutation. Not yet wired — returns daemon-down (exit 1) today; use --dry-run for now. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Host reconcile is global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run summary rendered. (Once --apply is wired, a successful apply will also exit 0.) |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
78 |
Tier-0 all-legacy refusal, Tier-0 mixed single-writer conflict, or typed broker-error / not-yet-implemented (v1.0 daemon-only per ADR 0015; no bash fallback). |
tier-0-legacy-uses-nixos-module, single-writer-conflict, broker-error |
In v1.0 daemon-only (per ADR 0015) the historical bash fallback was retired in v1.0; the verb surfaces typed envelopes (broker-error exit 78, daemon-down exit 1) instead.
Human example
$ d2b host prepare --dry-run
host prepare --dry-run: would reconcile nftables + routes + sysctls + /etc/hosts + NetworkManager unmanaged state
Native
--apply: not yet wired — returns the typeddaemon-downenvelope (exit 1) today; re-run with--dry-runfor now. When the daemon-side dispatch ships,--applywill route throughd2bd→ broker; daemon-unreachable will surfacedaemon-downexit 1, native-handler-deferrednot-yet-implementedexit 78, andbroker-errorexit 78. On a Tier-0 legacy/mixed host--applyis refused today (exit 78). The historical bash fallback was retired in v1.0 (per ADR 0015).- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired in v1.0; in v1.0 (ADR 0015) the daemon-only contract is the only path. Broker failures surface on stderr with the redacted public-safe remediation from security fix4dde2b9and exit78. - LiveNative (forthcoming): once the public-socket dispatch ships,
--applywiresd2bd→ brokerApplyNftables+ApplyRoute+ApplySysctl+UpdateHostsFile+ApplyNmUnmanaged(broker ops staged in commitee6ed0b; public-socket dispatch pending).
Bash
- In v1.0 daemon-only,
exec_legacy_passthroughalways returns the typednot-yet-implementedenvelope (exit 78 per ADR 0015); the historical bash-fallback shim was retired in v1.0.
Synopsis: d2b host destroy [--dry-run | --apply] [--human | --json]
Status
host destroy is a daemon-native host-reconcile verb. If neither mutation flag is set, stderr emits "d2b: NOTICE: defaulting to --dry-run; d2b 1.0 will require explicit --dry-run or --apply" and the CLI defaults to --dry-run. --dry-run is wired live; --apply is not yet wired — the daemon-side typed-intent dispatch and bundle resolver that back it are still pending, so it returns the typed daemon-down envelope (exit 1) today (use --dry-run for now). On a Tier-0 legacy host, --apply is refused with tier-0-legacy-uses-nixos-module (exit 78).
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | implicit if neither mutation flag is set | Plan removal of d2b-owned host reconcile state without mutating host state. |
--apply |
boolean | false |
Perform the host-reconcile teardown. Not yet wired — returns daemon-down (exit 1) today; use --dry-run for now. |
--json |
boolean | false |
Emit the dry-run summary or typed mutating-verb envelope as JSON. |
--human |
boolean | false |
Force the human summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Host teardown is global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run summary rendered. (Once --apply is wired, a successful apply will also exit 0.) |
— |
2 |
Unknown flag or unsupported invocation shape. | usage |
78 |
Tier-0 all-legacy refusal or typed broker-error (v1.0 daemon-only per ADR 0015; no bash fallback). |
tier-0-legacy-uses-nixos-module, broker-error |
In v1.0 daemon-only (per ADR 0015) the historical bash fallback was retired in v1.0; the verb surfaces typed envelopes (broker-error exit 78, daemon-down exit 1) instead.
Human example
$ d2b host destroy --dry-run
host destroy --dry-run: no d2b-owned resources to remove
Native
--apply: not yet wired — returns the typeddaemon-downenvelope (exit 1) today; re-run with--dry-runfor now. When the daemon-side dispatch ships,--applywill route throughd2bd→ broker; daemon-unreachable will surfacedaemon-downexit 1, native-handler-deferrednot-yet-implementedexit 78, andbroker-errorexit 78. On a Tier-0 legacy host--applyis refused today (exit 78). The historical bash fallback was retired in v1.0 (per ADR 0015).- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired in v1.0; in v1.0 (ADR 0015) the daemon-only contract is the only path. Broker failures surface on stderr with the redacted public-safe remediation from security fix4dde2b9and exit78. - LiveNative (forthcoming): once the public-socket dispatch ships,
--applywires the same broker-op set in reverse order:ApplyNmUnmanaged+ApplyRoute+ApplySysctl+UpdateHostsFile+ApplyNftables(broker ops staged in commitee6ed0b; reverse-order hardening inb73e28f; public-socket dispatch pending).
Bash
- In v1.0 daemon-only,
exec_legacy_passthroughalways returns the typednot-yet-implementedenvelope (exit 78 per ADR 0015); the historical bash-fallback shim was retired in v1.0.
Synopsis: d2b host migrate-storage [--dry-run | --apply | --rollback --from-checkpoint <id>] [--human | --json]
Status
Plans the one-time breaking storage layout cutover. The current build is
read-only for this verb: --dry-run emits a checkpoint ID, the exact
rollback command, preflight requirements, preserved persistent data,
cutover-only cleanup candidates, and fail-closed hazards. --apply and
--rollback fail closed until the broker-backed mover ships.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | required unless --apply or --rollback is set |
Plan the storage cutover without moving or deleting host state. |
--apply |
boolean | false |
Apply the storage cutover. Currently returns a typed not-implemented envelope. |
--rollback |
boolean | false |
Roll back from a checkpoint. Currently returns a typed not-implemented envelope. |
--from-checkpoint |
string | required with --rollback |
Checkpoint ID from the dry-run plan. |
--json |
boolean | false |
Emit the dry-run plan or typed refusal envelope as JSON. |
--human |
boolean | false |
Emit the human dry-run plan. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Storage cutover planning is global. |
Dry-run JSON shape
{
"command": "host migrate-storage",
"mode": "dry-run",
"checkpointId": "storage-cutover-…",
"rollbackCommand": "d2b host migrate-storage --rollback --from-checkpoint storage-cutover-…",
"vmCount": 2,
"vms": ["corp-vm", "work-vm"],
"preflightRequirements": [
"all d2b VMs stopped",
"d2bd.service stopped",
"d2b-priv-broker.service stopped",
"net VMs stopped; guest routing, TAP connectivity, and dependent bridge traffic will be interrupted"
],
"preserve": [
"per-VM swtpm NVRAM and swtpm identity markers",
"declared host bridges, TAP naming intent, nftables/NM/networkd ownership metadata, and network-preflight evidence"
],
"cutoverOnlyCleanup": [
"/run/d2b-gpu",
"boot-scoped runtime socket files only after all d2b services are stopped"
],
"failClosedHazards": [
"symlink or path traversal inside any moved path",
"recursive operations traversing hardlink farms or mutating shared /nix/store inodes",
"any attempt to unlink lock files during cutover rather than leaving /run locks for reboot/tmpfs cleanup"
],
"applyStatus": "not-implemented-in-this-build"
}Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Dry-run plan rendered. | — |
2 |
Unknown flag or invalid flag combination. | usage |
78 |
--apply or --rollback requested before the broker-backed mover is available. |
storage-migration-apply-not-implemented, storage-migration-rollback-not-implemented |
Human example
$ d2b host migrate-storage --dry-run
host migrate-storage --dry-run: checkpoint=storage-cutover-… vm_count=2
rollback command: d2b host migrate-storage --rollback --from-checkpoint storage-cutover-…
preflight requirements:
- all d2b VMs stopped
- d2bd.service stopped
- d2b-priv-broker.service stopped
- net VMs stopped; guest routing, TAP connectivity, and dependent bridge traffic will be interrupted
Native
--dry-runis a rust-native read-only planner.--applyand--rollbackfail closed with typed exit-78 envelopes until the broker-backed mover lands. There is no bash fallback and no manual chmod/chown/setfacl remediation.
Bash
- No bash implementation exists. The Rust CLI owns this surface.
The dry-run text deliberately avoids manual chmod/chown/setfacl
instructions. Operators should treat the checkpoint ID and rollback command as
the handoff contract for the later broker-backed cutover implementation.
Synopsis: d2b host reconcile-otel-acls [--dry-run | --apply] [--human | --json]
Reconciles per-VM OTel relay/bridge filesystem ACLs to the
current bundle's intended state. Replaces the v0.4-era
host-otel-relay-acl.nix activation script (retired in v1.1
per ADR 0018 "Host-OTel
ACL migration table"). Invoked from
system.activationScripts.d2bReconcileOtelAcls on every
nixos-rebuild switch and from d2bd.service ExecStartPost=
on daemon startup; operators may also invoke it directly for
mid-cycle reconciliation.
Behaviour
--dry-run(default if neither flag given): reports the planned ACL set/revoke ops without dispatching to the broker; exit 0 with a planned-ops summary.--apply: dispatches throughd2bd→daemon-api/host-prep ReconcileOtelAcls→ brokerSetSocketAcl/RevokeSocketAclIfPresentper the ADR 0018 migration table. Emits daemon-sideReconcileOtelAclsStarted/Succeeded/FailedOpAuditRecordentries with abroker_op_idscorrelation array, plus the per-row broker-op entries.
Exit codes / typed envelopes (per
docs/reference/error-codes.md)
- Exit 0: success (
--dry-runplan summary OR--applycomplete). - Exit 1:
#daemon-down(daemon unreachable; activation script defers to the daemon-startupExecStartPost=trigger). - Exit 31:
#broker-validation-failed(broker rejected one or moreSetSocketAcl/RevokeSocketAclIfPresentops with a validation reason; see audit log for the per-op denial class perdocs/reference/error-codes.md:115). - Exit 50:
#internal-io(broker dispatch error or daemon-side I/O failure during the reconcile). - Exit 2:
#usage(invalid flag combination).
JSON output shape (committed in v1.1 with full schema in
cli-output/host-reconcile-otel-acls.schema.json):
{
"mode": "dry-run|apply",
"planned": [
{ "op": "SetSocketAcl", "path": "...", "group": "...", "mode": "rwx|--x|rw" },
{ "op": "RevokeSocketAclIfPresent", "path": "...", "groups": ["..."] }
],
"applied": [],
"broker_op_ids": ["..."],
"daemon_op_id": "..."
}(applied has the same shape as planned; it is populated only in
--apply mode and omitted/empty in --dry-run mode.)
Native
- v1.1 introduces this verb. v1.0 has no equivalent surface
(the legacy
host-otel-relay-acl.nixscript is bash, not a daemon-dispatched CLI op). - Daemon-unreachable surfaces
#daemon-downexit 1 per the v1.1 typed-envelope contract. - No bash fallback exists per ADR 0017.
Synopsis: d2b host doctor --read-only [--human] [--json]
Read-only daemon-path health probe. The baseline broker socket, daemon socket, and audit-log checks are extended with structured liveness for the broker-spawned singletons and the recovery report files the daemon persists during startup. Probed surfaces:
- broker-ready —
SOCK_SEQPACKETconnect to the broker socket. - daemon-ready — connect to the public daemon socket.
- metrics-endpoint — loopback HTTP GET against the Prometheus scrape
URL (default
http://127.0.0.1:9101/metrics; seedaemon-metrics). - otel-host-bridge-runner — counts
role: "otel-host-bridge"entries in<daemon-state>/pidfd-table.json. - usbipd-runners — counts
role: "usbip"entries (one per env that owns USB). - kernel-module-matrix — reads
<daemon-state>/kernel-module-report.json(written by the daemon's startup self-check, seekernel-module-check). - autostart-status — reads
<daemon-state>/autostart-report.json(written after the daemon's autostart pass, seedaemon-autostart). - storage-lifecycle-report — reads
<daemon-state>/storage-lifecycle-report.json(written by the daemon's startup storage/restart/sync contract check, seestorage-lifecycle-report).
Doctor never calls a privileged broker operation. The --read-only
flag is currently mandatory; mutation forms are later deliverables. The
full JSON schema lives at
cli-output/host-doctor.schema.json;
prose is in cli-output/host-doctor.md.
Legacy top-level fields (broker_ready, findings[], summary,
exitCode) are preserved verbatim.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--read-only |
boolean | (required) | Acknowledge that this invocation does not mutate host state. Mandatory. |
--human |
boolean | false |
Force the human doctor summary on stdout. |
--json |
boolean | false |
Emit the stable doctor report document on stdout. |
Environment overrides
| Variable | Default | Purpose |
|---|---|---|
D2B_BROKER_SOCKET |
/run/d2b/broker.sock |
Probe target for broker-ready. |
D2B_PUBLIC_SOCKET |
/run/d2b/public.sock |
Probe target for daemon-ready. |
D2B_DAEMON_STATE_DIR |
/var/lib/d2b/daemon-state |
Where the daemon writes pidfd/module/autostart reports. |
D2B_METRICS_URL |
http://127.0.0.1:9101/metrics |
URL probed by metrics-endpoint. |
Exit codes
| Exit | Meaning | Catalog anchor |
|---|---|---|
0 |
Every check passed. | — |
1 |
At least one check is warn, none are fail. |
host-check-warning |
2 |
At least one check is fail (e.g. required kernel module missing, autostart VM failed, broker socket unreachable). |
host-check-warning |
78 |
Missing required --read-only flag (doctor is read-only; mutation forms are later deliverables). |
--read-only-required |
Disposition: rust-native.
Synopsis: d2b host install (--dry-run | --apply [--enable] [--start | --no-start]) [--human | --json]
--dry-run prints the synthesized 5-step installer preview. --apply
routes through the daemon → broker RunHostInstall path. Broker
failures surface the typed broker-error envelope with exit 78;
they do not fall back to bash. If the daemon socket is
unreachable, the verb surfaces the typed daemon-down envelope with
exit 1 (the v1.0 daemon-only contract; the historical bash
fallback was retired in v1.0).
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | false |
Preview the synthesized 5-step install plan. |
--apply |
boolean | false |
Run the live daemon → broker RunHostInstall path. |
--enable |
boolean | false |
After --apply, enable d2bd.service. |
--start |
boolean | false |
After --apply --enable, start d2bd.service. |
--no-start |
boolean | false |
After --apply, leave d2bd.service stopped. |
--json |
boolean | false |
Emit the stable JSON plan or typed error envelope. |
--human |
boolean | false |
Force the human summary. |
Exit codes
| Exit | Meaning | Catalog anchor |
|---|---|---|
0 |
Dry-run plan rendered or daemon → broker apply succeeded. | — |
78 |
Missing --dry-run / --apply, or the daemon → broker apply path returned broker-error. |
--apply-or-dry-run-required, broker-error |
Disposition: rust-native (--apply dispatches through daemon → broker RunHostInstall).
Synopsis: d2b migrate (--dry-run | --apply) [--human | --json]
migrate is the migration analyzer. --dry-run reports the current
deployment-shape tier plus the stable migration checklist. Per-VM
supervisor classification is still unavailable on the public manifest, so
the planner keeps that limitation explicit and points operators at
d2b status <vm> for per-VM truth. --apply uses the daemon-first
bridge and broker RunMigrate path.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--dry-run |
boolean | false |
Report the deployment-shape tier and the planned migration checklist. |
--apply |
boolean | false |
Run the live daemon → broker RunMigrate marker-writer path. |
--json |
boolean | false |
Emit the planner / apply result envelope on stdout. |
--human |
boolean | false |
Force human-readable output on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Migration analysis is host-global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
2 |
Unknown flag or unsupported invocation shape. | usage |
78 |
Missing explicit --dry-run/--apply or typed broker-error / not-yet-implemented (v1.0 daemon-only per ADR 0015; no bash fallback). |
--apply-or-dry-run-required, broker-error, not-yet-implemented |
In v1.0 daemon-only (per ADR 0015) the historical bash fallback was retired in v1.0; the verb surfaces typed envelopes (broker-error exit 78, daemon-down exit 1) instead.
Human example
$ d2b migrate --dry-run
d2b migrate --dry-run: deployment shape = tier-0-mixed, 2 VM(s) in manifest.
Per-VM supervisor classification is not available on the public manifest today.
Use `d2b status <vm>` to inspect each VM directly; `d2b migrate --apply`
is the live mutation path when you are ready.
Native
--apply: routes throughd2bd→ broker. Daemon-unreachable surfacesdaemon-downexit 1; native-handler-deferred surfacesnot-yet-implementedexit 78;broker-errorexit 78. The historical bash fallback was retired in v1.0 (per ADR 0015).- The
D2B_NATIVE_ONLY=1/D2B_LEGACY_BASH_OPT_IN=1env vars were retired in v1.0; in v1.0 (ADR 0015) the daemon-only contract is the only path. Native refusals stay on the typed envelope, and broker failures surface with exit78. - Dry-run analysis is pure Rust;
--applydispatches throughd2bd→ brokerRunMigrate.
Bash
d2b migrate path directly.
- In v1.0 daemon-only,
exec_legacy_passthroughalways returns the typednot-yet-implementedenvelope (exit 78 per ADR 0015); the historical bash-fallback shim was retired in v1.0.
Disposition: rust-native — dry-run analysis is native, and
--apply uses daemon → broker RunMigrate when available.
Synopsis: d2b auth status [--human] [--json]
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
--json |
boolean | false |
Emit the stable caller-capability document instead of the human summary. |
--human |
boolean | false |
Force the human caller-capability summary on stdout. |
Arguments
| Argument | Semantics |
|---|---|
| (none) | Auth status is always global. |
Exit codes
| Code | Meaning | Typed error / reference |
|---|---|---|
0 |
Success. | — |
1 |
Socket reachability or capability probe failure. | generic |
2 |
Unknown flag or unsupported invocation shape. | usage |
Human example
$ d2b auth status
role: launcher
public socket: /run/d2b/public.sock (reachable, server=0.2.0, selected=0.2.0)
allowed commands: auth status, host check, list, status
denied commands:
- audit: requires admin role in d2b.site.adminUsers
--json example — schema: auth-status.schema.json; prose companion: auth-status.md.
{
"role": "launcher",
"publicSocket": {
"path": "/run/d2b/public.sock",
"reachable": true,
"serverVersion": "0.2.0",
"selectedVersion": "0.2.0"
},
"allowedCommands": [
"auth status",
"host check",
"list",
"status"
],
"deniedCommands": [
{
"command": "audit",
"reason": "requires admin role in d2b.site.adminUsers"
}
]
}Status
Auth status is a read-only daemon query that reports caller mapping, socket reachability, and authorization hints.
Native
- Read-only daemon query resolving the caller's
SO_PEERCREDrole and the allowed/denied verb set.
Bash
- There is no live bash fallback for this verb; the bash disposition is retained only as coverage taxonomy / the retired path.
Synopsis: d2b config sync <vm> [--guest-path <path>] [--host <h>] [--user <u>] [--key <path>] [--known-hosts <path>] [--dry-run] [--json]
On a guest-control-capable VM, config sync reads the VM's canonical
guest config working copy (default /var/lib/d2b-guest/guest-config.nix)
over the authenticated guest-control transport — a typed
readGuestConfig request to d2bd over the daemon public socket.
There is no SSH: no ssh/scp process is spawned, and the SSH-shaped flags
(--host/--user/--key/--known-hosts) and a non-default
--guest-path are rejected up front with guest-control-ssh-flag-rejected
(exit 2). JSON responses carry transport: "guest-control".
The host treats the received bytes as untrusted data: the read is bounded (1 MiB hard cap + a per-attempt timeout, so a hostile guest cannot OOM/hang the host), the host re-enforces the size cap and recomputes size
- sha256 from the received bytes (never a guest-reported value), the
content is validated (non-empty, valid UTF-8), and the result is
atomically written to a host-side staging file
(
${XDG_STATE_HOME:-~/.local/state}/d2b/config-staging/<vm>.guest.nix). The staging copy is never evaluated until approved.
--dry-run selects and reports the transport WITHOUT contacting the
daemon or reading any guest bytes: it emits transport: "guest-control"
plus the planned staging target only — never an SSH argv and never guest
content.
Fail-closed behaviour:
config syncis admin-only:readGuestConfigis gated to thed2b-adminrole (d2b.site.adminUsers) at the daemon'sSO_PEERCREDaccept time. A launcher-role caller is rejected with the typedauthz-not-adminerror (exit75, AUTH) before any socket request reads guest bytes. The staging-only verbs (diff/approve/reject/status) dispatch no daemon verb and are not admin-gated.- A known VM whose generation does not declare the guest-control transport
(old or partial generation) is rejected with
guest-control-unavailable-old-generation(exit70); no socket request is sent and nothing is staged. The operator SSH-compatibility transport is not wired into this command. - When the daemon socket is unreachable, the command reports
guest-control-transport-unavailable(exit70). - Per-kind read errors surfaced by the daemon
(
guest-control-file-not-found,guest-control-file-too-large,guest-control-path-unsafe,guest-control-read-denied,guest-control-timeout,guest-control-protocol-error,guest-control-auth-failed,guest-control-capability-unavailable) each map to exit70with their slug and never echo guest content, paths, or transport detail.
d2b host shutdown-hook --apply is intentionally absent from the public
clap/completion surface and is invoked by d2bd.service only while the host
manager is stopping. When that hook connects as uid 0, the daemon assigns the
narrow HostShutdown role. This role can dispatch only vmStop; it cannot run
admin-only operator verbs such as exec, USB attach/detach, host prepare/destroy,
audit export, key rotation, or config sync.
Disposition: rust-native — host-initiated typed readGuestConfig
over the daemon public socket; no SSH, no virtiofs, no new privileged
surface.
Synopsis: d2b config diff <vm> --against <guestConfigFile> [--json]
Shows a unified diff between the staged guest config and the live
host-side file the operator names with --against (typically their
guestConfigFile). Exits 0 whether or not they differ; --json
reports differs + the diff text.
Disposition: rust-native — read-only diff -u.
Synopsis: d2b config approve <vm> --to <target-file> [--json]
Validates the staged guest config (non-empty, valid UTF-8) and
atomically writes it onto the operator-chosen --to target (unique
O_EXCL temp + fsync + rename + parent-dir fsync), then clears the
staging file. The CLI never auto-locates the operator's config tree —
the operator names the target explicitly. The authoritative containment
- eval gate is the per-VM
guestConfigFileassertion that runs on the subsequentd2b switch.
Disposition: rust-native — host-operator-only; atomic publish.
Synopsis: d2b config reject <vm> [--json]
Discards the staged guest config for a VM.
Disposition: rust-native.
Synopsis: d2b config status [<vm>] [--all] [--json]
Reports whether a VM (or, with --all, every VM) has a pending
(un-approved) staged guest config.
Disposition: rust-native — read-only.
All config verbs share these exit codes:
| Exit | Meaning |
|---|---|
0 |
Success (including diff whether or not files differ). |
1 |
Runtime error: nothing staged, a low-level public-socket I/O failure on config sync (send/receive frame), size-cap/timeout on the staging verbs, missing --to/--against target dir, I/O error. |
2 |
Usage error (bad/missing arguments; surfaced by clap), or config sync SSH-shaped flags rejected on a guest-control VM (guest-control-ssh-flag-rejected). |
70 |
config sync only. The VM is not declared in the active manifest (require_known_vm); the VM's generation does not declare the guest-control transport (guest-control-unavailable-old-generation); the daemon socket is unreachable (guest-control-transport-unavailable); or a per-kind guest-control read error (guest-control-file-not-found, guest-control-file-too-large, guest-control-path-unsafe, guest-control-read-denied, guest-control-timeout, guest-control-protocol-error, guest-control-auth-failed, guest-control-capability-unavailable). The staging-only verbs (diff/approve/reject/status) do not consult the manifest or transport and so never return 70. |
75 |
config sync only. The caller is not in d2b.site.adminUsers. config sync dispatches the admin-only ReadGuestConfig daemon verb, so a launcher-role peer is rejected with the typed authz-not-admin (AUTH) error — exit 75, the daemon's reserved authz code — before any guest read. The staging-only verbs (diff/approve/reject/status) dispatch no daemon verb and so never return 75. |
With --json each verb emits a single stdout object:
config sync→{ "command": "config sync", "vm", "transport": "guest-control", "staging", "bytes", "sha256" }(or{ "command": "config sync", "mode": "dry-run", "vm", "transport": "guest-control", "staging", "guestFile" }under--dry-run— no SSH argv, no guest bytes).config diff→{ "command": "config diff", "vm", "against", "staging", "differs": <bool>, "diff": <string> }.config approve→{ "command": "config approve", "vm", "target", "bytes" }.config reject→{ "command": "config reject", "vm", "removed": <bool> }.config status→{ "command": "config status", "pending": [ <vm>… ] }(the single-VM form reports a list with 0 or 1 entry).
Pending-staging notes (d2b status, d2b vm start, and the mutating
verbs) are emitted on stderr for human output only, so they never perturb a
--json stdout envelope.
Synopsis: d2b shell <target> [ACTION] [--name NAME] [--force] [--json|--human]
ACTION is one of:
- omitted or
attach— attach to the target's configured default shell session, or to--name NAME; list— list persistent shell sessions;detach— detach a live/stale client without killing the shell;kill— terminate a named shell session.
The first positional after shell is always a d2b target address. Declared
local VM names retain their existing behavior. Canonical direct-local workload
targets and unambiguous workload-id aliases resolve inside d2bd: transition
local VMs use legacyVmName, first-class local VMs use the workload id, and
unsafe-local targets stay canonical rather than being coerced to VM names.
A local VM named list, attach, detach, or kill attaches by default; use
d2b shell <target> <ACTION> for management. Command-like trailing words such as
d2b shell work htop are rejected with a hint to use
d2b vm exec <target> -- <cmd> for one-off commands.
shell keeps declared local VM names on the local daemon public socket and the
authenticated guest-control terminal transport. Unsafe-local targets use the
same public ShellOp shape, but d2bd resolves bundle-owned policy and the exact
requester-UID helper, then multiplexes the validated helper terminal fd behind
an opaque public attachment handle. List/detach/kill use helper management
operations. Disconnect and closeAttach detach only; kill tears down only the
verified shell scope. Gateway-backed management forms
(list, detach, kill) resolve the local realm entrypoint, verify the gateway
VM is running, and run the same d2b shell <target> ... command inside the
gateway VM over the typed vm exec guest-control path. The host does not load
realm credentials, provider transports, raw guest-control frames, SSH, or
provider-native shell APIs.
All shell actions remain admin-only. Launcher authorization for configured exec
items does not extend to shell. Unsafe-local policy (defaultName and
maxSessions) never appears in the public request and cannot be supplied by a
client.
Interactive gateway attach is fail-closed in this generation with an
actionable gateway-shell-attach-unavailable error. Use
d2b realm enter <realm> and run d2b shell <target> inside the
gateway until semantic ADR 0039 shell attach is implemented. ADR
0039 defines the final
constellation route: gateway-backed targets forward through the selected gateway
and require the remote node or provider agent to advertise persistent-shell.
Flags
| Flag | Applies to | Semantics |
|---|---|---|
--name NAME |
attach, detach, kill | Persistent shell session name. Omitted attach/detach uses the configured default; kill requires --name. |
--force |
attach | Detach an already-attached client for the same named session before attaching. |
--json |
list, detach, kill | Emit one JSON document on stdout. Attach is human/TTY-only and rejects JSON. |
--human |
list, detach, kill | Force human output. Attach is always human/TTY-only. |
Shell name rule
Names are 1-64 ASCII bytes, start with [A-Za-z0-9_], and then contain only
[A-Za-z0-9._-]. Names are user-visible operational identifiers, but daemon
metrics never use names or terminal handles as labels.
Human examples
$ d2b shell work
attached to shell 'default' on vm 'work'; detach with Ctrl-Space Ctrl-q; exit or Ctrl-D ends the session
$ d2b shell tools.host.d2b
attached to shell 'primary' on vm 'tools.host.d2b'; detach with Ctrl-Space Ctrl-q; exit or Ctrl-D ends the session
$ d2b shell work list
NAME STATE ATTACHED DEFAULT
default detached false true
--json examples
{
"command": "shell list",
"vm": "work",
"default_name": "default",
"sessions": [
{
"name": "default",
"state": "detached",
"attached": false,
"is_default": true
}
]
}{
"command": "shell detach",
"vm": "work",
"name": "default",
"result": "already-detached-or-absent",
"cause": null
}{
"command": "shell kill",
"vm": "work",
"name": "build",
"result": "killed",
"state": "killed"
}The JSON field remains named vm for the current schema. For local VM targets
it contains the resolved backing VM name; for unsafe-local it carries the
configured canonical workload target. Gateway-backed management commands
forward the requested target through the selected gateway; the in-gateway
response keeps its own current schema until a future output-version bump can
rename this field to target.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success, including idempotent detach/kill no-op results. |
1 |
Unexpected daemon reply or local protocol/serialization failure. |
2 |
Usage error, invalid flag combination, missing required --name for kill, invalid shell name, non-TTY attach, or gateway-backed interactive attach before semantic shell attach support lands. |
42 |
Internal scope/daemon failure. |
69 |
Daemon/helper/user-manager/terminal transport unavailable or timed out. |
70 |
Required shell capability or unsafe-local-shell-v1 is unavailable. |
75 |
Admin authorization failed, another attachment owns the shell, or capacity is exhausted. |
76 |
Protocol, operation-correlation, name, cursor-gap, offset, or terminal-size failure. |
77 |
Stale public attachment handle or authenticated guest session. |
Redaction
Shell management JSON may include validated shell names because they are operator-facing identifiers. Daemon audit records use a fixed shell correlation digest and may include the configured canonical target and peer uid; metrics labels are closed provider/component/operation/outcome/error enums. Neither surface carries shell names, terminal session handles, helper diagnostics, supervisor metadata, paths, argv, env, cwd, transcripts, or terminal bytes.
Synopsis: d2b vm exec [-i] [-t] [-d|--detach] [--env KEY=VALUE]… [--cwd DIR] [--json|--human] <vm> -- <cmd> [args…]
Detached management synopsis:
d2b vm exec [--json] <vm> listd2b vm exec [--json] <vm> logs <exec-id>d2b vm exec [--json] <vm> status <exec-id>d2b vm exec [--json] <vm> kill <exec-id>
Runs or manages commands inside a running VM over the authenticated
guest-control transport: the CLI opens an owner connection to the
daemon public socket, the daemon reaches the VM's guestd over the
authenticated guest-control vsock channel, and the endpoints exchange
typed exec operations. There is no SSH and no host PTY — the
guest owns the PTY. Exec is admin-only (the same SO_PEERCRED admin
gate as other privileged verbs); a launcher-role caller is rejected with
the typed authz-not-admin error (exit 77, AUTH) before any guest
session or detached create is attempted.
Status
vm exec is a rust-native, daemon-backed guest command surface. It
supports attached, interactive, detached, and detached-management forms.
Native
- The Rust CLI owns parsing, terminal safety, detached-management rendering, and the single terminal JSON envelope contract.
Bash
- There is no live bash fallback for this verb; old-generation VMs fail closed with a typed guest-control error.
Flags
| Flag | Type | Default | Semantics |
|---|---|---|---|
-d, --detach |
boolean | false |
Start a non-interactive detached exec and print its exec_id. Incompatible with -i/-t. |
-i, --interactive |
boolean | false |
Forward host stdin. Must be paired with -t; -t implies stdin forwarding. |
-t, --tty |
boolean | false |
Allocate a guest PTY and put the host terminal in raw mode. Human-only. |
--env |
KEY=VALUE |
repeatable | Add one environment variable to the guest command after policy filtering. |
--cwd |
path | unset | Working directory for the guest command. |
--json |
boolean | false |
Emit a single terminal JSON envelope. Human-only interactive modes reject this flag. |
--human |
boolean | false |
Force human output. |
--stdout-offset |
byte offset | 0 |
logs only. Resume retained stdout from this byte offset; accepts --stdout-offset N or --stdout-offset=N. |
--stderr-offset |
byte offset | 0 |
logs only. Resume retained stderr from this byte offset; accepts --stderr-offset N or --stderr-offset=N. |
--max-len |
byte length | daemon default | logs only. Request at most this many retained bytes per stream; accepts --max-len N or --max-len=N. |
Arguments
| Argument | Semantics |
|---|---|
vm |
Required VM name as declared in d2b.vms.<name>. Management words such as status and logs are valid VM names because command execution always uses --. |
cmd [args…] |
Guest command and arguments after --. argv[0] may be a bare command name, absolute path, or relative path; it must be non-empty and must not start with -. |
list |
Detached management verb: list retained detached exec metadata. |
logs <exec-id> |
Detached management verb: emit retained stdout/stderr bytes plus bounded metadata warnings, optionally starting at per-stream offsets. |
status <exec-id> |
Detached management verb: print state, terminal disposition, and aggregate retained-log metadata. |
kill <exec-id> |
Detached management verb: request cancellation; repeated cancellation of a terminal exec reports already-terminal. |
Exec command forms always require -- before <cmd>. Tokens after
<vm> without -- are management verbs; an unknown verb is a usage
error that tells the operator to use -- to run a command. This means
list, logs, status, and kill remain valid VM names:
d2b vm exec list -- bash runs bash in a VM named list, while
d2b vm exec list status <id> asks that VM for a detached exec's
status.
Execution identity and command resolution. Every attached and
detached exec runs the requested command as the VM's configured workload
user (ssh.user) — never root — inside a real PAM login session
(systemd-run --property=PAMName=login --uid=<user>). The command sees
the same login environment an interactive SSH login would
(XDG_RUNTIME_DIR, WAYLAND_DISPLAY, login-shell profile). argv[0]
may be a bare program name or relative path; the workload user's login
shell resolves it through that user's login PATH before exec. The
wire user field is host-fixed by guestd and ignored; operators
elevate with sudo inside the session. The console replacement is:
$ d2b vm exec -it corp-vm -- bashModes:
- Attached, non-interactive (default): stdin is closed up front; stdout and stderr are streamed back as separate streams and written to the host's stdout/stderr.
-t/--tty: allocates a PTY in the guest, puts the host terminal in raw mode for the session, and forwards host stdin to the guest command (-timplies-i). stderr is merged into stdout by the guest PTY. Requires stdin and stdout to be a terminal. Interactive modes are human-only:-t(and-i) are rejected together with--json.-i/--interactive: forwards host stdin to the guest command (non-blocking, partial-write aware) until EOF, which closes the guest stdin. The guest-control transport forwards stdin only in PTY mode, so-imust be paired with-t(use-it);-iwithout-tis a usage error.-d/--detach: creates a non-interactive detached exec, prints the opaqueexec_id, and returns without streaming stdio. The command continues after the host client disconnects.-dis rejected with-ior-t, and it still requires a command after--.
Attached FSM (one session, one exec, no per-op reconnect): the CLI
drains enqueued host signals, forwards ready stdin, then
bounded-long-polls stdout (and, in non-tty mode, polls stderr) so stdin
and signals are never starved behind an output poll. When both streams
reach EOF it polls Wait until the terminal disposition is known,
having already flushed all output. Host terminal state (termios +
O_NONBLOCK) is restored on every exit, error, disconnect, or panic
via an RAII guard.
Attached signal forwarding (enqueue-only; handlers never touch termios
or make syscalls): SIGWINCH → guest PTY Resize (tty mode only);
SIGINT → guest signal 2; SIGQUIT → 3; SIGHUP → 1;
SIGTERM → 15; SIGTSTP → 20, all delivered to the exec's
foreground process group.
Detached management:
- create —
d2b vm exec -d <vm> -- <cmd> [args…]: human output is one copy-pasteableexec_idline. JSON emits{ "command": "vm exec", "vm": "<vm>", "execId": "<id>", "state": "<state>" }. - list —
d2b vm exec <vm> list: human output is a table withexecId, state, start time, terminal status when available, aggregate and per-stream retained offset windows, and aggregate/per-stream dropped/truncated metadata. JSON emits{ "command": "vm exec list", "vm": "<vm>", "execs": [ { "execId", "state", "startedAt", "exitCode"?, "signal"?, "startOffset", "endOffset", "droppedBytes", "truncated" } ] }; implementations also expose per-stream stdout/stderr offsets and dropped/truncated flags for resume-capable clients. - status —
d2b vm exec <vm> status <exec-id>: human output is the state plus terminal disposition. JSON emits{ "command": "vm exec status", "vm": "<vm>", "execId": "<id>", "state", "reason"?, "exitCode"?, "signal"?, "startOffset", "endOffset", "droppedBytes", "truncated" }. - logs —
d2b vm exec <vm> logs <exec-id>: human output writes retained stdout/stderr bytes to the corresponding host streams and prints only bounded metadata warnings to stderr when bytes were dropped or truncated. An expired detached record is a typed failure (guest-control-exec-expired, exit76), not a warning. JSON emits{ "command": "vm exec logs", "vm": "<vm>", "execId": "<id>", "stdoutBase64", "stderrBase64", "startOffset", "endOffset", "droppedBytes", "truncated" }, plus per-streamstdoutStartOffset/stdoutEndOffset/stdoutNextOffset/stdoutEofandstderrStartOffset/stderrEndOffset/stderrNextOffset/stderrEoffields for offset resume. Logs are bounded ring buffers; dropped and truncated accounting is metadata, not log content. - kill —
d2b vm exec <vm> kill <exec-id>: public name forExecCancel. Guestd requests graceful termination, waits a bounded grace window, then force-kills the workload if needed. The operation is idempotent: human output confirms the result, and JSON emits{ "command": "vm exec kill", "vm": "<vm>", "execId": "<id>", "result": "cancelling"|"already-terminal", "state": "<state>" }.
Detached exec is supervised inside guestd and its in-guest detached
runner. It does not add a privileged broker operation. Guestd reconciles
detached runner/workload units before advertising detached capability,
re-adopts structurally valid work, cleans orphaned workload units, and
runs a periodic reaper for terminal records and retained-log slots.
Exit codes
| Exit | Source | Meaning |
|---|---|---|
0 |
cli | Detached create/list/status/logs/kill succeeded. |
0–255 |
guest | Attached guest command WIFEXITED status passes through unchanged. |
128+N |
guest | An attached guest command was killed by signal N (WIFSIGNALED). |
2 |
cli / guest-control | Usage error: missing command after --, unknown management verb without --, missing detached exec id, malformed --env, -d with -i/-t, -t without a terminal, -i without -t, --json combined with -i/-t, or guest-side INVALID_PROGRAM (guest-control-invalid-program) for an empty/leading-- program. |
69 |
transport | The guest-control transport was unreachable, a per-op/establishment deadline elapsed, or guestd disappeared before the exec reported a terminal status (guest-control-transport-unavailable, guest-control-timeout, guest-control-lost-guestd). |
70 |
guest-control | The VM generation does not support guest-control exec, or it lacks a required exec capability (guest-control-unavailable-old-generation, guest-control-capability-unavailable, guest-control-exec-detached-unavailable). No SSH fallback. |
75 |
guest-control | The exec session table is at capacity, Start was rate limited, or an established session was cancelled/reaped before a terminal guest status arrived (exec-session-capacity, exec-session-rate-limited, exec-session-cancelled, exec-session-reaped). |
76 |
protocol / guest-control | The guest returned a malformed/out-of-contract response, returned an op error, or no longer retains the requested detached record (guest-control-protocol-error, guest-control-exec-error, guest-control-exec-not-found, guest-control-exec-expired). |
77 |
guest-control | The authenticated guest-control handshake was rejected (guest-control-auth-failed), the daemon's admin gate refused a non-admin caller (authz-not-admin), or a stale exec session was detected (guest-control-stale-session). |
42 |
internal | Daemon-internal or CLI-internal failure driving exec. |
Human example
$ d2b vm exec work -- id
uid=1000(alice) gid=100(users)
$ d2b vm exec work list
EXEC ID STATE STARTED AT EXIT/SIGNAL OFFSETS DROPPED/TRUNCATED
exec-1 exited 2026-06-15T00:00:00Z exit=0 all=4..18 stdout=4..8 stderr=9..18 all=5/truncated stdout=2/truncated stderr=3/complete
$ d2b vm exec work logs exec-1 --stdout-offset=4 --stderr-offset=9 --max-len=4096
OUT
ERR
d2b: vm exec logs: retained output incomplete (startOffset=4 endOffset=18 droppedBytes=5 truncated=true stdoutStartOffset=4 stdoutEndOffset=8 stdoutNextOffset=10 stdoutEof=false stdoutDroppedBytes=2 stdoutTruncated=true stderrStartOffset=9 stderrEndOffset=18 stderrNextOffset=21 stderrEof=true stderrDroppedBytes=3 stderrTruncated=false)
Detached JSON shapes are generated as
vm-exec-create.schema.json,
vm-exec-list.schema.json,
vm-exec-status.schema.json,
vm-exec-logs.schema.json, and
vm-exec-kill.schema.json.
--json example
{
"command": "vm exec logs",
"vm": "work",
"execId": "exec-1",
"stdoutBase64": "T1VUCg==",
"stderrBase64": "RVJSCg==",
"startOffset": 4,
"endOffset": 18,
"droppedBytes": 5,
"truncated": true,
"stdoutStartOffset": 4,
"stdoutEndOffset": 8,
"stdoutNextOffset": 10,
"stdoutEof": false,
"stdoutDroppedBytes": 2,
"stdoutTruncated": true,
"stderrStartOffset": 9,
"stderrEndOffset": 18,
"stderrNextOffset": 21,
"stderrEof": true,
"stderrDroppedBytes": 3,
"stderrTruncated": false
}A guest command that itself exits 70 (or any reserved transport
number) is not ambiguous in machine-readable output: attached
--json carries source plus guestExitCode/transportExitCode so a
consumer distinguishes a guest exit code from a transport class that
shares the shell status number.
Attached --json envelope (non-interactive, non-detached only): a
single terminal stdout object.
- success →
{ "command": "vm exec", "vm", "source": "guest", "exitCode", "reason": "exited"|"signaled", "guestExitCode"?|"signal"?, "stdoutBase64", "stderrBase64", "stdoutTruncated", "stderrTruncated" }. Only a true guestWIFEXITED/WIFSIGNALEDterminal is a success. - failure →
{ "command": "vm exec", "vm", "source": "transport"|"guest-control"|"protocol"|"internal"|"cli", "reason": "<wire-kind>", "exitCode", "transportExitCode"?, "message", "remediation"? }. Abnormal terminal kinds (lost-guestd,cancelled,reaped) and a malformed/missing terminal status are failures with a reserved code and a non-guestsource — never a synthesized guest exit. A failure envelope never carries captured stdio bytes. Usage errors (source: "cli", exit2) also emit one envelope.
Captured output in JSON envelopes is bounded; stdoutTruncated /
stderrTruncated flag a clamp. argv, env, cwd, and stdio bytes never
appear in any span, log, audit record, or metric label. Attached exec
emits only an aggregate outcome counter and a single kind=critical
session-establishment event (VM name, peer uid, negotiated tty).
Detached create/kill daemon audit is similarly redacted: VM, peer uid,
closed action/result enums, and the opaque exec id only.
Disposition: rust-native — daemon public socket → authenticated
guest-control session → guestd exec RPCs; no SSH, no host PTY, no new
privileged broker op (attached sessions live in-process in d2bd;
detached state lives in guestd's detached registry).
| Command | Current disposition | Rationale |
|---|---|---|
list |
rust-native |
Pure read-only inventory query; the daemon answers it without mutating host or guest state. |
vm start |
rust-native |
The Rust CLI owns parsing and dry-run DAG output; --apply routes through the daemon-backed SpawnRunner path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
vm stop |
rust-native |
The Rust CLI owns parsing and dry-run DAG output, including explicit --force / -f stop intent; --apply routes through the daemon-backed SignalRunner path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
vm restart |
rust-native |
The Rust CLI owns parsing and dry-run DAG output, including explicit --force / -f stop-phase intent; --apply routes through the daemon-backed stop+start sequence. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
vm list |
rust-native |
Daemon-side runtime inventory from d2bd's public socket; daemon-unavailable returns an explicit empty inventory with remediation text. |
status |
rust-native |
Status is a read-only daemon RPC, including the frozen per-VM JSON shape. |
status --check-bridges |
rust-native |
The bridge-health probe is part of the read-only status surface, even though reconcile remains deferred. |
usb attach |
rust-native |
USBIP attach parses and dispatches one intent to d2bd; the daemon coordinates broker host bind/firewall/proxy state and authenticated guestd import over guest-control. |
usb detach |
rust-native |
USBIP detach parses and dispatches one intent to d2bd; the daemon asks guestd to detach matching imports, then runs broker UsbipUnbind / UsbipProxyReconcile. |
usb probe |
rust-native |
USBIP probe is a read-only daemon query backed by the broker's UsbipProxyReconcile validation pass. |
console |
rust-native |
The Rust CLI owns help / argument validation and attaches to the daemon-native foreground console handoff via ConsoleOp, with provider-capability-aware streaming across Cloud Hypervisor, qemu-media, and ACA targets; see provider capability matrix. |
audio status |
rust-native |
The Rust CLI dispatches AudioOp::Status and renders provider-capability-aware per-target audio state/errors across Cloud Hypervisor, qemu-media, and ACA; see provider capability matrix. |
audio mic |
rust-native |
The Rust CLI dispatches microphone grant/revoke through AudioOp::Mute, persisting policy and applying host/guest enforcement according to provider capability. |
audio speaker |
rust-native |
The Rust CLI dispatches speaker grant/revoke or level changes through AudioOp, persisting policy and applying host/guest enforcement according to provider capability. |
audio off |
rust-native |
The Rust CLI dispatches the off shorthand as audio mute operations for both directions, sealing supported host boundaries and reporting any degraded guest/provider enforcement. |
build |
rust-native |
Build is a native non-destructive planner that renders the eval/build preview without falling back to bash. |
switch |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunActivation path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
boot |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunActivation path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
test |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunActivation path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
rollback |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunActivation path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
generations |
rust-native |
Generations is a native introspection surface that reports current/booted symlink targets without falling back to bash. |
gc |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunGc path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
store verify |
rust-native |
Routes through d2bd → broker StoreVerify; the CLI never reads the store-view directly. |
trust |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunHostKeyTrust path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
rotate-known-host |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunRotateKnownHost path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
keys list |
rust-native |
Keys list is a native inventory preview that reports the managed-key resolution placeholders without falling back to bash. |
keys show |
rust-native |
Keys show is a native preview that reports daemon-resolved key metadata placeholders without falling back to bash. |
keys rotate |
rust-native |
The Rust CLI owns dry-run output; --apply routes through the daemon-backed RunKeysRotate path. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
audit |
rust-native |
Audit is part of the daemon surface and keeps both human and JSON output contracts. |
host check |
rust-native |
Host check is a read-only daemon RPC by design. |
host prepare |
rust-native |
The Rust CLI owns dry-run output (wired live); --apply is not yet wired — it returns the typed daemon-down envelope (exit 1) today (use --dry-run for now). When the daemon-side dispatch ships, --apply will route through the daemon-backed ApplyNftables / ApplyRoute / ApplySysctl / UpdateHostsFile / ApplyNmUnmanaged sequence, with broker failures surfacing broker-error (exit 78); a Tier-0 host is refused today (exit 78). The historical bash fallback was retired in v1.0. |
host destroy |
rust-native |
The Rust CLI owns dry-run output (wired live); --apply is not yet wired — it returns the typed daemon-down envelope (exit 1) today (use --dry-run for now). When the daemon-side dispatch ships, --apply will route through the reverse-order daemon-backed host-reconcile sequence, with broker failures surfacing broker-error (exit 78); a Tier-0 host is refused today (exit 78). The historical bash fallback was retired in v1.0. |
host doctor |
rust-native |
Host doctor is a read-only daemon health probe; --read-only is mandatory and there is no bash fallback for mutation forms. |
host migrate-storage |
rust-native |
Storage cutover dry-run planning is native and read-only; --apply / --rollback fail closed until the broker-backed mover lands. |
host install |
rust-native |
Host install owns its dry-run preview in Rust and routes --apply through the daemon → broker RunHostInstall path without broker-error fallback to bash. |
migrate |
rust-native |
Dry-run analysis is native; --apply routes through d2bd → broker RunMigrate. Daemon-unreachable / native-handler-deferred conditions surface typed envelopes (exit 1 / exit 78 per ADR 0015); the historical bash fallback was retired in v1.0. |
auth status |
rust-native |
Auth status is a read-only daemon query that reports caller mapping, socket reachability, and authorization hints. |
vm exec |
rust-native |
Daemon public socket → authenticated guest-control session → guestd exec RPCs. Admin-only; no SSH, no host PTY, no new privileged broker op. Attached exec uses the in-process d2bd session table; detached exec uses guestd's detached registry and VM-first management verbs. |
shell |
rust-native |
Admin-only provider-neutral ShellOp: local VMs use authenticated guest-control; unsafe-local uses the exact requester-UID helper and a multiplexed terminal fd. No SSH, host-shell fallback, root unit, per-VM service, or broker op. |