# mvirt 0.14.15

migrations: no
wire: unchanged
rev: 421111987d623e8e0fad6385ee1829e7e26aa54c

0.14.15 — the ACME edge converges again

A patch release for one defect class: the managed-certificate reconciler on the
control-plane edge could enter states the level pass was unable to leave. On the
test environment those states lined up and expired api./console.mvirt.io, after
which the edge served both names a valid Let's Encrypt certificate for
*.storage.mvirt.io — a certificate every TLS client rejects by name, on a front
door that answers with HSTS.

What changed (all in mvirt-cplane):

- The ACME fleet gate now distinguishes an instance that has REPORTED and is
  stale (still blocks — a real agent serving the wrong generation, which LE
  could hit) from one that has NEVER reported (no longer blocks — an empty
  slot). `converge_host_edge` mints one `instance:{raft_id}` ledger row per raft
  member, because ADR-0073 D7 assumes an edge on every member, while the nix
  module deploys mvirt-gw-agent only on the bootstrap host; those rows pinned
  `applied_generation` at None and held the gate shut permanently. Growing a
  control plane from one member to three silently ended HTTP-01 issuance. The
  relaxation is scoped to the control-plane edge — a pod fleet keeps the strict
  rule, since there a ledger row does imply a pod that will report.

- `acme_order_url` is treated as what it is: a cache of state that lives at the
  CA. Every failure to read the order was previously classified transient, so
  the in-flight branch latched and a purged order was re-polled every 15 minutes
  forever. `poll_order` now reports `OrderProgress::Gone` for the permanent RFC
  8555 problem types (malformed, unauthorized, accountDoesNotExist, and 404),
  and the reconciler drops the order and derives a fresh one.

- The ADR-0068 D3 self-signed placeholder is withdrawn PER HOSTNAME rather than
  whenever any real certificate exists. A certificate covering a disjoint SAN
  set no longer removes the last-resort material from names it does not cover,
  and the non-SNI default chain is elected over real certificates only. This is
  what turned a certificate gap into a wrong-domain certificate.

- `edge_instance_is_down` re-derives the ledger role through `cp_instance_role`
  instead of comparing the role suffix as text. The role is minted from
  `raft_id as i64` while ControlPlaneMember.raft_id holds the u64 decimal, so
  for any raft id above 2^63 the lookup could never match and a provably dead
  control-plane host would also have parked renewal fleet-wide.

Five regression tests plus SAN-matching unit coverage. Closes #225.

Upgrade note: this is a control-plane-only change. Upgrade the control-plane
host; node agents are unaffected. Certificates parked at `fleet-converging` with
a dead order recover on their own within one backoff interval after the restart.


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