
As active members of the Critter Stack community know, I’ve been increasingly concerned at our frequent release cadence, as that has been something we have been criticized for in the past. At least now, I feel like I can justifiably claim this is mostly due to the high volume of community contributions we get plus JasperFx Software client requests more than needing to patch new bugs.
April was an exceptionally busy month for the Critter Stack. Across Wolverine, Marten, Weasel, and Polecat we shipped 4 Wolverine releases, 3 Marten releases, 6 Weasel releases, and 3 Polecat releases — driven by a healthy mix of new features, important bug fixes, and a growing stream of community contributions. Here’s a tour of what’s new.
Wolverine
Wolverine saw four releases this month: V5.28.0, V5.29.0, V5.30.0, and V5.31.0, totaling 59 merged pull requests — the most active month in Wolverine’s history.
Wire Tap for Message Auditing
Wolverine now supports the Wire Tap pattern from the Enterprise Integration Patterns book. A wire tap lets you record a copy of every message flowing through configured endpoints for auditing, compliance, analytics, or monitoring — without affecting the primary message processing pipeline.
Implement the IWireTap interface:
public interface IWireTap { ValueTask RecordSuccessAsync(Envelope envelope); ValueTask RecordFailureAsync(Envelope envelope, Exception exception); }
Then enable it on specific endpoints:
opts.Services.AddSingleton<IWireTap, AuditWireTap>(); opts.ListenToRabbitQueue("incoming").UseWireTap(); opts.PublishAllMessages().ToRabbitExchange("outgoing").UseWireTap(); // Or enable across all listeners opts.Policies.AllListeners(x => x.UseWireTap());
You can even use keyed services to assign different wire tap implementations to different endpoints. See the full Wire Tap documentation for details.
Transport Health Checks
All Wolverine transports — RabbitMQ, Kafka, Azure Service Bus, Amazon SQS, Redis, NATS, and Pulsar — now expose ASP.NET Core IHealthCheck implementations. This means you can integrate transport connectivity checks directly into your existing health check infrastructure with minimal configuration.
Retry Jitter
When many nodes retry at the same fixed delay after a shared failure, they produce a “thundering herd” that can overwhelm a recovering dependency. Wolverine now supports additive jitter on delay-based error policies with three strategies:
// Full jitter: effective delay ∈ [d, 2·d] opts.OnException<DownstreamUnavailableException>() .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds()) .WithFullJitter(); // Bounded jitter: effective delay ∈ [d, d × (1 + percent)] opts.OnException<DownstreamUnavailableException>() .ScheduleRetry(1.Seconds(), 5.Seconds(), 30.Seconds()) .WithBoundedJitter(0.25); // +0% to +25% // Exponential jitter: spread widens with each attempt opts.OnException<DownstreamUnavailableException>() .PauseThenRequeue(5.Seconds()) .WithExponentialJitter();
Jitter only extends the configured delay, never shortens it — the configured values remain the lower bound. See the error handling docs for the full details. Thanks to @BlackChepo for the contribution in #2504.
IHandlerConfiguration Interface
Handler chain customization now has a compile-time safe alternative to the convention-based Configure(HandlerChain) method. Just implement IHandlerConfiguration:
public class InterfaceConfiguredHandler : IHandlerConfiguration { public void Handle(InterfaceConfiguredMessage message) { // handle the message } public static void Configure(HandlerChain chain) { chain.Middleware.Add(new CustomFrame()); chain.SuccessLogLevel = LogLevel.None; } }
This makes it explicit in the type system that your handler participates in chain configuration, rather than relying on method name conventions alone.
Header Propagation
Wolverine can now automatically forward headers from an incoming message to all outgoing messages produced within the same handler context. This is useful for propagating correlation identifiers or tracing metadata across a chain of messages:
builder.Host.UseWolverine(opts => { // Forward a single header opts.Policies.PropagateIncomingHeaderToOutgoing("x-on-behalf-of"); // Or forward multiple headers at once opts.Policies.PropagateIncomingHeadersToOutgoing( "x-correlation-id", "x-source-system"); });
This works across all transports. See the full header propagation docs. Thanks to @lyall-sc for the contribution in #2446.
Soft-Deleted Sagas with Marten
When a Marten-backed saga calls MarkCompleted(), Wolverine now respects Marten’s soft-delete configuration. If your saga type is configured for soft-deletes, the document will be soft-deleted rather than hard-deleted, allowing you to keep a history of completed sagas:
[SoftDeleted] public class SubscriptionSaga : Saga, ISoftDeleted { public Guid Id { get; set; } public string PlanName { get; set; } = string.Empty; public bool IsActive { get; set; } // ISoftDeleted members — Marten populates these automatically public bool Deleted { get; set; } public DateTimeOffset? DeletedAt { get; set; } public void Handle(CancelSubscription command) { IsActive = false; MarkCompleted(); // Marten will soft-delete instead of hard-delete } public void Handle(UpgradeSubscription command) { if (Deleted) { // Saga was already completed — do nothing return; } PlanName = command.NewPlanName; } }
See the Marten saga documentation for more details.
TenantId Overloads for MartenOps
All MartenOps and IMartenOp return types now support explicit TenantId overloads, making it straightforward to perform cross-tenant operations in multi-tenanted Marten setups from within Wolverine handlers.
New Wolverine Diagnostics CLI Commands
Three new sub-commands were added to help diagnose Wolverine applications that were specifically added with AI assisted development in mind:
codegen-preview— Preview generated handler code for specific message typesdescribe-routing— Display all configured message routing rulesdescribe-resiliency— Show error handling and circuit breaker configurations
OTEL Improvements
This is largely about some future CritterWatch work to be able to quickly tie Wolverine messages to the full Open Telemetry span history in Jaeger, DataDog, or AppInsights (for now).
- Handler and HTTP endpoint spans now include a
handler.typetag for more granular tracing - Saga spans are tagged with
wolverine.saga.idandwolverine.saga.type - Aggregate handler workflow spans include
wolverine.stream.idandwolverine.stream.type - Fixed a trace ID leak after circuit breaker trip/resume cycles (#2494)
Wolverine.HTTP Enhancements
I made a concerted effort a couple weeks ago to try to plus any remaining gaps between Wolverine.HTTP and ASP.Net Core Minimal API or MVC Core. That also included quite a bit of new documentation on the Wolverine website to map MVC Core concepts to Wolverine.HTTP concepts — or just tell you which ASP.Net Core features work as is with Wolverine.HTTP.
V5.28.0 included a substantial expansion of the HTTP capabilities:
- Route prefix groups for organizing endpoints
- Antiforgery/CSRF protection for form endpoints
- SSE/streaming response support
- Rate limiting integration
- Output caching integration
- Content negotiation with
[Writes]andConnegMode - API versioning documentation
OnExceptionconvention for exception handling
Additional Notable Fixes
- Fixed Turkish culture (dotless-i) corruption of SQL identifiers
- Fixed EF Core
DomainEventScraperO(n) full ChangeTracker scan (#2476) - Fixed strong-typed saga ID causing CS0246 code generation errors
- Parallelized tenant database initialization in EF Core multi-tenancy
- Fixed Redis stream listener ignoring endpoint
DatabaseId(thanks @BlackChepo) - Fixed CloudEvents fallback and message aliases (thanks @lahma)
- Exposed
FluentValidationconfiguration throughUseFluentValidationoverload (thanks @outofrange-consulting)
Marten
Marten shipped three releases this month: V8.29.0, V8.29.3, and V8.30.0.
EnableBigIntEvents for 64-bit Event Sequences
For high-volume event stores, Marten now supports 64-bit event sequences via a new EnableBigIntEvents flag. This addresses the int32 overflow reported in #4246 where mt_quick_append_events could fail with “integer out of range” when sequences exceed int32 limits. Thanks to @vicmaeg for both reporting and contributing the SQL fix in #4248.
ProjectLatest API
A new ProjectLatest API lets you project aggregates with pending (uncommitted) events — useful for validation scenarios where you need to see the would-be state of an aggregate before committing or return the new state of a projection from a Wolverine command handler.
EnrichEventsAsync Hook
EventProjection now supports an EnrichEventsAsync hook, allowing you to augment events with additional data during projection processing.
ConfigureNpgsqlDataSourceBuilder
Marten now exposes ConfigureNpgsqlDataSourceBuilder for Npgsql plugin registration, making it easier to register custom type mappings and other Npgsql extensions. This innocuous little item was added to enable our commercial add ons for Marten using PgVector and PostGIS.
OrderByNgramRank for Full-Text Search
A new OrderByNgramRank method enables relevance-sorted ngram search results, useful for “fuzzy” full-text search scenarios where you want to rank results by how closely they match.
Adaptive EventLoader for Sparse Projections
Long story short, this change made Marten’s Async Daemon more resilient for certain runtime circumstances.
An opt-in event type index and adaptive EventLoader now make sparse projections significantly more efficient — projections that only care about a small subset of event types can skip irrelevant events entirely at the database level.
Removed FSharp.Core Compile-Time Dependency
Marten no longer has a compile-time dependency on FSharp.Core, reducing dependency bloat for C#-only projects.
Notable Bug Fixes
- Fixed EF Core 10 JSON column mapping compatibility (via Weasel 8.11.4)
- Fixed
NaturalKeySourcediscovery when methods are on the projection class - Fixed natural key and DCB tag operations with archived stream partitioning
- Fixed
mt_updateandmt_upsertWHERE clauses for partitioned tables - Fixed long identifier names exceeding PostgreSQL’s NAMEDATALEN limit
- Quoted column names in
DuplicatedFieldupdate SQL fragments
Weasel
Weasel had six releases this month, from V8.11.2 through V8.13.0. The headline feature is the new EF Core testing infrastructure.
EF Core Batch Queries
Wolverine is also able to support this in code generation similar to what it does today for Marten. Blow post coming soon on this.
The new BatchedQuery API combines multiple IQueryable<T> queries into a single database round trip using ADO.NET’s DbBatch. This works across PostgreSQL, SQL Server, and SQLite — a significant performance win for scenarios that need to load multiple independent datasets.
IDatabaseCleaner for Integration Testing
Inspired by Respawn and Marten’s ResetAllData(), the new IDatabaseCleaner<TContext> provides FK-aware database cleanup for integration testing with EF Core. It supports multi-tenant scenarios with explicit DbConnection overloads and uses provider-specific SQL for PostgreSQL, SQL Server, and SQLite.
PostgreSQL Identifier Improvements
- New
PostgresqlIdentifier.Shorten()for deterministic identifier truncation when names exceed PostgreSQL’s NAMEDATALEN limit. This has been a continuously annoying problem since Marten 1.0! - Reserved keywords are now properly quoted in index column expressions and function update fragments (thanks @MarkVDD for the contributions)
Notable Bug Fixes
- Fixed primary key migration when other tables have referencing foreign keys
- Fixed deadlock in
ManagedListPartitions.InitializeAsync - Fixed culture-invariant SQL identifier casing (Turkish locale issue)
- Fixed Npgsql v10 compatibility for cidr/IPNetwork mapping
- Fixed EF Core 10 JSON column mapping
VitePress Documentation Site
Weasel now has its own VitePress documentation site, making it easier to find and navigate Weasel-specific documentation. We’ll have that live in the next week or two.
Polecat
Polecat shipped three releases: V1.6.1, V2.0.0, and V2.0.1.
Polecat 2.0 — SQL Server 2025 Native JSON
Yeah, this one was an oopsie we fixed:(
The major V2.0.0 release defaults to SQL Server 2025’s native JSON column type, taking advantage of the database engine’s built-in JSON support for better performance and query capabilities.
DDL Migration to Weasel SchemaMigration
V2.0.1 migrated all DDL generation to Weasel’s SchemaMigration infrastructure, aligning Polecat with the rest of the Critter Stack’s database migration approach.
EnrichEventsAsync and Time-Based Projections
Polecat picked up EnrichEventsAsync tests for EventProjection and a new time-based multi-stream projection example, expanding the projection capabilities available on SQL Server.
Community Contributions
A special thank you to the community contributors who made April so productive:
- @BlackChepo — Retry jitter support (#2504), Redis stream DatabaseId fix (#2452), and silent message loss fix for RabbitMQ/MQTT (#2511)
- @lyall-sc — Header propagation (#2446) and publicly exposed
MetadataRules(#2464) - @outofrange-consulting — FluentValidation configuration overload (#2497) and MassTransit envelope header fix (#2439)
- @lahma — CloudEvents fixes (#2453) and NUKE build upgrade (#2454)
- @vicmaeg — Marten int32 overflow fix (#4248)
- @MarkVDD — Weasel PostgreSQL reserved keyword quoting (#238, #242)
- @Sonic198 — Kafka PartitionId on Envelope (#2440)
- @Ferchke7 — Fluent circuit breaker configuration for Kafka listeners (#2506)
- @codeswithfists — OpenAPI OperationId and Summary/Description support (#2445)
- @ali-pouneh — New
MessagesImplementingoverload (#2449) - @LodewijkSioen —
ValidationResultas validation return type (#2332) - @dmytro-pryvedeniuk — Trigger restriction fix (#2398) and Alba auto-start fix (#2411)
- @Shield1739, @benv-nti, @ericwscott, @Blackclaws — Documentation fixes across the stack
April’s new contributors: @Sonic198, @ali-pouneh, @codeswithfists, @Ferchke7, and @ericwscott — welcome to the Critter Stack!
What’s Next
We’ll see! JasperFx & I are admittedly moving more to our commercial add on tools for a little bit.
As always, find us on the JasperFx Discord or file issues on GitHub!