feat(critterwatch): polecat outbox listener fix + DocumentStores capability surface#2672
Merged
jeremydmiller merged 1 commit intomainfrom May 4, 2026
Merged
Conversation
…bility surface Two CritterWatch-facing changes: 1. fix(polecat): wire SqlServerMessageStore into FlushOutgoingMessagesOnCommit GH-2668. OutboxedSessionFactory.buildSessionOptions used to pass `null!` for the SqlServerMessageStore on the assumption that a post-construction setter would fill it in — but the listener's field is readonly and no such setter exists. The first read of `_messageStore.Role` NRE'd, killing every Polecat-backed Wolverine handler that traversed the durable inbox (e.g. PolecatTrips in CritterWatch's BffHost). Direct cast to SqlServerMessageStore covers the simple case but throws InvalidCastException when runtime.Storage is a MultiTenantedMessageStore wrapping the SQL Server-backed root. Added resolveSqlServerMessageStore() that mirrors PolecatEnvelopeTransaction's existing two-shape resolution (SqlServerMessageStore | MultiTenantedMessageStore { Main: SqlServerMessageStore }) and throws a clear InvalidOperationException if the runtime isn't SQL-Server-backed at all. Coverage gap that let the bug ship: no PolecatTests test exercised the OutboxedSessionFactory listener path. Existing tests use InvokeMessageAndWaitAsync against non-durable defaults, which leaves Envelope.WasPersistedInInbox false and short-circuits the listener's interesting branch. The new PolecatTests/Bugs/Bug_2668_outboxed_session_listener_null_message_store.cs wires UseDurableLocalQueues + SendMessageAndWaitAsync so the local queue persists the envelope before the handler runs (flipping WasPersistedInInbox to true), and asserts the document the handler stores actually lands. With the bug the handler NREs in SaveChangesAsync and the document never persists; with the fix the round-trip completes. 2. feat(capabilities): expose DocumentStores list on ServiceCapabilities Mirrors the existing readEventStores walk for the document side. Walks IDocumentStoreUsageSource registrations (Marten and Polecat both implement it via IDocumentStore), dedupes by Subject URI to avoid double-counting when the same instance wears both event-store and document-store hats, and stuffs DocumentStoreUsage snapshots into the capabilities surface so CritterWatch can render document-side config the same way it already renders event stores. Stores that fail transient init are silently skipped — same permissive policy as the event-store path. 3. chore(deps): bump JasperFx 1.28.2 -> 1.29.0, JasperFx.Events 1.31.1 -> 1.33.1, Marten + Marten.AspNetCore 8.32.0 -> 8.35.0 IDocumentStoreUsageSource and DocumentStoreUsage live in the bumped JasperFx packages; the Marten bump tracks the latest version that ships the corresponding source-side wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 4, 2026
Minor release. Highlights: - WolverineFx.Marten: durable local messages routed by the receiving handler's ancillary store (Uri-based gate, no IMessageStore.Id dependency). Closes #2669. PR #2674. - WolverineFx.RabbitMQ: public AddClusterNode(...) API for multi-node failover. Closes #2659. PR #2664. - WolverineFx.Polecat: fixed NRE in OutboxedSessionFactory when constructing the FlushOutgoingMessagesOnCommit listener. Closes #2668. PR #2672. - WolverineFx core: new DocumentStores collection on ServiceCapabilities for CritterWatch document-side rendering. - Dependency bumps: JasperFx 1.28.2 → 1.29.0, JasperFx.Events 1.31.1 → 1.33.1, Marten + Marten.AspNetCore 8.32.0 → 8.35.0. Also backfilled a 5.36.2 entry in CHANGELOG covering the EF Core + outbox flush rework from PR #2665 that landed without a CHANGELOG note at the time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 4, 2026
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.
Two CritterWatch-facing changes plus the dependency bumps they need.
1. fix(polecat) — GH-2668
OutboxedSessionFactory.buildSessionOptionswas constructing theFlushOutgoingMessagesOnCommitlistener withnull!for theSqlServerMessageStoreon the assumption that a post-construction setter would fill it in — but the listener's field isreadonlyand no such setter exists. The first read of_messageStore.RoleNRE'd, killing every Polecat-backed Wolverine handler that traversed the durable inbox (e.g.PolecatTripsin CritterWatch's BffHost).Direct cast to
SqlServerMessageStorecovers the simple case but throwsInvalidCastExceptionwhenruntime.Storageis aMultiTenantedMessageStorewrapping the SQL-Server-backed root. AddedresolveSqlServerMessageStore()that mirrorsPolecatEnvelopeTransaction's existing two-shape resolution (SqlServerMessageStore | MultiTenantedMessageStore { Main: SqlServerMessageStore }) and throws a clearInvalidOperationExceptionif the runtime isn't SQL-Server-backed at all.Coverage gap that let the bug ship: no PolecatTests test exercised the
OutboxedSessionFactorylistener path. Existing tests useInvokeMessageAndWaitAsyncagainst non-durable defaults, which leavesEnvelope.WasPersistedInInboxfalse and short-circuits the listener's interesting branch. The newPolecatTests/Bugs/Bug_2668_outboxed_session_listener_null_message_store.cswiresUseDurableLocalQueues+SendMessageAndWaitAsyncso the local queue persists the envelope before the handler runs (flippingWasPersistedInInboxto true), and asserts the document the handler stores actually lands. With the bug present the handler NREs inSaveChangesAsyncand the document never persists; with the fix the round-trip completes.2. feat(capabilities) — DocumentStores list on ServiceCapabilities
Mirrors the existing
readEventStoreswalk for the document side. WalksIDocumentStoreUsageSourceregistrations (Marten and Polecat both implement it viaIDocumentStore), dedupes bySubjectURI to avoid double-counting when the same instance wears both event-store and document-store hats, and stuffsDocumentStoreUsagesnapshots into the capabilities surface so CritterWatch can render document-side config the same way it already renders event stores. Stores that fail transient init are silently skipped — same permissive policy as the event-store path.3. chore(deps) — JasperFx 1.28.2 → 1.29.0, JasperFx.Events 1.31.1 → 1.33.1, Marten + Marten.AspNetCore 8.32.0 → 8.35.0
IDocumentStoreUsageSourceandDocumentStoreUsagelive in the bumped JasperFx packages; the Marten bump tracks the latest version that ships the corresponding source-side wiring.Test plan
nuke compile— greendotnet test src/Persistence/PolecatTests --filter Bug_2668passes against the fixdotnet test ...against the pre-fix code fails with the exact NRE from Polecat: FlushOutgoingMessagesOnCommit NRE on every message handler — null SqlServerMessageStore passed at construction #2668.NETworkflow green🤖 Generated with Claude Code