Skip to content

Latest commit

 

History

History
307 lines (263 loc) · 28.1 KB

File metadata and controls

307 lines (263 loc) · 28.1 KB

Error codes

This page is the canonical reference for d2b's shared exit-code anchors and stable typed-error kinds.

Operator-visible typed errors use stable leaf Kind discriminants and reserved exit codes in 10..=99. Shared command-local exits such as generic failures, usage errors, and legacy-bash lock conflicts are documented separately below so docs/reference/cli-contract.md can link to a real target for every anchor.

Shared non-typed exit-code anchors

These anchors document common process exits that are not emitted as d2b_core::error::Error envelopes.

docs anchor exit code meaning
#generic 1 Generic CLI failure outside the stable typed-error catalog.
#usage 2 Unknown flag, unsupported invocation shape, or other command-line usage error.
#readiness-timeout 4 up timed out while waiting for the VM to reach its documented ready state.
#net-vm-busy 4 down or restart refused to stop a net VM while dependent workload VMs were still active.
#bridge-unhealthy 4 status --check-bridges found a missing, administratively down, or carrier-missing bridge.
#drift 4 store verify found live-pool drift or could not establish integrity (unknown).
#peer-unreachable 3 An SSH-backed legacy command could not reach the VM at its declared endpoint.
#lock-conflict 5 A legacy-bash exclusive host lock was already held by another session.
#strict-deviation 1 A strict-mode command reported at least one deviation and converted that summary into a non-zero exit.
#host-check-warning 1 host check reported at least one advisory warning but no required failures.
#host-check-failure 2 host check reported at least one required failure in its summary output.
#not-found 70 Named VM is not declared in the active manifest or the caller is not authorized to know it exists.

Typed error catalog

The table below is generated by cargo xtask gen-error-codes from d2b_core::error::Error::all_kinds(). Each row is a stable leaf Kind discriminant with its reserved exit code, owning command, redacted message shape, remediation hint, and docs anchor.

docs anchor kind exit code owningCommand message template remediation
#authz-not-a-launcher authz-not-a-launcher 10 daemon-api/Hello peer uid {peer_uid} is not authorized to use the launcher API Add the caller to d2b.site.launcherUsers or retry with an authorized launcher account.
#authz-audit-requires-admin authz-audit-requires-admin 11 audit peer uid {peer_uid} is not authorized to read broker audit records Add the caller to d2b.site.adminUsers before retrying the audit read path.
#wire-version-mismatch wire-version-mismatch 20 daemon-api/Hello client version requirement {client} does not include server version {server} Upgrade or downgrade the client so its declared semver range includes the server version.
#wire-frame-too-large wire-frame-too-large 21 daemon-api/frame received frame of {received} bytes exceeds the {limit}-byte limit Reduce the request body so the framed JSON payload stays at or below 1 MiB.
#wire-unknown-field wire-unknown-field 22 daemon-api/request {type_name} rejected unknown field {field} Remove fields that are not documented for this request or response type.
#wire-ifname-invalid wire-ifname-invalid 23 daemon-api/request wire interface name is invalid: {reason} Use a Linux interface name that fits in IFNAMSIZ-1 and only contains [A-Za-z0-9_-].
#wire-malformed-json wire-malformed-json 24 daemon-api/request {type_name} could not be decoded from JSON (opaque reason: {opaque_reason}) Send a complete JSON object that matches the documented wire schema.
#guest-shell-disabled guest-shell-disabled 70 shell persistent guest shell is not available for this VM Enable d2b.vms..guest.shell when the shell runtime is available, rebuild the guest, and retry.
#broker-unimplemented broker-unimplemented 30 daemon-api/broker broker operation {operation} is not implemented in this build Upgrade to a build that implements this operation.
#broker-validation-failed broker-validation-failed 31 daemon-api/broker broker validation failed for opaque target {what} Re-render the trusted bundle artifacts and retry with the newly emitted opaque identifiers.
#manifest-parse-error manifest-parse-error 40 status could not parse manifest artifact {artifact} (opaque reason: {opaque_reason}) Re-render the manifest bundle and retry with the committed schema-compatible artifact set.
#manifest-version-mismatch manifest-version-mismatch 41 status manifest {artifact} declared an incompatible manifestVersion (opaque reason: {opaque_reason}) Re-run nixos-rebuild switch against an updated d2b input pinning the daemon's supported manifestVersion. Manifest version changes do not ship a compatibility window.
#internal-io internal-io 50 daemon-api/internal an internal I/O step failed (opaque reason: {opaque_reason}) Retry the command; if the error persists, inspect the daemon logs with the opaque reason token.
#bundle-tampered bundle-tampered 60 bundle-load bundle artifact {path} failed tamper-resistance check: {reason} Re-run nixos-rebuild switch to restore the bundle artifacts to their signed state.
#provider-misconfigured provider-misconfigured 80 provider provider for {vm} is misconfigured: {reason} Check the provider configuration for the VM and verify the expected guestd-compatible agent or sidecar is running.

