| Binary | Runs on | Holds |
|---|---|---|
dh-control |
1, 3 or 5 control-plane machines | Raft log and snapshots (bbolt), member identity and root-issued certificate, CAS of artifacts, optional Postgres mirror |
dh-noded |
every host | host identity, policy.yaml, journal.jsonl (hash-chained), admitted-state file, CAS, volume data, WireGuard key |
dh |
operator machines | cluster root key, root CA, config ($DH_HOME) |
- Replicated FSM. All desired state is changed by commands that go through Raft (hashicorp/raft on a mutual-TLS stream layer with root-issued member certificates). The FSM is deterministic: timestamps travel inside commands, and nothing reads the clock during apply. The same FSM holds the control-plane audit ledger, so every committed change has a ledger entry.
- Leader duties. Only a verified leader issues bundles, signs assignments
and reconciles. Followers forward writes and relay the leader's console view.
Every bundle carries
stateIndex, and hosts reject lower indexes, so a deposed leader or restored backup cannot roll a host back. - Reconciler (
reconcile.go). This is where desired state becomes scheduler plans (pkg/scheduler, deterministic and stability-preserving), then signed assignments with per-assignment capabilities, rolling updates withinmaxUnavailable, volume placement and restores, edge routing tables, and certificate issuance fortls: local. - Observations are verified (host key, monotonic
seq) and cached by the leader. They are committed to the log periodically, so followers and backups carry them without a Raft write per heartbeat. - Views (
views.go) project state and evidence for the console and CLI. Every value carries its truth basis (§11 of the spec).
A reconcile loop runs every second (--tick):
- Fetch and verify the bundle. This checks the pinned root, the root-signed roster, the member signature and rollback protection. The bundle is judged trusted, fresh and skew-free independently.
- Admit.
policy.Admitruns for every assignment, with its checks in a fixed order and hold semantics (spec §9.3). The decision and its checks are journaled when they change. - Converge the runtime. Artifacts are fetched from mesh peers first and
the control plane last, and each chunk is BLAKE3-verified. Admitted
generations then start. Nothing is stopped unless a trusted, fresh bundle
no longer lists it, a signed stop arrives, or local policy says
offlineAdmission: stop. - Observe and sign. The agent measures processes and containers, health,
the mesh, storage and edge, then signs an observation. If the control plane
is unreachable, observations go to a local outbox and are flushed later
marked
buffered.
Runtimes (pkg/runtime):
- process: executables started with
$PORT. Adoption across agent restarts uses a start token, so PIDs are verified, not assumed. - docker: pinned
sha256:images, with enforced--memory/--cpusand OOM detection.
wireguard-go runs on a gVisor netstack in userspace: no root and no kernel
module. Each host signs a wg-binding tying its WireGuard key and endpoint to its
dh1 id, and hosts configure only peers whose bindings verify. memberlist
(SWIM) gossip runs inside the tunnel. The peer API (/peer/v1/*, port 7800
on mesh addresses) serves verified chunks, bucket roots, ledgers and logs, and
exec only when policy allows it. Each assignment's port is exposed on the mesh
by a forwarder that lives as long as the assignment; restarts only retarget it.
- CAS. Content-addressed BLAKE3 objects, verified on every read, with
corrupt objects quarantined. Writes are durable per object (fsync), with
full barriers (
F_FULLFSYNCon macOS) at commit points. - Volumes. Each volume is chunked with FastCDC into snapshots (manifests
of file chunk lists), taken every
snapshotEveryMs. A snapshot is committed once two replicas send signedreplica-evidenceproving they hold every chunk. A moved replica restores the last committed snapshot, so an interrupted write rolls back cleanly. - Anti-entropy. Replicas compare 256 Merkle bucket roots, fetch only
differing buckets, and record every repaired object in
repair-evidence.
An L7 reverse proxy on edge hosts, over the mesh, fed by the bundle's routing table and by its own probes:
- endpoints are
routing,draining,ejectedorpending; - hung replicas are ejected on first-byte timeout;
- connections of a replaced generation drain.
TLS certificates come from the cluster-local CA or from ACME (HTTP-01, DNS-01, wildcards), and their states are reported verbatim.
The console is plain ES modules and CSS with no build step and no
dependencies. It is embedded into dh-control, served by every member under
a strict CSP (default-src 'self'), and revalidated on every load.
- Data. It polls
/api/v1/view(followers relay the leader's view). - Staleness. It never updates state optimistically. It marks the whole view stale when polls fail or stall.
- Rendering. Every interpolated string is escaped, because host names and reasons come from hosts.
- Controls. Keyboard-first, with a command palette and reduced-motion support.
- Development. Set
DH_CONSOLE_DIR=web/distto serve the console from disk while working on it.
devcluster starts real processes: members, hosts, edges, and optionally
Postgres or TLS. It can kill, restart, pause, partition (through UDP fault
proxies in front of WireGuard sockets) and inject host faults (clock offset,
ENOSPC, dropped control-plane connectivity). chaos runs 17 scenarios on
disposable clusters under traffic and signs a report for each.
Everything signed or hashed uses canonical JSON (spec §2): integers only, sorted keys, and duplicates, lone surrogates and invalid UTF-8 rejected. Transport uses JSON without HTML escaping, and verifiers canonicalize again anyway. See protocol/dh-v1.md.