FrameRefused
A frame from a peer was refused by gate — the one variant that reports something the engine did not do.
Every dispatch-boundary guard refuses by returning, so a refusal's only other observable is the absence of a state change, and absences carry no attribution: when two guards refuse the same frame, "term unchanged, still a Follower" cannot say which one did it (#1980, #1989). This event names the guard. One frame in, one event out — no run threshold, no latch, no commit-index precondition.
Not a wedge detector
RaftMetric.WedgeSuspected keeps that job, and a production consumer that wants to know whether a node is jammed must watch it, not this. Two observables, two jobs: the metric is the sustained-condition diagnosis, this is the per-frame attribution.
The reason for the split is that this event is losable by design. trace is a MutableSharedFlow with BufferOverflow.DROP_OLDEST, so emitting it can never backpressure consensus — which is exactly what makes it safe to emit on a path a remote frame controls, and exactly why WedgeSuspected needs a run threshold and a latch and this does not. The consequence, stated here rather than left to be rediscovered as a bug: a hostile flood of refused frames evicts honest trace events from the buffer. A slow consumer sees the same thing. Neither harms the engine; both mean this flow is evidence, not a ledger.