claim(2w): WC5 promote-on-green-cold proven — green cold run advances canonical (1.10.0→1.11.0); --quick never promotes; only cold advances

should_promote_canonical (enrolled+green+cold+latest) + promote_canonical
(re-seed canonical at green-verified latest, snapshot+registry, old known-good
replaced only on green). +5 unit (70 pass). Live: custom-html canonical advanced
1.10.0+1.28.0 → 1.11.0+1.29.0 via a full green cold run; snapshot refreshed; idle;
per-run app torn down. WC6 nightly sweep next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 04:08:14 +01:00
parent cf5999cdda
commit 125453df20
4 changed files with 140 additions and 1 deletions

View File

@ -342,3 +342,19 @@ judged sufficient. No finding. **WC1.1 FULLY closed (keycloak + traefik).**
Phase-2w verified: WC1, WC1.1, WC1.2, WC2, WC3, WC4, WC7. Remaining: WC5, WC6, WC8, WC9.
Adversary now idle → safe for live cold runs. Building W3 WC5 (promote-on-green-cold) next.
## 2026-05-29 — W3 WC5 promote-on-green-cold built + proven; claiming. (WC6 next.)
should_promote_canonical(recipe,ref,overall,quick) = is_enrolled & green & cold & on-latest(no ref);
promote_canonical(recipe,head_ref) = deploy warm-<recipe> at latest (reattach retained volume if any,
else fresh) → healthy → undeploy → seed_canonical (snapshot+registry, atomic; old known-good replaced
ONLY on green so it's never lost). Wired into main() after a green cold run; non-fatal on failure.
+5 unit tests (70 pass). LIVE: set custom-html canonical to 1.10.0+1.28.0, ran full cold (no REF),
all tiers green + deploy-count=1 → promote advanced canonical 1.10.0→1.11.0+1.29.0, snapshot refreshed,
idle, per-run cust-* torn down, traefik/kc still 200. WC5 proven; claimed.
Mechanism note: cold runs still use FRESH per-run domains (unchanged); promote re-deploys the
canonical at latest separately (one extra deploy) so the old known-good is never at risk on a red run
(DECISIONS Phase-2w WC5). Next: WC6 nightly sweep (systemd timer: nixos-rebuild switch FIRST then
serial cold sweep over enrolled recipes; need canonical.enrolled_recipes() + a nightly-sweep nix
module). Building WC6 code while the Adversary verifies WC5.

View File

@ -34,7 +34,11 @@ nightly full-cold sweep. Definition of Done = WC1WC9 (plan §1), each Adversa
head (chaos) → generic UPGRADE+serving+overlay+custom; PASS→undeploy-keep-volume (known-good
UNCHANGED, never promote); FAIL→restore last-known-good snapshot then undeploy. Proven live on
custom-html (PASS + FAIL). **Adversary PASS @2026-05-29** (REVIEW-2w 31f0e42, gate 3ff2bf6).
- [ ] **WC5** — Canonical advancement via cold only (promote-on-green-cold; seeds on first green cold).
- [x] **WC5** — Canonical advancement via cold only (promote-on-green-cold). `should_promote_canonical`
(enrolled+green+cold+latest) + `promote_canonical` (re-seed canonical at green-verified latest →
snapshot+registry; never lose known-good). Proven live: green cold custom-html run advanced the
canonical 1.10.0+1.28.0 → 1.11.0+1.29.0 (snapshot refreshed, idle, per-run app torn down).
`--quick` never promotes (W2). **CLAIMED — see Gate.**
- [ ] **WC6** — Nightly full-cold sweep (scheduled, declarative, MAX_TESTS-bounded).
- [x] **WC7** — Trigger/authority/labeling: default `!testme`=cold (unchanged); `--quick` opt-in via
bridge `parse_trigger` (`!testme --quick` → CCCI_QUICK=1 Drone param, deployed+live-verified);
@ -128,6 +132,28 @@ headline e2e is green (below). No recipe/harness change needed.
## Gate
### Gate: WC5 — CLAIMED, awaiting Adversary (@2026-05-29)
**WHAT.** Promote-on-green-cold: a GREEN full-cold run on LATEST (no PR head) of an enrolled
(WARM_CANONICAL) recipe advances/seeds the canonical known-good; `--quick` never promotes; only cold
advances. **WHERE:** `runner/run_recipe_ci.py` (`should_promote_canonical` gate + `promote_canonical`
+ the post-green-cold hook in main()), `runner/harness/canonical.py` (seed_canonical).
**HOW + EXPECTED (cold):**
1. **Units:** `cc-ci-run -m pytest tests/unit -q`**70 passed** (incl. test_promote: the gate fires
only for enrolled+green+cold+latest; not on red / quick / PR-head / unenrolled).
2. **Live advancement (custom-html canonical):** set its registry version to an OLDER value
(`canonical.write_registry("custom-html", version="1.10.0+1.28.0", …)`), then a full COLD run
`RECIPE=custom-html cc-ci-run runner/run_recipe_ci.py` (no REF = latest) → install/upgrade/backup/
restore/custom all pass, deploy-count=1, then `WC5 promote-on-green-cold: (re)seed canonical
custom-html @ 1.11.0+1.29.0` → afterwards `canonical.json` version **ADVANCED to 1.11.0+1.29.0**
(commit=head 8a02606…), snapshot refreshed (`warmsnap.read_meta` version=1.11.0+1.29.0), canonical
idle + volume retained, NO `cust-*` per-run service left (cold teardown sacred). Builder ran this
live: **advanced 1.10.0→1.11.0**. (A PR `!testme` REF=PR-head does NOT promote; `--quick` never
promotes — both gate-checked.)
---
### Gate: W0.10a traefik WC1.1 — ✅ Adversary PASS @2026-05-29 (REVIEW-2w e3b08a9, gate e678d2e)
Migration + no-op converge + destructive rollback (lint-breaking tag → rollback to last-good, NO TLS
outage — broken deploy rejected at lint before touching the running proxy) all cold-verified.