v0.192.0 — the capture floor replaces the bulkhead (R-165, decision B2)
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
Ships BEFORE the disk-layout merge it exists for, and is harmless on a box that never gets it. The mp1 partition was a BULKHEAD as well as a ceiling: it kept a runaway capture from filling the space the container runtime needs, because /var/lib/docker was a different filesystem. After the merge it is the same one, and a full Docker data-root is a stopped box. The floor sits in captureAllRecoveryUnits, checked BEFORE anything is written: below the reserve, that ONE app's capture is refused, its previous unit is left byte-identical, the R-158 alert fires with the space figures, and the loop continues. Two terms whichever binds first (97% used / 1 GiB free) in fillwatch's shape, deliberately BEYOND its critical band (95% / 2 GiB) so the customer is always warned before a refusal can happen — a floor that fires before its own warning is a silent failure wearing a threshold. Headroom, never unit size: a per-unit cap would be R-163 rebuilt inside one volume. Refuses, never deletes: nothing here is generational, so pruning could only destroy a different app's only local copy; pruneStalePrimaryDirs is an orphan sweep, not retention, and must not be repurposed. Tests 1184 -> 1191. One fixture strengthened mid-red-proof: the "old 20 G ceiling is gone" test sat at exactly 20 GB and survived a literal UsedGB > 20 cap — hollow. Now 120 GB, and the mutation fails it.
This commit is contained in:
@@ -1,5 +1,50 @@
|
||||
## Changelog
|
||||
|
||||
### v0.192.0 — the capture floor replaces the bulkhead (2026-08-03, R-165 · decision B2) — MinAgent: none
|
||||
|
||||
**Ships BEFORE the disk-layout merge it exists for, and is harmless on a box that never gets it.**
|
||||
The `mp1`→`mp0` merge (R-165 / decision D-a) removes a wall that was quietly doing a second job: the
|
||||
20 G backup partition kept a runaway recovery-unit capture from filling the space the container
|
||||
runtime itself needs, because `/var/lib/docker` was a **different filesystem**. After the merge it is
|
||||
the same one, and a full Docker data-root is a stopped box, not a slow one. Decision **B2** is that
|
||||
bulkhead, done deliberately instead of by accident.
|
||||
|
||||
**The floor, in `captureAllRecoveryUnits`, checked BEFORE anything is written.** If the target
|
||||
filesystem is below the reserve, that **one app's** capture is refused, its previous unit is left
|
||||
**byte-identical**, the operator alert wired in v0.191.0 fires with the used/free figures, and the
|
||||
loop continues to the next app.
|
||||
|
||||
**Two terms, whichever binds first — 97% used or 1 GiB free** — the same shape as `internal/fillwatch`,
|
||||
which proved live on 2026-08-02 that a percentage alone is not enough (its critical alert fired on the
|
||||
free-byte term at 91% used, where a percent-only rule stayed silent).
|
||||
|
||||
**They sit deliberately BEYOND fillwatch's critical band (95% / 2 GiB), so the customer is ALWAYS
|
||||
warned before a refusal can happen.** A floor that fires before its own warning is a silent failure
|
||||
wearing a threshold; `TestFloorSitsBelowTheCriticalWarningBand` pins the whole ordering
|
||||
(warn → critical → refuse) on both terms, and a red-proof setting the floor equal to the critical band
|
||||
fails it.
|
||||
|
||||
**IT IS ABOUT THE FILESYSTEM'S HEADROOM, NEVER THE UNIT'S SIZE.** A per-unit cap would be R-163 rebuilt
|
||||
inside one volume — the wall moved rather than removed — so a 120 GB app on a filesystem with 180 GB
|
||||
free is captured. A red-proof substituting `UsedGB > 20` for the headroom predicate fails two tests.
|
||||
|
||||
**IT REFUSES; IT NEVER DELETES, and the reason is recorded because the question will be asked again.**
|
||||
Nothing on this filesystem is generational: a unit is ONE fixed path per app
|
||||
(`backups/primary/<app>`) refreshed in place, and a DB dump is `<stack>-<dbtype>.sql`, also fixed. So
|
||||
"prune the oldest" could only mean deleting a **different** app's only local recovery unit to make room
|
||||
for this one. `pruneStalePrimaryDirs` is **not** a retention policy — it removes ORPHANED directories
|
||||
left when an app moves drives and has no notion of age — and must never be repurposed here.
|
||||
|
||||
**A nil usage read neither refuses nor warns** (§8.4): an unreadable filesystem is the drive gate's
|
||||
business and already has its own alert, and refusing on it would block every capture on a box whose
|
||||
drive merely blipped.
|
||||
|
||||
**Tests:** 1184 → **1191** (+7). New `unitSpaceFn` seam so a filesystem's occupancy is a test input
|
||||
rather than something a test must manufacture on a real disk. One fixture was **strengthened** during
|
||||
the red-proofs: `TestFloor_TheOld20GCeilingIsGone` originally sat at exactly 20 GB and therefore
|
||||
survived a literal `UsedGB > 20` cap — a hollow test that passed the very shape it forbids. Its
|
||||
figure is now 120 GB and the mutation fails it.
|
||||
|
||||
### v0.191.2 — a quiet fill check now says so (2026-08-02, R-167) — MinAgent: none
|
||||
|
||||
**Earned during v0.191.1's own live validation, which is the strongest evidence it was needed.** After
|
||||
|
||||
Reference in New Issue
Block a user