A25: surface a clock-skew warning instead of failing silently
cargo-deny / cargo-deny (push) Has been cancelled
windows-build / windows-build (push) Has been cancelled

A validly-signed gossip payload rejected only by the 120s replay
freshness window (GossipReject::OutOfWindow) now drives a room-level
"clocks out of sync" warning banner, instead of silently dropping the
peer so the room shows "1 in room" with no error.

Observe-only: verify_gossip's accept/reject decision and
GOSSIP_FRESHNESS_MS are unchanged; the payload is still dropped exactly
as before. The warning is gated strictly on OutOfWindow (which, because
the signature is verified first, implies a genuine authenticated peer
whose clock is skewed), never on BadSignature.

Policy lives in a pure, unit-tested ClockSkewMonitor seam with injected
now_ms: >=3 OutOfWindow drops from the same author within 60s warn once,
5-min per-author cooldown, bounded/pruned author map. The warning rides
the existing in-process RoomEvent -> UiEvent -> transient-banner path
(no wire/serialization or dependency change).

Implemented by Codex (gpt-5.5), senior-audited against the 5-point
checklist and independently verified (452 lib tests, clippy
--all-targets clean, release build green). Tests-green only; a 2-machine
deliberate-skew field test is still owed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 03:43:39 -04:00
co-authored by Claude Opus 4.8
parent 9a059e1bb8
commit e3ff778d5b
5 changed files with 398 additions and 9 deletions
+4
View File
@@ -106,6 +106,10 @@ pub enum RoomEvent {
/// from `PeerLeft` precisely so a momentary `NeighborDown` can't tear down the
/// reconnect path the way it used to.
PeerConnectionLost(EndpointId),
/// A validly signed gossip payload was rejected only because its timestamp is
/// outside the replay-protection window. The peer is not in the roster yet,
/// so this surfaces as a room-level warning instead of a peer-card state.
ClockSkewSuspected { author: EndpointId, skew_ms: i64 },
/// A peer sent a room text-chat message. Carries the sender's id, their
/// display name (embedded so it shows even without a presence entry), the
/// text, and a sender-stamped millisecond timestamp.