Architecture overview
NXD keeps three concerns apart: the consumer repository owns what the infrastructure should be, the core engine owns what must change and in what order, and providers own how a specific system is actually mutated.
The boundaries are what make the engine testable and the providers replaceable. Each layer may only consume what the previous one produced.
Ownership boundaries
┌──────────────────────────────────────────────────────────────┐
│ Consumer repository │
│ Topology, inventory, addresses, storage, site ACLs │
│ SOPS/age encrypted secrets (references only, never values) │
└───────────────────────────┬──────────────────────────────────┘
│ evalConfiguration (offline)
▼
┌──────────────────────────────────────────────────────────────┐
│ Core engine — nxd-core │
│ Canonical JSON parsing and validation │
│ Action graph scheduling and risk classification │
│ Plan persistence, approval evidence, journaling │
└───────────────────────────┬──────────────────────────────────┘
│ provider port protocol
▼
┌──────────────────────────────────────────────────────────────┐
│ Providers — crates/providers/ │
│ nix NixOS and Darwin build, transfer, activation │
│ pve Proxmox VE API, PXE assets, QDevice witness │
│ pbs Proxmox Backup Server API │
│ headscale Tailnet control plane: users, keys, nodes, ACLs │
│ identity SOPS/age key sink and host trust │
│ vmware VMware Fusion and ESXi VMX substrate │
│ digitalocean, wsl │
└──────────────────────────────────────────────────────────────┘
The core never speaks a provider’s protocol, and a provider never decides ordering. A provider that learned to schedule its own work would break the one property the whole design exists to provide: that ordering across provider boundaries is decided in one place.
Evaluation and canonical JSON
nxd.lib.evalConfiguration (or evalConfigurationUnstable for in-development
schemas) evaluates consumer modules into a canonical JSON specification.
- Offline. Evaluation performs no network I/O, contacts no API, and decrypts no secret.
- Target-scoped.
lib.selectTargetInventoryextracts the resource subgraph for the selected targets, so planning one host does not evaluate every host in the site.
Offline evaluation is a correctness property, not a performance one: if evaluation could read live state, the plan would depend on the thing it is planning against, and two runs against a drifting endpoint could disagree.
Transport — nxd-transport
One shared SSH layer serves every provider.
- Multiplexed by default.
ControlMaster=yeswithControlPersist=10mreuses a single connection instead of paying a TCP and SSH handshake per remote command. - Host keys pinned. Every connection verifies the target’s key against the managed identity record. There is no prompt and no fallback.
- Jump hosts. Targets behind a bastion route through SSH proxy-jump without ambient credentials.
These are one decision, not two. Before the transport was shared, twelve non-test modules across four providers invoked SSH and exactly one multiplexed — which meant host key handling was also inconsistent. Consolidating fixed both.
Provider port
Providers implement four handlers, mirroring the pipeline:
| Handler | Responsibility | Mutates |
|---|---|---|
observe() | Report current live state | no |
plan() | Diff desired against observed, emit actions | no |
apply() | Execute approved actions | yes |
verify() | Assert live postconditions hold | no |
Only apply() mutates. plan() emitting an action is a proposal, not a
commitment — nothing runs until a plan carrying that action is persisted,
hashed, and approved.
Where to go next
- Safety model — the refusal set, approval binding, and risk levels.
- CLI reference — commands, intents, and flags.
- Resource schema reference — provider resource fields.