# DESIGN — Staleness Self-Demotion & the Corroboration Gate

> **SHIPPED vs DESIGNED (read first).** Neither mechanism designed here shipped, and EDGE 5a is
> defended a different way:
> - **Self-demotion did NOT ship — it was removed.** An interim fixed-timeout version existed
>   (`isolationDemoteMs`); a probe showed it was (a) **redundant** — support-weighting in
>   `compareFrameRank` already keeps an established multi-peer session when a stale waker returns (the
>   waker loses on headcount), and (b) **harmful** — it zeroed a legitimate lone founder's age, and in
>   a genuine 1-v-1 it wrongly let the younger live peer beat the older one (where seniority *should*
>   win). The phi-accrual version below was never built. EDGE 5a's actual defence is support-weighting
>   plus the seniority tie-break; see the "Stale-but-present EDGE 5a" block in
>   `../../tests/adversarial-break-it.test.js`.
> - **The corroboration gate did NOT ship** and `witnesses` does not exist on the token. Support
>   headcount in `compareFrameRank` provides the same in-scope property (a lone/minority dissenter
>   can't move a group); a colluding/forged *majority* defeats both designs and stays out of scope.
> The text below is preserved as the design record (phi-accrual, the gate, `beat.authority.unsynced`,
> `_frameWitnesses`); treat all of it as *designed-not-shipped*.

**Covers: EDGE 5a (a slept/absent peer must self-demote, not yank the live group).**
**Designs-for (OUT of initial test scope): EDGE 5b (a lone forged-seniority peer must not move a group of 3).**

This file is the *defensive sibling* of the authority spine (`DESIGN_EDGE1_EDGE2_EDGE4.md`). The spine
decides which frame wins by seniority; left alone, a stale-but-old token (a peer that was here first,
then slept) or a lone forged token would win and hijack a healthy group. Two rules close that gap:
**self-demotion** (a peer that has lost contact downgrades its *own* token) and the **corroboration
gate** (a peer in a healthy agreement won't adopt a *foreign* frame on one voice). EDGE 5a needs only
self-demotion; EDGE 5b additionally needs the gate, and is designed-for but not yet a test target.

## Rule
A peer that has not had its clock **corroborated by any other distinct peer** for longer than a
staleness horizon `T` — computed by a **phi-accrual** suspicion value over the inter-arrival times of
corroboration events, crossing a fixed threshold `Φ_max` — downgrades its own `beat.authority` to the
unsynced token (`presenceAge ← 0`, `authority.unsynced ← true`), thereby dropping any seniority claim and
forcing it to re-bootstrap onto whatever frame the live group is running. Complementarily, a peer that is
**already in a healthy ≥k-peer agreement** (its current frame corroborated by ≥k distinct peers) will
**not adopt a different incoming frame unless ≥k distinct peers corroborate that other token** (the
corroboration gate), so a single dissenting or forged token cannot move the group.

## Message / Field
No new message is introduced. The *input* to the phi detector is the stream of **corroboration events**
the clock layer already produces: a `pong` answering this peer's `ping` (the `SyncedPeerClock`
ping/pong that rides the transport), tagged with the responder's distinct id — the detector records the
arrival timestamp per remote id. The *output* of demotion is reflected by zeroing the existing
**`beat.authority.presenceAge`** and setting a new boolean **`beat.authority.unsynced = true`** on the
attendance already defined in `DESIGN_EDGE1_EDGE2_EDGE4.md` (so peers reading the token see an honest
"I am not currently synced" signal and rank it last). The corroboration gate consumes the same
`beat.authority` tokens arriving in `_onAttendance`/`MSG_BOOT`, counting **distinct `playerId`s** whose
token matches the candidate frame within a sliding window; it adds no field, only a receive-side counter
(`this._frameWitnesses`, a `Set` of ids per observed frame).

## Monotonicity Argument
Self-demotion changes only the peer's *advertised authority*; it never writes the local tick, so it
cannot by itself move the tick backward. When demotion causes the peer to re-bootstrap onto the live
frame, the re-bootstrap path (`DESIGN_EDGE3_EDGE6.md`) resumes at `max(currentTick, syncedTarget)`,
freezing the tick (catching-up) and then advancing forward — never below what was already displayed, even
though the sleeper's free-running tick may have run *ahead* during the partition (the spec's "tick loop
keeps free-running while asleep" NOTE). The **live group** is unaffected by the demoted peer's lowered
token (their own token stays maximal), so their tick cadence is undisturbed — EDGE 5a's "B,C were NOT
yanked apart by A's stale clock." The corroboration gate likewise only *prevents* an adoption; refusing
to adopt cannot lower a tick, so it is trivially monotone.

## Determinism Argument
`T` is not a magic wall-clock constant: it is the point where the phi-accrual value
`Φ = -log10(P(arrival later than now | observed inter-arrival distribution))` crosses a fixed `Φ_max`,
a deterministic function of the corroboration arrival history. In the harness every ping/pong rides the
**deterministic master timeline** with a seeded RNG, so the arrival history — and therefore the exact
simulated instant demotion fires — is reproducible across runs; a **fixed-timeout fallback** (`demote if
no corroboration for T_fixed`) is fully deterministic and is the conservative default. Two peers in the
same scenario with the same connectivity demote at the same simulated time. The corroboration gate is a
pure count of distinct ids against the fixed threshold `k`, with no randomness — given the same set of
received tokens, every peer makes the same adopt/refuse decision.

## Failure Modes
1. **False demotion on a slow/lossy link.** A peer on a genuinely high-latency or lossy link could be
   demoted by a too-tight horizon even though it is alive. Mitigation: phi-accrual *adapts to that
   link's own inter-arrival distribution* (a chronically slow link raises its expected interval, so Φ
   stays low), and we require **whole-group silence** (no corroboration from *anyone*), not loss of a
   single peer, before demoting — a peer still hearing one other peer is not stale.
