Early April Releases for the Critter Stack

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 WolverineMarten, Weasel, and Polecat we shipped 4 Wolverine releases3 Marten releases6 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.0V5.29.0V5.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 types
  • describe-routing — Display all configured message routing rules
  • describe-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.type tag for more granular tracing
  • Saga spans are tagged with wolverine.saga.id and wolverine.saga.type
  • Aggregate handler workflow spans include wolverine.stream.id and wolverine.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] and ConnegMode
  • API versioning documentation
  • OnException convention for exception handling

Additional Notable Fixes

  • Fixed Turkish culture (dotless-i) corruption of SQL identifiers
  • Fixed EF Core DomainEventScraper O(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 FluentValidation configuration through UseFluentValidation overload (thanks @outofrange-consulting)

Marten

Marten shipped three releases this month: V8.29.0V8.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 NaturalKeySource discovery when methods are on the projection class
  • Fixed natural key and DCB tag operations with archived stream partitioning
  • Fixed mt_update and mt_upsert WHERE clauses for partitioned tables
  • Fixed long identifier names exceeding PostgreSQL’s NAMEDATALEN limit
  • Quoted column names in DuplicatedField update 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 MessagesImplementing overload (#2449)
  • @LodewijkSioen — ValidationResult as 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!

Leave a comment