# mvirt 0.22.0

migrations: no
wire: unchanged
rev: 9f8a1675d00eb83e471d467e47f3eb21be36d85a

v0.22.0 — the control plane stops locking itself out

Two releases in a row are dominated by the same shape of defect: a part of the
platform could not reach the platform. 0.21.0 fixed the host firewall that
refused the platform's own guests at :80/:443. This release fixes the one that
took the region's API down on 2026-09-21 — the control plane ran out of file
descriptors and could then accept nothing, on any listener, while every daemon
still reported `active (running)`.

0.21.0 was published as a pre-release and never rolled, so its contents ship
here for the first time. A fleet on 0.20.0 takes this release directly.

Control plane
- The raft transport no longer dials a fresh connection per leadership-confirm
  round (#351). openraft asks the network factory for a client on every
  `ensure_linearizable`, and the prober runs one every 150 ms; the factory
  honoured that literally, so the leader opened ~13 TCP connections plus full
  mTLS handshakes per second, and a dial abandoned inside openraft's 100 ms
  heartbeat ttl was never closed. The production leader held 1005 dead-idle
  sockets after eight days against a 1024 soft limit — median socket idle for
  3.8 days, oldest as old as the process. The factory now owns one channel per
  peer, keyed by node id and revalidated against the address, so a moved member
  still redials and an abandoned task holds a clone rather than a socket.
- Both hand-written accept loops back off after a failed `accept` (50 ms
  doubling to 1 s). Retrying immediately is a spin under EMFILE, not a retry:
  one runtime worker at 90% CPU and journald suppressing ~10.6 million lines
  per 30 s window, which starved the control plane long before the descriptor
  count was visible anywhere.
- The cplane systemd unit sets `LimitNOFILE = 262144`. It previously set none
  at all, inheriting systemd's 1024 soft default, while the storage units in
  the same module deliberately raise theirs.

Carried from the unrolled 0.21.0
- The host firewall no longer gates its served ports on the ingress NIC; GRE
  and the storage wire stay scoped (#260, #346). This is what silently expired
  four managed certificates on 2026-09-19.
- A Task must actually re-run after its first failure.
- The log plane survives control-plane failover and a restart.
- Replicated volume creates the fleet can never place are refused up front.
- stor-agent hydration races that could certify a store swapped under the fill
  are closed.

Upgrading: this tag adds no migration of its own, but a fleet coming from
0.20.0 still crosses the two migrations 0.21.0 introduced (0138, 0139). Treat
the roll as migrating and use section C1a of docs/runbooks/release.md —
host-by-host is not a safe order on a multi-replica control plane.


## 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 adds no migrations, so the control-plane host rolls back the same way
a node does: boot the previous NixOS generation.