Unsafe-local persistent-shell errors

These daemon wire errors carry only a closed kind. They never include helper diagnostics, shell names, public session handles, terminal bytes, supervisor metadata, paths, environment, or cwd.

docs anchor / kind exit Meaning and remediation
unsafe-local-shell-feature-unavailable 70 Update d2b, d2bd, and the helper together; no host-shell fallback is permitted.
unsafe-local-shell-helper-unavailable 69 Start the requesting user's d2b-unsafe-local-helper in a login session.
unsafe-local-shell-helper-stale 69 Restart the same-UID helper; d2bd rejected a stale generation.
unsafe-local-shell-capacity 75 Wait for pending work, or detach/kill an existing session before retrying.
unsafe-local-shell-timeout 69 Inspect helper/user-manager health and list sessions before retrying; daemon timeouts are not replayed automatically.
unsafe-local-shell-protocol-error 76 Update d2b, d2bd, and the helper together.
unsafe-local-shell-operation-conflict 76 Retry the public action so d2bd derives a fresh private operation id.
unsafe-local-shell-user-manager-unavailable 69 Log in through a PAM-backed session and start the user manager; d2b does not enable linger.
unsafe-local-shell-environment-invalid 70 Repair the user manager's runtime-directory and D-Bus environment, then restart the helper.
unsafe-local-shell-executable-unavailable 70 Configure an absolute executable login shell for the user.
unsafe-local-shell-scope-create-failed 42 Repair transient user-scope creation and retry.
unsafe-local-shell-scope-identity-mismatch 70 Restart the helper and inspect the degraded user scope; ambiguous metadata is not adopted or killed.
unsafe-local-shell-graphical-session-inactive 69 Start a graphical login session.
unsafe-local-shell-wayland-unavailable 69 Start the login session's Wayland compositor.
unsafe-local-shell-proxy-unavailable 69 Repair the d2b Wayland proxy; direct compositor fallback is forbidden.
unsafe-local-shell-first-client-timeout 69 Repair proxy readiness and retry.
unsafe-local-shell-unavailable 69 List sessions and attach again; attachment loss does not kill the shell.
unsafe-local-shell-not-found 76 List sessions and retry with a listed name.
unsafe-local-shell-already-attached 75 Detach the existing client or use --force.
unsafe-local-shell-output-gap 76 Reattach to redraw from the retained output cursor.
unsafe-local-shell-offset-mismatch 76 Reattach before writing more input.
unsafe-local-shell-terminal-closed 69 List and reattach; the persistent shell may still be running.
unsafe-local-shell-invalid-terminal-size 76 Retry from a terminal reporting non-zero rows and columns.
unsafe-local-shell-stale-session 77 Reattach to obtain a fresh opaque public handle.
unsafe-local-shell-internal 42 Retry, then inspect the bounded daemon lifecycle event if it persists.

CLI host-verb refusal envelope

