controllerswap: F1 verify hardening — reject crash-looping no-healthcheck image v0.47.0
controllerHealthy reads RestartCount (running&&rc>0 -> not ok) + signals needsDwell for no-healthcheck; verify requires verifyDwell(=3) consecutive ok polls for a no-healthcheck image (real healthcheck trusted immediately). Closes the F1 hole (alpine crash-loop passed the point-in-time check). Red-proof + dwell + real-image tests. No sudoers/orchestration change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
This commit is contained in:
@@ -1,3 +1,23 @@
|
||||
## v0.47.0 — controller-swap verify hardening: reject a crash-looping no-healthcheck image (F1) (2026-06-29)
|
||||
|
||||
Closes F1 from the no-mercy testrun: a controller image with **no HEALTHCHECK that crash-loops** could
|
||||
land a single "Running" inspect poll → the swap marked it healthy → **no rollback** (alpine tagged as
|
||||
the controller passed in ~4 s, then `Restarting (0)`). The real controller image has a healthcheck so
|
||||
the live severity is low, but the rollback safety net had a hole.
|
||||
|
||||
- **`internal/localapi/controllerswap.go`:** `controllerHealthy` now also reads `{{.RestartCount}}` (a
|
||||
4th `docker inspect -f` field) — `running && RestartCount>0` → not-ok (a process that has already
|
||||
crash-restarted isn't stably up, regardless of healthcheck). It also signals `needsDwell` for the
|
||||
no-healthcheck (`none`) case. `verify` adds a **stability dwell**: a no-healthcheck image must report
|
||||
ok on `verifyDwell` (=3) **consecutive** polls before it's accepted; a real `healthy` result is
|
||||
trusted immediately (Docker already gated it). Any not-ok resets the dwell. Timeout → existing
|
||||
rollback path runs. No change to writeImage, the sudoers grants (the `*` in `docker inspect -f *`
|
||||
spans the extended template — confirmed live), or the state-file/rollback orchestration.
|
||||
- Tests: F1 **red-proof** (`RestartCount>0` → verify false; companion: rc=0+dwell=1 verifies → the rc
|
||||
check is what blocks it); the **dwell** (single ok then crash → verify false; companion dwell=1
|
||||
accepts it); a real `healthy` image verifies promptly (no false rollback). Existing
|
||||
`RollbackOnUnhealthy` / `HealthyWithNoHealthcheck` stay green. Version `0.46.0 → 0.47.0`.
|
||||
|
||||
## v0.46.0 — leaf lifecycle: signal + loud-log a regenerated leaf (prevention, Part B.1) (2026-06-29)
|
||||
|
||||
Makes an accidental local-API leaf **regeneration** (the 2026-06-28 root→non-root migration class —
|
||||
|
||||
Reference in New Issue
Block a user