2. **Brief blip then reconnect.** A momentary partition should not trigger a disruptive re-bootstrap.
   Mitigation: `Φ_max`/`T` is set well above the normal corroboration interval (a safety multiple), and
   a hysteresis dwell means a peer that re-corroborates before crossing `Φ_max` simply resets its
   suspicion with no demotion.
3. **Sole founder sleeps.** If the sleeper was the only member of its frame, demotion means it abandons
   its own (now stale) frame entirely and adopts whatever it finds on wake — which is the *desired*
   EDGE 5a behavior, but it must not deadlock if it wakes alone. Mitigation: a peer that demotes and then
   finds *no* live frame re-founds from its own state via the existing `_goLive()` path (same as the
   cold-start co-founder), so it never wedges.
4. **Gate too strict at small N.** Requiring ≥k corroborators could stall a *legitimate* 2-peer session
   from ever adopting a needed correction if `k` > available peers. Mitigation: `k` is `min(k_target,
   currentHealthyWitnesses)` — the gate only applies *while you are already in a ≥k agreement*; a 2-peer
   group uses k=2 internally and a lone/late peer (no healthy agreement yet) is not gated, so EDGE 3/4
   adoption still proceeds.

## How This Stays Sparse
Both rules are **pure consumers of traffic that already exists** — the ping/pong the clock exchanges and
the attendance tokens the spine already sends; **no new message, no new round, no per-tick flood**. The
phi detector keeps O(1) running statistics (mean/variance of inter-arrival per remote id) and the gate
keeps an O(witnesses) `Set` per observed frame, both updated incrementally on message receipt. A demoted,
isolated peer actually sends *less* (it stops winning elections and may pause attending until
re-bootstrapped). Nothing here scales with the tick rate: corroboration events arrive at the existing
ping cadence, and demotion/gate decisions are evaluated only when such an event (or its absence) is
observed, not on every tick.

## How a Peer Could Exploit or Break This
A peer could try to **dodge demotion by forging corroboration** — emitting fake `pong`s (or pinging
itself) to keep its phi value low while actually isolated and stale, then hijacking the group on
reconnect with its retained seniority. The consequence, undefended, is that the very stale-clock hijack
EDGE 5a is meant to prevent slips through. Mitigation: corroboration only counts a `pong` that answers a
`ping` *this peer actually sent* to a **distinct remote id** with a plausible round-trip (the responder
cannot be self), so a peer cannot manufacture corroboration without a real, responding counterpart; and
even if it retains a high token, the **corroboration gate on the healthy group** refuses its lone claim
(≥k distinct corroborators required), so a single liar — stale or forged (EDGE 5b) — cannot move a group
that agrees among itself. A *colluding majority* or a cryptographically-signed forged presence defeats
both rules and needs attestation/Byzantine machinery that is explicitly **out of initial scope** and
recorded as an open risk in `./TIMER_RESEARCH_PLAN.md`.
