status(redfix): accept A-redfix-4 — weekly nightly-sweep.timer re-triggers reconcile; wedge is silent + recurring

Independently re-verified every leg against the node before editing (I adopted
A-redfix-2's probes wholesale at 8276ecd and inherited its error; not repeating that).

- Delete both 'no re-trigger' probes: they grep the app name, but the timer is
  named nightly-sweep, so they return empty while a timer drives it weekly.
  Sound probe greps the callee: git grep warm_reconcile -- runner/ nix/
- Replace 'recovery is manual' / 'heals on reboot' with the weekly DOWN/UP
  oscillation, and record that it is SILENT (roll_warm_infra discards the rc;
  the raise is upstream of every write_alert).
- Re-anchor the merge precondition: 'slot clean at merge time' -> 'never deploy
  07fc6d4'. A git merge executes nothing; b5f2b10 ships enrollment + fix together,
  so the precondition is self-maintaining.
- Line refs pinned to b5f2b10 coordinates (were main's, two lines off).
- BACKLOG B-redfix-5: severity corrected, remedy sketch extended to rc propagation.

Not armed and not self-arming: origin/main (the tree the sweep runs) has
WARM_CANONICAL = False. Nothing reopens; DONE stands, no VETO, b5f2b10 unchanged.
Inbox consumed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TkHo7RixQswYvvNnEcNaqW
This commit is contained in:
2026-07-09 08:52:44 +00:00
parent 85b068e395
commit 186b0ba46b
4 changed files with 176 additions and 110 deletions

View File

@ -1815,3 +1815,63 @@ Neither is unblocked work, so there is no backlog item left to hold. Untracked `
per the joint Builder/Adversary position: neither of us created it, it is inert (no `go.mod`, `go` not
installed, nothing on `:8080`, in no commit on any ref), and the guardrails say I do not delete files I
did not create.
---
## 2026-07-09T08:55Z — wake #44: the Adversary refuted itself, and I had inherited the error
Re-opened the loop on a watchdog ping. The Adversary withdrew half of A-redfix-2 and filed A-redfix-4:
`warm_reconcile.py` has a *second* invoker — `nightly-sweep.timer` (`OnCalendar=Sun`, `Persistent=true`)
`nightly_sweep.roll_warm_infra()``subprocess warm_reconcile.py keycloak`. So the "no re-trigger /
heals on reboot" mechanism I wrote into STATUS at `f64d102` is false.
**The part worth recording is how it got in.** At `f64d102` I accepted A-redfix-2 and copied its two probe
commands into STATUS as the HOW-to-verify. Both probes grep the *app* name — `git grep warm-keycloak -- nix/`
and `list-timers | grep -i keycloak`. The timer is named `nightly-sweep`. So both return empty *while a timer
drives the thing weekly*: they return the all-clear for a reason unrelated to the property under test. I ran
them, saw empty, and treated that as confirmation. It confirmed nothing. The Adversary named the class
correctly — it is the same shape as its own `e3b0c44298fc1c14` empty-input tell on B-redfix-8.
The lesson is not "the Adversary was wrong" — it self-corrected. It is that *I re-ran probes handed to me
rather than deriving probes from the property*. The property is "what invokes `reconcile()`", so the probe
must grep the **callee** (`git grep warm_reconcile -- runner/ nix/`), which finds both invokers immediately.
A probe supplied alongside the claim it is meant to test is not independent evidence.
So this wake I verified every leg of A-redfix-4 myself before editing:
- `git grep warm_reconcile -- runner/ nix/``nix/modules/warm-keycloak.nix:26` **and**
`runner/nightly_sweep.py:60`. Two invokers.
- `systemctl cat nightly-sweep.timer``OnCalendar=Sun *-*-* 03:00:00`, `Persistent=true`; next
`2026-07-12 03:05:34`, last `2026-07-05 03:04:50`.
- The timing inference, which is the load-bearing one: `warm-keycloak.service` `ExecMainStartTimestamp` =
`2026-06-17 17:29:31`, but `/var/lib/ci-warm/keycloak/last_good` mtime = `2026-07-05 03:04:51.209` — 18 days
after the unit last ran, 1.2 s after the timer fired. `write_last_good()` has four call sites
(`b5f2b10:478,484,491,527`), all inside `reconcile()`. Nothing else can write that file. ⇒ the sweep ran it.
- Silence: `roll_warm_infra()` binds `rc` and only prints it (`:59-62`); every `write_alert()` site
(`:493,500,503,539`) is downstream of the `snapshot()` raise at `:514`; `main():556` does not catch.
- Cannot self-arm: `git show origin/main:tests/keycloak/recipe_meta.py``WARM_CANONICAL = False`. Only
`07fc6d4` and `b5f2b10` carry `True`. `/etc/cc-ci` is `origin/main`, so Sunday's sweep cannot arm the trap.
- `ExecStart``/nix/store/fcqh…-runner/warm_reconcile.py`; `harness/warmsnap.py` is in that same
derivation, so a switch re-runs reconcile iff `runner/**` changed.
I also caught a line-number slip while writing: the `write_alert` / `write_last_good` sites I first wrote were
`main`'s coordinates, but every other ref in that STATUS block is `b5f2b10`'s (they sit two lines apart).
Fixed to branch coordinates and said so in the text, since an operator will `git show` the branch.
Then I falsification-tested each *new* probe before committing it, which is the whole point: the sound probe
must print both invokers, the fresh-deploy grep must return `0` warmsnap hits, the timer must print its
schedule. All did.
**Severity re-recorded, scope unchanged.** B-redfix-5's consequence is worse than I had it — a silent weekly
alternating SSO outage rather than a one-shot manual-recovery wedge — but its *reachability* is unchanged and
still nil on this node. I did not fix it: the two-line `try/except` widening would move the merge target off
`b5f2b10`, which is the sha the M2 PASS was given against, and the Adversary's asks were explicitly
"STATUS text only, no code". Fixing it is the operator's call at merge time; the remedy sketch in
BACKLOG-redfix.md now also notes `roll_warm_infra()` should propagate the rc so the failure is visible at all.
Re-anchored the merge precondition per ask 3. "Slot clean at merge time" named the wrong landmark — a
`git merge` executes nothing, and the slot can only go foreign when a canonical seed *runs* from a tree with
the enrollment but without `canonical_ns()`. That tree is `07fc6d4`, and only `07fc6d4`. `b5f2b10` ships both
halves, so the precondition is self-maintaining. The operator-facing rule is now "never deploy `07fc6d4`",
and the slot check is kept as a cheap not-armed assertion rather than as the rule itself.
Nothing reopens: M1 + M2 PASS stand, `## DONE` stands, no VETO, merge target `b5f2b10` unchanged.