status(redfix): record the b5f2b10 MERGE PRECONDITION; widen B-redfix-5 to both post-undeploy sites
Acting on the Adversary's wake-#41 verdict (3131e39), which CONFIRMED the merge guidance but left a merge precondition only in REVIEW-redfix.md — Adversary-owned, and not what an operator reads before merging. Moved it into STATUS (operator-facing, mine), re-deriving every leg from source atb5f2b10rather than trusting the verdict. - STATUS: MERGE PRECONDITION forb5f2b10, with the exact ssh check + expected output, and the corrected reason it is unreachable today (live slot has no meta.json — NOT "F-redfix-4 is closed"). - Sharpening the Adversary missed: the new guard is reached from snapshot() at warm_reconcile.py:514 as well as restore() at :536. Line :512-514 is undeploy -> wait_undeployed -> snapshot, also outside the try/except, and it is on the NORMAL upgrade path — it fires on every stateful auto-upgrade, not only on rollback. B-redfix-5 as filed named only the rollback site. - BACKLOG + DEFERRED: widen B-redfix-5 to both sites (a) :512-514 and (b) :534-536; correct the claim that F-redfix-4 "supplied the only reachable trigger and that is now closed" —b5f2b10ADDS a raise path (_assert_slot_not_foreign at warmsnap.py:158 and :209). Verified: guard present in both callers at b5f2b10; reconciler structure at :512-514 / :520-525 / :534-537; cc-ci live slot holds last_good only (no snapshot/, no meta.json, no canon-* slot). Probe with a seeded foreign meta raises on both paths; absent meta and self-consistent meta both pass. No DoD item touched. M1+M2 PASS stand, ## DONE stands, no VETO. Docs-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YZmH4rVNue3irZ6EsZzavb
This commit is contained in:
@ -58,17 +58,26 @@ hold). Concrete fix designs from M1 evidence:
|
||||
`_assert_slot_not_foreign()` guard. Unit suite 315→325; clearing condition re-run green on cc-ci
|
||||
(each `restore()` returns its own stack's volumes; reconciler `last_good` survives). Verify per
|
||||
STATUS-redfix.md "Gate: M2 RE-CLAIMED".
|
||||
- [ ] **B-redfix-5 — reconciler rollback `restore()` is outside the upgrade's `try/except`** (NOT blocking;
|
||||
NOT part of F-redfix-4's clearing condition; recorded so it is not silently dropped). In
|
||||
`warm_reconcile.py`, the unhealthy-rollback path runs `abra.undeploy(domain)` → `wait_undeployed` →
|
||||
`warmsnap.restore(...)` → `deploy_version(last_good)`. `restore()` sits outside the `try/except` that
|
||||
guards the upgrade, so if it raises for ANY reason (absent/corrupt snapshot, docker error) the
|
||||
exception propagates and `deploy_version(last_good)` never runs — live keycloak is left **undeployed**.
|
||||
F-redfix-4 supplied one way to make `restore()` raise (the shared slot) and that is now fixed, but the
|
||||
structural gap predates it. Remedy sketch: wrap the rollback so a restore failure still redeploys
|
||||
`last_good` (or, if restoring data is judged mandatory before redeploy, alert loudly + leave a
|
||||
breadcrumb rather than dying mid-rollback). Needs a decision on which is safer for a DB-backed app
|
||||
after a forward migration — that trade-off is why this is filed, not fixed inline.
|
||||
- [ ] **B-redfix-5 — reconciler's post-`undeploy` warmsnap calls are outside the upgrade's `try/except`**
|
||||
(NOT blocking; NOT part of F-redfix-4's clearing condition; recorded so it is not silently dropped).
|
||||
In `warm_reconcile.py` there are **two** such sites, both after `abra.undeploy(domain)` and both
|
||||
outside the `try/except` that guards the upgrade:
|
||||
**(a) upgrade path** `:512-514` — `abra.undeploy` → `wait_undeployed` → `warmsnap.snapshot(...)`;
|
||||
**(b) rollback path** `:534-536` — `abra.undeploy` → `wait_undeployed` → `warmsnap.restore(...)` →
|
||||
`deploy_version(last_good)` at `:537`.
|
||||
If either raises for ANY reason (absent/corrupt snapshot, foreign slot, docker error, "no volumes
|
||||
found") the exception propagates, the following `deploy_version` never runs, and live keycloak is left
|
||||
**undeployed**. Site (a) is on the *normal* upgrade path — it does not need a rollback to fire.
|
||||
**Reachability, corrected (wake #42):** F-redfix-4's fix does not remove the trigger, it *adds* one —
|
||||
`_assert_slot_not_foreign()` is a new raise inside BOTH `snapshot()` (`warmsnap.py:158`) and
|
||||
`restore()` (`:209`). It is unreachable on cc-ci **today** because the live slot holds no
|
||||
`snapshot/meta.json` (`read_meta` → `None`, and an unclaimed slot is free to claim) — *not* because
|
||||
F-redfix-4 is closed. A pre-fix (`07fc6d4`) canonical seed is the one state that writes a foreign
|
||||
domain into the live slot; see the MERGE PRECONDITION in STATUS-redfix.md.
|
||||
Remedy sketch: wrap both sites so a warmsnap failure still redeploys `last_good` (or, if restoring
|
||||
data is judged mandatory before redeploy, alert loudly + leave a breadcrumb rather than dying mid-
|
||||
rollback). Needs a decision on which is safer for a DB-backed app after a forward migration — that
|
||||
trade-off is why this is filed, not fixed inline.
|
||||
- [ ] **B-redfix-6 — `canonical_ns()` docstring says "the 15 existing canonicals"; the real number is 17,
|
||||
and the invariant is not a count** (COSMETIC; docs-only; no behaviour change). At
|
||||
`redfix-m2-harness`@`b5f2b10`, `runner/harness/canonical.py:52` reads "zero blast radius on the 15
|
||||
|
||||
Reference in New Issue
Block a user