FrameUndecodable
A frame from a peer could not be decoded, and was dropped (#2051).
Why this is not a FrameRefused
FrameRefused reports a guard declining a frame the engine understands, and every field it carries past from — FrameRefused.messageType, FrameRefused.gate — is a fact about the decoded frame. Here the failure is the decode, so neither exists: there is no RaftMessageType, because the bytes never became a RaftMessage, and no RefusalGate, because no guard ran. Widening either field to express "unknown" would weaken it everywhere it is currently exact, and minting a RaftMessageType.Undecodable would break that enum's one structural property — it mirrors the sealed wire hierarchy one-for-one, which is what makes a new frame type impossible to add without an entry (#1973). A separate event keeps both surfaces honest and says exactly what is knowable at a point where the frame is still bytes.
The trigger is ordinarily version skew
The engine's codec sets ignoreUnknownKeys, so an unknown field from a newer peer is tolerated; an unknown sealed-class discriminator is not. A peer on a newer build sending a frame type this build does not declare therefore lands here, and rolling upgrades across a voter set are the ordinary case. A corrupt link or a hostile peer reaches it too, for the cost of arbitrary bytes — which is why the frame is dropped rather than allowed to throw.
Losable, like FrameRefused
Emitted on the actor loop and subject to the same DROP_OLDEST buffer, so it can never backpressure consensus and a flood of undecodable frames evicts honest events. Evidence, not a ledger. Nothing here feeds RaftMetric.WedgeSuspected: that metric counts leader→peer frames at or above this node's term, and neither the sender's role nor the frame's term is knowable without the decode that just failed.