The CLI host verbs (d2b host prepare, host destroy, host doctor, host install) use a richer 7-field operator-UX envelope distinct from the typed daemon-API envelope above. The shape is { kind, code, exit_code, what_was_checked, observed_state, remediation, docs_anchor }; the goldens under tests/golden/cli-output/host-*-*.{json,txt} are the authoritative spec.

The relationship between the two envelopes:

  • Daemon-API typed envelope ({kind, code, message, remediation, docsAnchor, owningCommand} per daemon-api.md) is what d2bd, the broker, and the wire layer emit. CLI surfaces that proxy daemon-API responses (e.g. host check, auth status, audit) deserialize and re-render this shape.
  • CLI host-verb refusal envelope (the 7-field shape used by the host verbs above) is what the CLI itself emits when it intercepts a request before reaching the daemon, e.g. legacy refusals, missing-flag usage errors, or typed daemon-down / broker-error / not-yet-implemented carve-outs. The richer what_was_checked / observed_state pair gives operators the context the typed envelope's bare message field omits.

Both envelopes anchor into the catalog below; the CLI's code matches an #anchor here. The goldens are the contract.

docs anchor code exit code what_was_checked observed_state
#daemon-down daemon-down 1 Daemon connectivity at /run/d2b/public.sock and broker dispatch readiness. d2bd is reachable, but the daemon-side typed-intent dispatch and bundle resolver that back host prepare/destroy --apply are not yet wired through d2bd (the broker op is staged but not yet reachable from the public socket).
#broker-error broker-error 78 Daemon → broker execution for d2b <verb> --apply. The daemon reached the live broker executor, but the broker refused or failed the request. Disk-init failures use a broker-error:DiskInit:<broker-kind> shape when the broker fails while creating a declared image via fallocate + mkfs.ext4 or when automatic declared-posture repair is bypassed because the existing image is ambiguous or unsafe.
#not-yet-implemented not-yet-implemented 78 Whether the requested native surface is shipped in this release. The requested native backend is deferred; the CLI returns a typed exit-78 envelope instead of falling back to bash.
#--read-only-required --read-only-required 78 host doctor invocation flags. --read-only flag missing. The current host doctor verb is read-only; mutation forms are separate surfaces.
#--apply-or-dry-run-required --apply-or-dry-run-required 78 host prepare / host destroy / host install invocation flags. Neither --dry-run nor --apply was provided; these host verbs require one of the two to disambiguate plan vs mutate.

Note: the tier-0-legacy-uses-nixos-module and single-writer-conflict codes are anchored further down in the "host-prepare audit decision codes" catalog because they originate as broker-side audit decisions; the CLI re-uses the same docs anchors when intercepting them before reaching the broker.

vm exec exit codes

d2b vm exec applies its own reserved exit-code contract on top of the daemon wire kind, so the CLI exit is stable regardless of the daemon's fallback exitCode. A guest command's own WIFEXITED status (0-255) passes through unchanged and CAN collide with these reserved numbers; a --json run disambiguates via source + reason + guestExitCode / transportExitCode (see cli-contract.md). The reserved numbers avoid the pre-existing CLI exits 2/3/33/78.

docs anchor wire kind exit code source meaning
#exec-transport guest-control-transport-unavailable, guest-control-timeout 69 transport The vsock connect / authenticated handshake to the guest was unreachable, or a per-op / establishment deadline elapsed. The lost-guestd abnormal terminal maps here too.
#exec-old-generation guest-control-unavailable-old-generation, guest-control-capability-unavailable 70 guest-control The VM generation does not advertise guest-control exec, or it lacks a required exec capability. Fail-closed: no SSH fallback.
#exec-capacity exec-session-capacity, exec-session-rate-limited 75 guest-control The exec session table is at its global / per-uid / per-vm cap, or the per-uid Start rate limit fired. The cancelled / reaped abnormal terminals map here too.
#exec-protocol guest-control-protocol-error, guest-control-exec-error 76 protocol The guest returned a malformed / out-of-contract response, or deterministically rejected the op. A missing or out-of-range terminal status is also a protocol failure (never synthesized as a guest success).
#exec-auth guest-control-auth-failed, authz-not-admin 77 guest-control The authenticated guest-control handshake was rejected, OR the daemon's admin gate refused the caller (vm exec is admin-gated). authz-not-admin is an authorization failure, not an internal bug, so it maps to the AUTH reserved code rather than the internal default.
#exec-internal guest-control-exec-internal, any unrecognized exec slug 42 internal A daemon-internal or CLI-internal failure driving the session. The 42 default is reserved for genuinely-internal failures only; every known authorization / transport / protocol class above maps to its own reserved code.

