You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
WO-8, the Wolverine half of JasperFx/jasperfx#565 (implemented in JasperFx/jasperfx#567). Surface an ApplyEventException-stopped shard to the assignment/observer plane with a reason, so CritterWatch can alert on it and progress doesn't silently flatline. This was deliberately deferred to ship as a coordinated pair with the JasperFx.Events accessor, which has now landed.
What JasperFx.Events gives us
ISubscriptionAgent.Failure → ShardFailure? — a default interface member (non-breaking), set alongside Status when a shard pauses or stops and cleared on start/replay.
ShardFailure carries Category (ApplyEvent / EventSerialization / UnknownEventType / ProgressionOutOfOrder / Other), ExceptionType, RootExceptionType, Message, Detail (the full text ShardState.PauseReason has always carried), Event (sequence, event type, stream, tenant, version when known) and OccurredAt. It is a plain serializable record, not an Exception, so it survives the hop to the assignment plane and into a UI.
ShardStateTracker.CurrentState(...) / TryGetCurrentState(...) / CurrentStates() — a synchronous snapshot for pollers that aren't subscribed.
Expose it on IEventSubscriptionAgent so CritterWatch can read it through the agent surface it already resolves via IEventSubscriptionAgentFamily.FindAgentUriAsync.
2. Make the health check say something actionable.EventSubscriptionAgent.CheckHealthAsync currently returns a fixed string:
if(Status==AgentStatus.Paused)returnHealthCheckResult.Unhealthy($"Projection {Uri} paused due to errors");
With the failure in hand that becomes the category, the failing event sequence and the root exception type — the difference between an alert an operator can act on and one they have to go dig for.
3. Stop auto-restarting failures that cannot self-heal. This is the part worth the most. The stall detector auto-restarts after MaxConsecutiveStallsBeforeRestart stalled health checks. A shard paused on ApplyEvent, EventSerialization or UnknownEventType will fail on the same event every time it restarts, so the restart loop is pure churn that also keeps resetting the operator's view. Gate the auto-restart on the category: retry Other (a transient database blip is exactly what auto-restart is for), and for the per-event categories surface the failure instead and leave it stopped. ProgressionOutOfOrder means two processes are on the same shard — restarting makes that worse, not better.
4. Carry it into the observer/assignment plane so CritterWatch renders the reason next to the paused shard rather than just the status.
Summary
WO-8, the Wolverine half of JasperFx/jasperfx#565 (implemented in JasperFx/jasperfx#567). Surface an
ApplyEventException-stopped shard to the assignment/observer plane with a reason, so CritterWatch can alert on it and progress doesn't silently flatline. This was deliberately deferred to ship as a coordinated pair with the JasperFx.Events accessor, which has now landed.What JasperFx.Events gives us
ISubscriptionAgent.Failure→ShardFailure?— a default interface member (non-breaking), set alongsideStatuswhen a shard pauses or stops and cleared on start/replay.ShardFailurecarriesCategory(ApplyEvent/EventSerialization/UnknownEventType/ProgressionOutOfOrder/Other),ExceptionType,RootExceptionType,Message,Detail(the full textShardState.PauseReasonhas always carried),Event(sequence, event type, stream, tenant, version when known) andOccurredAt. It is a plain serializable record, not anException, so it survives the hop to the assignment plane and into a UI.ShardStateTracker.CurrentState(...)/TryGetCurrentState(...)/CurrentStates()— a synchronous snapshot for pollers that aren't subscribed.ShardState.Failurerides along on the published paused/stopped state, and Marten/Polecat persist the classified fields onto the extended progression row (Implement IEventFailureContext + extended progression failure columns (jasperfx#565) marten#5048, Implement IEventFailureContext + extended progression failure columns (jasperfx#565) polecat#368) so it is readable even when the publishing node is down.Proposed work in Wolverine
1. Delegate the reason on the wrapper.
EventSubscriptionAgent.Statusalready delegates to the live inner agent (GH-3519); do the same for the reason:Expose it on
IEventSubscriptionAgentso CritterWatch can read it through the agent surface it already resolves viaIEventSubscriptionAgentFamily.FindAgentUriAsync.2. Make the health check say something actionable.
EventSubscriptionAgent.CheckHealthAsynccurrently returns a fixed string:With the failure in hand that becomes the category, the failing event sequence and the root exception type — the difference between an alert an operator can act on and one they have to go dig for.
3. Stop auto-restarting failures that cannot self-heal. This is the part worth the most. The stall detector auto-restarts after
MaxConsecutiveStallsBeforeRestartstalled health checks. A shard paused onApplyEvent,EventSerializationorUnknownEventTypewill fail on the same event every time it restarts, so the restart loop is pure churn that also keeps resetting the operator's view. Gate the auto-restart on the category: retryOther(a transient database blip is exactly what auto-restart is for), and for the per-event categories surface the failure instead and leave it stopped.ProgressionOutOfOrdermeans two processes are on the same shard — restarting makes that worse, not better.4. Carry it into the observer/assignment plane so CritterWatch renders the reason next to the paused shard rather than just the status.
Notes
Failureis null and everything reads exactly as it does today.