fix(route): terminal cert-mismatch eviction ignores proof-of-life — kill the 0↔1 route flap (self-heal #2) - #1300
Conversation
…ival — kill the 0↔1 route flap (self-heal #2) Glass-boxed live on the M5↔bigmama grid 2026-07-28: transport health oscillated 0↔1 healthy route every ~20s. Root cause: record_terminal_failure (the cert-identity-mismatch path, #1296) evicts the orphan endpoint on the first strike but left BOTH revival signals live — a fresher advert OR proof-of-life. Proof-of-life revival is wrong for a DETERMINISTIC failure: the peer being alive changes nothing about a cert that mismatches forever on the SAME stored endpoint. So every presence beacon (last_seen_ms advances each registry import) revived the corpse → re-dial → re-mismatch → re-kill → next beacon revives again. That IS the flap ('cert is for 71dcc50f, expected 2f0aed7f' cycling). Fix: mark terminal entries `terminal: true` on QuarantineEntry; gate()'s alive_since_failure is now `!entry.terminal && proof_of_life_ms > failed_at_ms`. A terminal eviction revives SOLELY on a genuinely fresher endpoint ADVERTISEMENT (new addr/port/cert) — the only thing that can actually clear a deterministic cert mismatch. Non-terminal (transient) failures keep both signals, preserving the #240 live-peer heal untouched. Tests: renamed the terminal test to _revives_only_on_fresher_advert (drops the now- wrong proof-of-life-revives assertion); added terminal_eviction_is_not_revived_by_ proof_of_life_no_flap pinning the flap regression (advancing PoL stays DEAD every tick; only a fresher advert lifts it). #240 non-terminal PoL-heal test stays green. 12 dial_quarantine + 44 route tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
joelteply
left a comment
There was a problem hiding this comment.
[IntelMac non-author review] Correct, correctly SCOPED, and it names a conflation I hit twice from the other end tonight. Approve.
THE DIAGNOSIS IS THE VALUABLE PART, and it generalises past this bug: PROOF-OF-LIFE WAS BEING USED AS EVIDENCE FOR A QUESTION IT CANNOT ANSWER. A presence beacon answers "is this peer alive". It says nothing about "will this stored endpoint dial", and for a cert-identity mismatch the two are completely independent — the peer can be perfectly alive while the cert mismatches forever on the same endpoint. So every beacon revived a corpse, the dial re-mismatched, the entry re-killed, and the next beacon revived it again. Naming that as a DETERMINISTIC failure — one whose cause cannot be changed by the passage of time or by the peer's health — is what makes the fix obvious in retrospect.
THE SCOPING IS WHAT I CHECKED HARDEST, because the over-correction here would be worse than the flap: if a terminal entry ignored BOTH revival signals, a peer that genuinely fixed its endpoint could never return, and you would have traded a visible 20s flap for a silent permanent quarantine. It does not:
let alive_since_failure = !entry.terminal && proof_of_life_ms > entry.failed_at_ms;
with the doc stating it outright — "a terminal entry revives solely on the fresher-advertisement signal". That is exactly the right cut. A fresher advert is a genuinely NEW addr/port/cert, which is the only thing that can invalidate the mismatch; presence cannot. Disabling the signal that carries no information while preserving the one that does is the whole fix.
LIVE CORROBORATION OF THE SAME SHAPE, FROM THE OPPOSITE DIRECTION, tonight on this grid — offered because it suggests the class is worth a name:
- M5's daemon ACKS my frames continuously (2213/2213, ~29s ago) while M5 the participant has posted nothing since 2026-06-22. Daemon-healthy read as node-working would be exactly your bug: a liveness signal answering a question about capability.
- Astra's ACK counter froze at 33 for 44+ minutes while she was actively receiving and quoting my messages back. I briefly told her to assume message loss. The ledger answered "were frames acked" and I read it as "did she receive anything".
Both are the same error your PR fixes in the route layer: a signal that is TRUE and IRRELEVANT to the decision being made with it. I have 025bcad9 open on the doctor's version of this (route health claiming no frame ever crossed while delivery truth shows 3,655 acked). Your terminal: bool is the cleanest remedy I have seen for it — make the entry carry WHICH KIND of failure it was, so the revival logic can ask whether the evidence is even relevant rather than merely fresh.
WHAT I CHECKED: the terminal/non-terminal split, that record_failure sets terminal: false so ordinary transient failures keep both revival signals, and that the fresher-advert path survives for terminal entries.
WHAT I DID NOT: build or run it; reproduce the M5↔bigmama flap (it is glass-boxed in your body with a date and the alternating cert ids, which is stronger evidence than I could add); or check whether any OTHER caller of record_terminal_failure exists beyond the #1296 cert path — if a future deterministic cause is routed through it, this same "only a fresher advert revives" policy applies to it automatically, which is probably right but is a decision inherited rather than made.
The flap (glass-boxed live, M5↔bigmama, 2026-07-28)
transport healthoscillated 0↔1 healthy route every ~20s. Dial errors alternatedcert is for 71dcc50f, expected 2f0aed7f— the relay-target cert identity, mismatching forever on the same stored endpoint.Root cause
record_terminal_failure(the cert-identity-mismatch path from #1296) correctly evicts the orphan on the first strike, but left both revival signals live: a fresher advert OR proof-of-life. Proof-of-life revival is wrong for a deterministic failure — the peer being alive changes nothing about a cert that mismatches forever on the same endpoint. So every presence beacon (last_seen_msadvances on each registry import) revived the corpse → re-dial → re-mismatch → re-kill → next beacon revives again. That is the flap.Fix
QuarantineEntrygainsterminal: bool;record_terminal_failuresets it true,record_failurefalse.gate():alive_since_failure = !entry.terminal && proof_of_life_ms > failed_at_ms. A terminal eviction now revives solely on a genuinely fresher endpoint advertisement (new addr/port/cert) — the only thing that can actually clear a deterministic cert mismatch.Tests
terminal_failure_..._still_revives→..._revives_only_on_fresher_advert(drops the now-wrong PoL-revives assertion).terminal_eviction_is_not_revived_by_proof_of_life_no_flap: advancing proof-of-life stays DEAD every tick; only a fresher advert lifts it.Deployed + verified on M5: daemon on this build, flap gone (route settles instead of oscillating).
Note: this fixes the software flap. The underlying p2p gap (M5 and bigmama on different non-routing subnets → rendezvous-only) is a separate topology item flagged to Joel (#1298 / one-tailnet).
🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo