feat(#367,#368,#369): JasperFx.Events 2.36.1 — classified shard failures, widened natural key contract, drain-timeout docs - #371
Merged
Conversation
…res, widened natural key contract, drain-timeout docs Bumps the JasperFx family 2.34.0 -> 2.36.1 and lands the three Polecat issues that ride on that line. They are batched because the natural key half is SOURCE BREAKING -- NaturalKeyEventMapping.Extractor widens from Func<object, object?> to Func<IEvent, object?> -- so the bump does not compile without the call-site change. #369 (jasperfx#569/#571). NaturalKeyProjection.QueueOperationForEvent passes the whole IEvent to mapping.Extractor instead of e.Data. One call site, shared by the inline append path and the #259 rebuild re-emit path. Widening to the event is what makes an IEvent<T> [NaturalKeySource] bindable at all: discovery used to drop those silently -- no mapping, no error, no log -- so pc_natural_key_X was never written for that event type and a lookup after a key change came back null. Also fixes the error message in FindNaturalKeyDefinition, which pointed users at a NaturalKey() API that exists in neither Polecat nor JasperFx.Events; NaturalKeyFor() is the real escape hatch, and it became reachable in jasperfx#571. #368 (jasperfx#565). The daemon deliberately has no fallback type-name sniffing: a store's exception declares its own category, or the failure classifies as Other with no event details. Both halves land here. Part 1 -- Polecat's event loader raised bare InvalidOperationExceptions, so every read-side failure reached ShardFailure as Other. Two new exceptions implement IEventFailureContext: EventDeserializationFailureException (EventSerialization, carrying the sequence and the store's type alias rather than the assembly-qualified dotnet_type) and UnknownEventTypeException (UnknownEventType, kept distinct because a missing registration is a deployment fix, not a data fix). Part 2 -- four extended-progression columns behind the existing EnableExtendedProgressionTracking gate: failure_category (the enum NAME, never the ordinal), failure_event_sequence, failure_event_type, failure_event_tenant_id. No column for the reason text -- ShardFailure .Detail is exactly what pause_reason has always carried. They follow a different write rule from the rest: written when the state carries a Failure, CLEARED on a ShardAction.Started without one, otherwise LEFT ALONE. That last case is load-bearing -- SubscriptionAgent publishes a plain Stopped right behind a Paused, and an unconditional write would erase the reason microseconds after recording it. AllProjectionProgress rehydrates ShardState.Failure so a poller gets the same shape as a live observer. #367 (jasperfx#564). Docs only: a "Graceful Shutdown and the Drain Timeout" section covering what StopAndDrainTimeout bounds, the ProgressionProgressOutOfOrderException it prevents when the drain is cut short, pairing it with HostOptions.ShutdownTimeout and terminationGracePeriodSeconds, and what opting out costs. Full suite green: 1574 passed on net10, plus AspNetCore (53) and EF Core (37). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ETF4fye5MJk3kGX5kunrRk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the JasperFx family 2.34.0 → 2.36.1 and lands the three Polecat issues that ride on that line. They are batched because the natural key half is source breaking —
NaturalKeyEventMapping.Extractorwidens fromFunc<object, object?>toFunc<IEvent, object?>, so the bump does not compile without the call-site change.Closes #367, closes #368, closes #369.
#369 — natural key extraction on the widened contract (jasperfx#569 / #571)
NaturalKeyProjection.QueueOperationForEventpasses the wholeIEventtomapping.Extractorinstead ofe.Data. One call site, shared by the inline append path and the Natural-key lookup table not repopulated on projection rebuild (parity with marten#4788) #259 rebuild re-emit path.EventOperations.FindNaturalKeyDefinition, which told users to "Configure a natural key via NaturalKey() in a SingleStreamProjection registration" — an API that exists in neither Polecat nor JasperFx.Events. It now names[NaturalKey]/[NaturalKeySource]and the real escape hatch,NaturalKeyFor().Bug_369_natural_key_source_discovery) for what Polecat inherits from upstream: anIEvent<T>-parameter[NaturalKeySource]used to yield no mapping at all — silently, no error, no log — sopc_natural_key_Xwas simply never written for that event type. Pinned live and after a rebuild, since Polecat's rebuild path is its own code. Plus the explicitNaturalKeyFor(x => x.SetBy/SetByEvent)registration, which was unreachable dead code until jasperfx#571.NaturalKeyFor()section inevents/natural-keys.md, the loud-failure behaviour, and the constraint the attribute path now enforces — the key has to be a function of the event alone, because the lookup table is maintained inline at append time where no prior aggregate exists under anAsyncsnapshot lifecycle.#368 —
IEventFailureContext+ classified failure columns (jasperfx#565)The daemon deliberately has no fallback type-name sniffing: a store's exception declares its own category, or the failure classifies as
Otherwith no event details. Both halves land here.Part 1 — the read-path exceptions. Polecat's event loader raised bare
InvalidOperationExceptions, so every serialization or unknown-type failure reachedShardFailureasOther. Two new exceptions inPolecat.ExceptionsimplementIEventFailureContext:EventDeserializationFailureException→EventSerialization, carrying the sequence and the store's event type alias (thetypecolumn, not the assembly-qualifieddotnet_type— the alias is what a client can act on).UnknownEventTypeException→UnknownEventType, kept distinct on purpose: a missing registration or a rollback is a deployment fix, not a data fix.Part 2 — four extended-progression columns behind the existing
EnableExtendedProgressionTrackinggate:failure_category(the enum name, never the ordinal),failure_event_sequence,failure_event_type,failure_event_tenant_id. No column for the reason text —ShardFailure.Detailis exactly whatpause_reasonhas always carried.The failure columns follow a different write rule from the rest of extended progression: written when the state carries a
Failure, cleared on aShardAction.Startedthat has none (a recovered shard must stop reporting the reason it paused an hour ago), and otherwise left alone. That last case is load-bearing —SubscriptionAgentpublishes a plainStoppedright behind aPaused, and an unconditional write would erase the reason microseconds after recording it.AllProjectionProgressrehydratesShardState.Failurefrom the columns so a poller gets the same shape as a live observer.The columns are nullable and additive, so no backfill is needed; stores that already have extended tracking on do need the migration, and until it runs the write degrades to no telemetry rather than failing anything (extended progression is best-effort by contract).
#367 —
StopAndDrainTimeoutdocs (jasperfx#564)New "Graceful Shutdown and the Drain Timeout" section in
events/projections/async-daemon.md: what the timeout bounds (the in-flight page plus the progression flush, on all three stop paths), theProgressionProgressOutOfOrderExceptionit prevents when the drain is cut short, pairing a raised value withHostOptions.ShutdownTimeoutandterminationGracePeriodSeconds, why a deployment might instead lower it, and what opting out withTimeout.InfiniteTimeSpancosts.Tests
New:
event_failure_context_tests(exception contract +ShardFailure.Forclassification through wrapping andAggregateException),shard_failure_progression_columns_tests(write / clear / preserve / rehydrate / never-insert),Bug_369_natural_key_source_discovery, plus two event-loader cases covering the classified throw sites.event_store_instrumentation_testsextended for the four new columns.