# mvirt 0.21.0

migrations: yes
wire: unchanged
rev: 6be44759603b602959fce99aaafceaaeebb637da

v0.21.0 — the platform stops locking its own guests out of its own API

The release is dominated by one class of defect: a component of the platform could not
reach the platform. The host firewall admitted its served ports only on the public NIC,
so every VM and every pod was refused at :80/:443 on all three hosts — which silently
expired four managed certificates on 2026-09-19, because the PaaS ingress fleet polls
the control plane over exactly that endpoint and the ACME HTTP-01 fleet gate therefore
never saw a published challenge. Alongside it, the log plane learns to survive a
control-plane failover and a restart, a Task actually re-runs after its first failure,
and two stor-agent hydration races that could certify a store swapped under the fill
are closed.

Networking / deployment
- The host firewall no longer gates its served ports on the ingress NIC; GRE and the
  storage wire stay scoped (#260, #346). Guarded by a nix check falsified against all
  three regressions.
- The new egress counter counted traffic rather than the fault it names (#333).

Control plane
- A Task must actually re-run after its first failure — a settled pod stayed
  desired=Stopped and a template import stayed Importing forever.
- The log plane survives control-plane failover: replicated audit/events and a
  coverage-aware ring boundary, so resource logs no longer vanish with the replica that
  held them.
- A cplane restart no longer loses the un-flushed log window.
- Replicated volume creates the fleet can never place are refused up front, and a
  placement block now surfaces on Volume.status.error instead of a silent stall.
- Migration m0138 makes LogSegment.compaction_blocked writable on stores created before
  d7ab683 — an appended Option<T> column is free to read and rejected on write without it.

Storage
- stor-agent: the hydration barrier can no longer certify a store that was swapped under
  the fill, and the read-through is re-installed when a background-hydrated volume's
  record is lost.
- mvdb: restore skips NULL-bearing keys when rebuilding unique indexes.

Release tooling and docs
- release-facts.sh never shallows the working repository; the shipped example flake can
  no longer carry a stale pin.
- The runbook records the two constraints the 0.20.0 roll proved: force-switch an
  externally-routed prefix's anchor BEFORE that host reboots, and never roll a migrating
  release host-by-host on a multi-replica control plane until #334 lands.

Upgrading: this release migrates the store, so section C1a of docs/runbooks/release.md
applies on a multi-replica control plane — host-by-host is not a safe order.


## 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.