vm exec --json always emits exactly one terminal JSON document on stdout for every outcome (including early transport / auth / old-generation establishment failures); a usage error (bad --env, --json combined with -i/-t, missing command) is source: "cli", reason: "usage", exit 2.

Host-prepare audit decision codes

The host-prepare path (cgroup delegation + pidfd handoff, network reconcile, firewall coexistence, modules + devices + runner-shape) introduces a family of kebab-case audit decision codes that the privileged broker writes to its append-only audit log (/var/lib/d2b/audit/broker-<utc-date>.jsonl) as the error_kind field. They are not typed exit codes in the auto- generated table above; they are the broker's structured fail-closed reasons that map back to the audit record schema in docs/reference/privileges.md § "Audit record schema".

When surfaced through the daemon API, every audit decision code folds into the generic typed errors above:

  • A denied-refused decision → broker-validation-failed (exit 31) with the kebab-case code in the opaque reason field and the same code in the broker audit log's error_kind.
  • A denied-unknown decision (unknown variant / unknown field) → broker-validation-failed (exit 31) with error_kind= unknown-subject (or the analogous kebab code) and the trusted bundle hash for cross-reference.
  • An errored decision → internal-io (exit 50) with the same kebab-case error_kind in the audit record.

This split means CLI consumers continue to script against the small typed-error catalog above, while operators reading the audit log get the fine-grained kebab-case reason that the broker recorded.

Audit decision code catalog

Audit code Owner ADR Origin op(s) Maps to typed error
cgroup-controllers-missing 0011 DelegateCgroupV2 broker-validation-failed
cgroup-v2-unified-not-present 0011 DelegateCgroupV2 broker-validation-failed
cgroup-delegation-refused 0011 DelegateCgroupV2 broker-validation-failed
cgroup-kill-on-ancestor-refused 0011 CgroupKill (broker-mediated only — no daemon-direct cgroup.kill writes; see cgroup-delegation.md "Broker ops on the cgroup tree") broker-validation-failed
ifname-too-long 0012 CreateTapFd, CreatePersistentTap wire-ifname-invalid
ifname-collision 0012 bundle render + CreateTapFd broker-validation-failed
bridge-port-flag-drift 0012 SetBridgePortFlags broker-validation-failed
ipv6-sysctl-drift 0012 ApplySysctl broker-validation-failed
foreign-nft-rule-preserved 0013 ApplyNftables (informational; not a fail) n/a
nft-coexistence-firewalld-refuse 0013 ApplyNftables broker-validation-failed
nft-coexistence-ufw-refuse 0013 ApplyNftables broker-validation-failed
nft-coexistence-docker-require-unmanaged 0013 ApplyNftables broker-validation-failed
nft-coexistence-libvirt-require-unmanaged 0013 ApplyNftables broker-validation-failed
nft-coexistence-iptables-nft-priority-readback 0013 ApplyNftables broker-validation-failed
nft-coexistence-unknown-manager-refuse 0013 ApplyNftables broker-validation-failed
nft-coexistence-no-manager-coexist 0013 ApplyNftables (informational) n/a
nm-managed-foreign-conflict 0012 ApplyNmUnmanaged broker-validation-failed
nm-reload-required 0012 ApplyNmUnmanaged broker-validation-failed
route-preflight-no-default-route 0012 ApplyRoute broker-validation-failed
route-preflight-foreign-default-route 0012 ApplyRoute broker-validation-failed
dnsmasq-not-bound 0012 ApplyRoute (preflight) broker-validation-failed
host-lan-cidr-ambiguous 0012 ApplyRoute (preflight) broker-validation-failed
modules-disabled-sysctl-locked 0014 ModprobeIfAllowed broker-validation-failed
host-modules-locked 0014 ModprobeIfAllowed broker-validation-failed
modprobe-denied-not-in-matrix 0014 ModprobeIfAllowed broker-validation-failed
minijail-too-old 0014 runner-shape preflight broker-validation-failed
path-safety-violation 0012, 0013 UpdateHostsFile, ApplyNmUnmanaged, PrepareStateDir, PrepareRuntimeDir broker-validation-failed
single-writer-conflict 0011 any host-prepare op when legacy systemd + daemon coexist broker-validation-failed
ch-net-handoff-not-supported 0014 runner-shape preflight broker-validation-failed
runner-shape-drift 0014 runner-shape preflight broker-validation-failed
tier-0-legacy-uses-nixos-module 0014 host prepare --apply on the legacy NixOS-module path exit 78 (host-prepare carve-out)

