# mvirt 0.8.0

migrations: yes
wire: unchanged
rev: 46db90f3f1ce0bc449b82cd80209263e50b9a97b

v0.8.0 — the highly-available control plane

Until this release a mvirt control plane was one process on one host. If that
host was gone, the API was gone with it — workloads kept running, but nothing
could be created, changed or observed until someone restored a backup. This
release makes the control plane survive the loss of any one of its hosts, and
makes an operator's only input for that the same one ADR-0071 already
promised: move a node into a control-plane node group.

What changed

* The raft mesh authenticates its members (ADR-0059 P2). Replication is the one
  wire carrying every row of the store, and it had no authentication of its own.
  It now speaks mutual TLS with certificates from the internal CA, and a peer
  must carry a control-plane member identity — the same CA signs a client
  certificate for every host node, so chaining to it is not enough to append to
  the store. A non-loopback raft bind without that material is refused at boot.

* The control-plane group grows the raft (ADR-0073 M3). Flagging a node group
  adds those hosts as voters, hands each its credentials over the tunnel it is
  already connected on, and starts a replica there. The voter count is never
  even; an Offline voter keeps its vote; the first grow waits for the whole
  fleet to run an agent that can rotate its endpoints. The secrets KEK travels
  sealed to a per-exchange key, never through the store, the raft log, a
  snapshot or a backup.

* The edge follows the leader (ADR-0073 M4). Every control-plane host's Envoy
  health-checks GET /v1/leadership and routes only to the member that answers,
  so a failover is a retry rather than an outage. The control plane's REST
  additionally binds the underlay with internal-CA TLS; loopback stays plaintext.

* A control-plane host will not reboot into a quorum loss. Activations and
  rollbacks on a control-plane node wait until every other member is Online and
  caught up, with an audited override for the member that is permanently gone.

Upgrading

Control-plane hosts first, one at a time, waiting for GET /v1/leadership to
answer 200 somewhere before moving on. Growing to three voters is documented in
docs/runbooks/control-plane-ha.md, including the two disaster-recovery paths and
the rule that decides between them. Note the cost this buys and charges for: a
three-voter control plane survives any one host, and a 2-of-3 outage is a full
control-plane outage, which a single voter did not have.

Every node's closure now ships the control-plane binary, inert. That is what
makes "move the node into the group" the whole story, and it grows each node
image and release artifact by it.


## Upgrading

Point your flake's `mvirt` input at this release and redeploy the control-plane
host first, then each node in turn:

```
nix run .#colmena -- apply boot --reboot --on <control-plane host>
nix run .#colmena -- apply boot --reboot --on <node>   # one at a time
```

**This release migrates the store.** Once the control plane has started, the
previous version will refuse to run against it — migrations are forward-only, so
rolling the control-plane host back means restoring a backup, with data loss back
to the backup point. The control plane writes a backup immediately before it
applies migrations; nodes roll back freely by booting the previous generation.
