mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
cd1cde2fd4
* fix(resources): subtract shared layers when accounting managed prune bytes Both `pruneManagedOnly` and `estimateManagedReclaim` walked the Sencho-managed prunable image set and summed `img.Size` per image. That counts shared base layers once per image, so a 1 GB base layer shared across N managed images was reported as N GB freed — the same shape of inflation we just fixed for the system-scope banner. Introduce `getImageSharedSizeMap()` which reads `df.Images[].SharedSize` once and lets both code paths subtract `SharedSize` per image when totalling: `+= max(0, Size - shared)`. If `df` fails, the helper returns an empty map and the accounting degrades to the prior sum-of-Size behavior rather than failing the prune. Verified against a live daemon: `/api/system/prune/estimate` with `scope: managed, target: images` now returns the layer-aware number; the older per-image-Size sum was roughly 1.8× larger on the same set. * fix(resources): use df-delta for destructive managed prune; label estimate as lower bound The first attempt at this PR subtracted SharedSize per prunable image on both paths. That formula undercounts when prunable images share a layer exclusively with each other: Docker frees the layer once, but the per-image subtraction removes it from every referrer. The reported total is then strictly less than the truth. Split the two paths: - pruneManagedOnly (destructive) now snapshots `docker df` before and after the parallel removes and reports `max(0, before.LayersSize - after.LayersSize)`. That is the honest measurement of bytes freed. Concurrent pulls during the prune can grow the after value; the clamp treats that as 0 reclaimed for the affected delta rather than attributing the new bytes to us. - estimateManagedReclaim keeps the per-image Σ(Size - SharedSize) formula but the JSDoc now calls it a "conservative lower bound" and documents the under-report mechanism. There is no cheap way to exactly price an arbitrary prune subset without per-layer enumeration. Fallback chain when df fails on the destructive path: - before-snapshot succeeded, after failed → per-image lower bound from before-snapshot (safe; SharedSize was known at start). - before-snapshot failed → report 0 with a warn log (after-only would build a SharedSize map missing the just-pruned images, which would over-report by treating them as having no sharing). Replaces the prior `getImageSharedSizeMap()` helper with two pieces: `safeDfSnapshot()` (I/O) and a private static `mapSharedSizesFromDf()` (pure parse), reused by both code paths. New invariant test asserts `prune.reclaimedBytes >= estimate.reclaimableBytes` on the same inputs so future changes to either formula cannot flip the direction. Addresses Codex audit blocker on PR #1155.