host check --apply summarizes any denied-refused / errored decisions per VM and returns exit 2 (#host-check-failure) when any required prerequisite fails. Advisory-only denied-refused findings return exit 1 (#host-check-warning).

Remediation rendering conventions

The reference shape below is normative for the migration-guide cross-link remediation hint. The Rust-side rendering changes live in packages/d2b-core/src/error/remediation_rendering.rs and are wired into the typed-envelope Display impl for the four verbs listed below.

Most typed-error envelopes render Remediation: as a single-line hint inline with the envelope JSON / human pretty-printing. The historical inline rendering is preserved unchanged for every envelope EXCEPT the four cases below; operator grep patterns targeting other remediation strings are unaffected.

Multi-line Remediation: block format applies to ONLY these two CLI verbs when they emit #daemon-down or #not-yet-implemented envelopes pointing operators at the migration guide:

  • d2b audit (and d2b audit --strict)
  • d2b keys (read-only subcommands)

The multi-line block format renders differently for the two verb categories described above:

Category 1 — Truly deferred verbs (audit --strict) emit #not-yet-implemented (exit 78) unconditionally. The remediation block renders as:

Remediation:
  This subcommand is queued for a later release.
  See the operator migration runbook:
    docs/how-to/migrate-d2b-v0-to-v1.md
  Specifically the "<verb-specific anchor>" section.

Category 2 — Daemon-down rendering pointers (audit without --strict, keys list, keys show) emit #daemon-down (exit 1) only when the broker is stopped; otherwise the successful invocation path runs normally. When #daemon-down does fire, the remediation block renders as:

Remediation:
  d2bd is not reachable. Start the daemon and re-run:
    sudo systemctl start d2b-priv-broker.socket
    sudo systemctl start d2bd.service
  For the full operator runbook context, see:
    docs/how-to/migrate-d2b-v0-to-v1.md
  Specifically the "<verb-specific anchor>" section.

Indentation is exactly 2 spaces per nesting level; the URL on its own line ensures the full path is copy-paste-safe on terminals narrower than 80 columns (the old inline rendering wrapped the URL across lines on narrow terminals, breaking copy-paste). Operator-facing grep patterns previously matching Remediation: <inline-hint> on the two verbs above MUST switch to matching Remediation: as a line prefix followed by the multi-line block; grep patterns targeting other envelopes (e.g., #bridge-port-flag-drift, #nft-coexistence-*) keep working unchanged because their inline rendering is preserved.

The two affected verbs and their migration-guide anchors are:

d2b console and d2b audio (subcommands status|mic|speaker|off) are implemented and no longer emit #not-yet-implemented envelopes. Their golden files (console-deferred.golden, audio-deferred.golden) are retired. The Rust Display impl for the two remaining deferred verbs is verified byte-for-byte against tests/golden/cli-output/audit-*-deferred.golden and keys-deferred.golden.