claim(2w): WC8 + WC9 (FINAL gates) — resource-safety consolidation + stale-warm prune + docs/warm.md + --quick rollback proof

WC8: canonical.prune_stale (drop de-enrolled warm data + volumes) wired into the
nightly sweep + df log; consolidated evidence (DRONE_RUNNER_CAPACITY=MAX_TESTS
serialize; autoPrune drops --volumes so warm vols survive; cold teardown sacred;
warm excluded from D8 — no nix source ref). +1 unit (72 pass). WC9: docs/warm.md
documents the full warm/quick model; --quick rollback proof already proven live
(W2 FAIL restores exact known-good; WC4 PASS byte-identical snapshot). On PASS,
all WC1-WC9 (incl WC1.1/WC1.2) verified → DONE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 04:43:34 +01:00
parent b8b698e2f5
commit 40b03a9bf1
6 changed files with 234 additions and 6 deletions

View File

@ -138,6 +138,36 @@ def undeploy_keep_volume(recipe: str) -> None:
_set_status(recipe, "idle")
def prune_stale() -> list[str]:
"""WC8 disk hygiene: remove warm data for DE-ENROLLED canonicals — a `/var/lib/ci-warm/<recipe>/`
that carries a `canonical.json` but whose recipe is no longer enrolled (WARM_CANONICAL dropped).
Drops the dir (snapshot + registry) AND the retained `warm-<recipe>` data volumes. Leaves the
live-warm reconciler dirs (keycloak/traefik — they have a `last_good`, no `canonical.json`),
`alerts/`, and currently-enrolled canonicals untouched. Returns the recipes pruned."""
import shutil
import subprocess
root = warmsnap.warm_root()
keep = set(enrolled_recipes())
pruned: list[str] = []
try:
entries = sorted(os.listdir(root))
except OSError:
return pruned
for name in entries:
d = os.path.join(root, name)
if not os.path.isdir(d) or name in keep:
continue
if not os.path.isfile(os.path.join(d, "canonical.json")):
continue # not a data-warm canonical (e.g. keycloak/traefik reconciler dir, alerts/)
# drop the retained warm-<recipe> volumes, then the snapshot/registry dir
for vol in warmsnap.stack_volumes(canonical_domain(name)):
subprocess.run(["docker", "volume", "rm", vol], capture_output=True, text=True)
shutil.rmtree(d, ignore_errors=True)
pruned.append(name)
return pruned
def seed_canonical(recipe: str, version: str, commit: str | None = None) -> dict:
"""Record <warm-domain> (already deployed at `version`) as the recipe's canonical: write the
registry, then (app must be UNDEPLOYED) take the known-good snapshot. Caller deploys + verifies

View File

@ -68,6 +68,12 @@ def sweep() -> int:
).returncode
results[r] = rc
print(f"nightly: {r} rc={rc} ({'green→canonical refreshed' if rc == 0 else 'red'})", flush=True)
# WC8 disk hygiene: drop warm data for de-enrolled canonicals; log the disk budget.
pruned = canonical.prune_stale()
if pruned:
print(f"nightly: pruned stale warm data for de-enrolled canonicals: {pruned}", flush=True)
df = subprocess.run(["df", "-h", "/"], capture_output=True, text=True)
print(f"nightly: disk / →\n{df.stdout.strip()}", flush=True)
print("\n===== nightly sweep summary =====", flush=True)
for r, rc in results.items():
print(f" {r}: {'PASS' if rc == 0 else 'FAIL'}", flush=True)