Skip to content

Bump WolverineFx and 6 others#187

Merged
emeraldleaf merged 1 commit into
mainfrom
dependabot/nuget/wolverine-59db9e4210
Jul 20, 2026
Merged

Bump WolverineFx and 6 others#187
emeraldleaf merged 1 commit into
mainfrom
dependabot/nuget/wolverine-59db9e4210

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 20, 2026

Copy link
Copy Markdown
Contributor

Pinned WolverineFx at 6.21.0.

Release notes

Sourced from WolverineFx's releases.

6.21.0

Wolverine 6.21.0 is a big one: conjoined multi-tenancy for EF Core, and a measured messaging-performance wave across Kafka and RabbitMQ.

Conjoined multi-tenancy for EF Core (#​3465)

Mark an EF Core entity with ITenanted (the marker shared critter-stack-wide from JasperFx.MultiTenancy) and register your DbContext with AddDbContextWithWolverineManagedConjoinedTenancy<T>(), and Wolverine gives you what Marten users have had for years: a mapped tenant_id column, a tenant-bound global query filter you can't forget, stamp-on-insert, CrossTenantWriteException on cross-tenant writes, conjoined sagas, opt-in Weasel-managed physical tenant partitioning (PostgreSQL list partitions + SQL Server tenant-ordinal), and an authoritative wolverine_tenants registry that doubles as a dynamic tenant source and feeds CritterWatch tenant management. The behavior is checked against a port of Marten's conjoined-tenancy compliance battery. See the new ConjoinedMultiTenantedEfCore sample app and docs.

Messaging performance (GH-3490 / GH-3492)

A client-reported "Wolverine-over-Kafka is 3-12x slower than native" investigation turned into a measured optimization wave (methodology, rigs, and full ledgers are in the repo):

  • Sender batching now flushes within the batch timeout (JasperFx 2.30.1). The batch timer was a quiet-period debounce — a steady 8 msg/s stream at the default (100, 250ms) settings measured 5.8 seconds publish-to-consume p50; it now measures 136ms, bounded by the timeout. Affects every transport that sends through the batched sender (RabbitMQ routes were unaffected — they don't).
  • Durable (inbox-backed) listeners batch their inbox writes. Kafka: a 2,000 msg/s stream went from unbounded backlog to a steady 32ms delivery p50, max sustained durable throughput +83%. RabbitMQ: same load went from 14.7s-and-climbing to 0.8ms delivery p50, max sustained durable throughput +186% (1,086 → 3,101 msg/s). New MaximumMessagesToReceive listener knob on both (default 100; 1 restores strict message-at-a-time persistence).
  • Kafka hot path: incoming envelope mapping ~21% faster / ~28% less allocation per message; outgoing ~17% faster; inline senders no longer issue a blocking full-producer flush per send. RabbitMQ's mapper gets the same incoming fix.
  • Batch-arrival correctness: the Envelope[] arrival path now applies the same per-envelope guards as single-message arrival (interop serializer unwrap, dead-lettering of unidentifiable messages, expiry, drain latching), and batched inbox writes route to ancillary message stores correctly.
  • SQS: SendMessageBatch per-entry failures (throttling, oversize) are now routed to Wolverine's retry machinery instead of being silently dropped — a silent message-loss fix (GH-3493).
  • Out-of-the-box RabbitMQ consumption is Inline — one message at a time, which measures identically to an equivalent single-consumer raw-client loop. The new RabbitMQ "Performance Tuning" docs page covers scaling with BufferedInMemory() / ListenerCount() with measured numbers, and a matching page exists for Kafka.

Behavior changes to note

  • The per-message "successfully processed" log now defaults to Debug (was Information) — restore with opts.Policies.MessageSuccessLogLevel(LogLevel.Information).
  • wolverine-execution-time is now a floating-point histogram and no longer silently drops sub-millisecond executions (same name/unit; the point type changes).

Transports and messaging

  • Azure Service Bus: PrefetchCount on listeners and transport defaults (GH-3471)
  • SQS: native DelaySeconds for short scheduled sends on standard queues (GH-3472)
  • Pulsar: native scheduled delivery via DeliverAt (GH-3470)
  • Kafka: KIP-848 next-generation consumer rebalance protocol support (GH-3473)
  • Standardized diagnostic headers on dead-letter and retry moves across all transports (GH-3474)
  • SignalR cascading responses are no longer dropped when the HTTP context has already flushed (GH-3499)
  • The dead-letter metric only counts genuine moves to the error queue (#​3495)
  • Global partitioning docs now cover all eight sharded transports (GH-3466)
  • Tolerate empty agent URI lists in agent command serialization (GH-3460)

HTTP / gRPC

  • Code-first gRPC client streaming: IAsyncEnumerable<TRequest> -> Task<TResponse> handler shape (#​3500)
  • Proto-first gRPC client streaming via the new StreamAsync<TRequest, TResponse> overload (#​3459)

Dependencies

JasperFx 2.30.1 (sender-batching max-age fix), Weasel 9.18.1, Marten 9.16.1.

6.20.0

Wolverine 6.20.0

Dependency upgrades (critter stack)

  • Marten 9.16.0, Polecat 5.2.0, JasperFx 2.29.0, Weasel 9.17.0 (aligned across the stack).

Multi-tenancy & connection footprint

  • Scope tenant scheduled-job polling to the owning node (GH-3376) — under Wolverine-managed distribution, nodes no longer open a scheduled-job polling connection to every tenant database; the footprint scales with databases, not nodes × databases. Plus daemon tracker-subscription leak hygiene.
  • Polecat primary-store database-per-tenant (GH-3445) — IntegrateWithWolverine() now honors a database-per-tenant Polecat store and reads MainDatabaseConnectionString.
  • DatabaseServerId sourced from DatabaseDescriptor.Port instead of re-parsing (jasperfx#​514).

CritterWatch / connection state

  • Degrade-only connection state for Azure Service Bus, GCP Pub/Sub (GH-3237) and Kafka (GH-3454) listeners — heuristics only degrade to Reconnecting/Disconnected from real SDK callbacks; they never synthesize Connected, and resting state is Unknown.

Kafka

  • Read record timestamps into SentAt and expose record headers on raw-JSON listeners (GH-3407).
  • JsonSerializerOptions on raw-JSON endpoints now actually applies; PublishRawJson mapper registration fixed.
  • Bounded the listener shutdown drain instead of awaiting forever (GH-3434).

Sagas

  • SagaConcurrencyException now inherits JasperFx.ConcurrencyException (GH-3444) — existing OnException<ConcurrencyException>() policies now catch saga concurrency failures.
  • Lightweight RDBMS saga provider treated as a catch-all so Marten/EF Core win precedence in mixed-storage hosts (GH-3443).

Other

  • Ancillary Marten store now registers the event-subscription family interface aliases (GH-3438).
  • F# code generation improvements (#​3437); TrackedSession not-tracked vs not-routed fix (#​3435).
  • Buffered circuit-breaker tests re-scoped to the transport's non-durable contract (GH-3137); durability docs corrections.

6.19.0

CosmosDB

  • Add optimistic concurrency (ETag compare-and-swap) to saga persistence (GH-3414) @​mysticmind
  • CosmosDbConfiguration.PartitionSagasById(): opt-in, saga id becomes the document partition key (GH-3415) @​mysticmind
  • Refuse a CosmosClient whose serializer would drop a saga's id at host start; document the camelCase requirement (GH-3416) @​mysticmind

HTTP / OpenAPI

  • Describe the whole route table on a pre-start ApiExplorer read; minimal API and MVC endpoints were silently omitted (GH-3421) @​mysticmind
  • Type a Marten/Polecat aggregate-id route parameter as uuid instead of falling back to string (GH-3420) @​mysticmind

Durability / persistence

  • Dead letter recovered envelopes whose transport can't be resolved, instead of losing the rest of the batch and rethrowing forever (GH-3413) @​mysticmind
  • Measure and surface per-server database connection budgets: OTel gauges + IWolverineObserver.ConnectionBudget (GH-3397) @​jeremydmiller

Test infrastructure only

  • Stop IntegrationContext from disposing a class fixture it doesn't own; pin ApplicationAssembly in the CoreTests harness (GH-3423) @​jeremydmiller
  • Give the modular monolith fixture its own message storage schema (GH-3413) @​mysticmind
  • Stop using_dynamic_multi_tenancy from poisoning its own next run @​mysticmind

Milestone: https://github.com/JasperFx/wolverine/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A6.19.0
Full Changelog: JasperFx/wolverine@V6.18.0...V6.19.0

6.18.0

Wolverine 6.18.0

A security-relevant serialization fix, a startup-fatal codegen fix, a silently-dead-listener fix in RabbitMQ, the first F# saga codegen support of any persistence provider, and the CI split that makes "merge when green" mean something again.

If you use MassTransit interop over a durable listener, take this release. See the first section.

⚠️ Security-relevant: reserved envelope headers could be spoofed through the durable inbox

#​3408fixed in #​3411

EnvelopeSerializer wrote the typed envelope properties to the wire format and then appended every Envelope.Headers entry verbatim, with no reserved-key filter — and the appended entries came last. Because the reader parses reserved keys straight back into typed properties, a Headers entry under a reserved key silently overwrote the real property on the next read.

A value in envelope.Headers["tenant-id"] is inert while the envelope is in memory. It stops being inert the moment the envelope crosses the serializer — any durable listener, the inbox/outbox, or the scheduled-message store:

  1. Something puts tenant-id into envelope.Headers.
  2. The durable inbox persists the envelope; the header is appended after the (null) typed property.
  3. On read back, env.TenantId is set from it.

saga-id reaches another saga's state, and id rewrites Envelope.Id — the inbox's dedupe identity.

This was live, not theoretical. MassTransitEnvelope.TransferData already copies every incoming MassTransit header into envelope.Headers unfiltered (and by assignment, not TryAdd). Any Wolverine app doing MassTransit interop over a durable listener has had this path open. If that describes you, this release is the one to take.

The fix filters reserved keys on the write side, so the typed property stays authoritative and a reserved key sitting in Headers becomes a no-op. causation-id is deliberately not filtered — DeliveryOptions intentionally carries it as a loose header for Wolverine.Marten's OutboxedSessionFactory, and it is never promoted by the reader.

Startup-fatal codegen fix

#​3399fixed in #​3406 — invalid generated class name for batched (array) message types. This one prevents the application from starting.

Fixes

  • #​3388 (#​3400) — refuse a competing Marten daemon under Wolverine-managed event subscription distribution. A DaemonMode.Solo/HotCold daemon alongside managed distribution is now an actionable startup exception instead of two schedulers quietly fighting over the same shards.
  • CritterWatch #​698 (#​3396) — IAgentRuntime.ApplyRestrictionsAsync persisted the restriction and then never dispatched the commands it computed, so pausing an agent had no immediate effect. Reported by @​erdtsieck against a live cluster.
  • #​3385 (#​3403) — a header-identified saga invoked over a gRPC hop failed with an opaque Internal status. It now returns an actionable diagnostic telling you to put the saga identity on the request DTO.
  • #​3398 (#​3404) — [AsParameters] now rejects unparseable values in collection query parameters, closing the gap left by the scalar fix in #​3372.
  • #​3365 (#​3412) — the Polecat primary IEventStore bridge registered twice, so GetServices<IEventStore>() returned the same store instance two times and anything iterating it double-counted. Polecat's own AddPolecat() had started registering IEventStore and Wolverine was still bridging it as well.
  • #​3391 (#​3419) — RabbitMQ: a successful eager channel restart never re-consumed. A callback-exception restart could leave an open channel with zero consumers while reporting State = Connected — a silently dead listener. The listener now defers to ReconnectedAsync(), which re-declares and re-consumes. Also pins the ConnectionMonitor tracking invariant that #​3370 fixed but nothing guarded.

OpenAPI

#​3380 (#​3418) — OpenAPI parameters are now derived from the full binding chain rather than the handler signature alone. Two real defects closed:

  • Query/header values bound only by an After/Finally postprocessor were omitted from the operation entirely.
  • Route parameter types were read off resolved binding variables, so they degraded to the route constraint (or string) whenever the description was assembled before those frames resolved — which is exactly the build-time OpenAPI / openapi CLI path, because ASP.NET caches the first ApiExplorer read.

More importantly, this ships the OpenAPI shape-test harness that was missing. Adding a shape assertion is now one endpoint plus one [Fact], which is why this class of omission kept shipping unnoticed.

New: Azure Service Bus emulator support

#​3366 (#​3409) — the docs told you to call UseAzureServiceBusTesting(), which only ever existed in Wolverine's own test suite. It is now a real, shipping API:

... (truncated)

Commits viewable in compare view.

Pinned WolverineFx.EntityFrameworkCore at 6.21.0.

Release notes

Sourced from WolverineFx.EntityFrameworkCore's releases.

6.21.0

Wolverine 6.21.0 is a big one: conjoined multi-tenancy for EF Core, and a measured messaging-performance wave across Kafka and RabbitMQ.

Conjoined multi-tenancy for EF Core (#​3465)

Mark an EF Core entity with ITenanted (the marker shared critter-stack-wide from JasperFx.MultiTenancy) and register your DbContext with AddDbContextWithWolverineManagedConjoinedTenancy<T>(), and Wolverine gives you what Marten users have had for years: a mapped tenant_id column, a tenant-bound global query filter you can't forget, stamp-on-insert, CrossTenantWriteException on cross-tenant writes, conjoined sagas, opt-in Weasel-managed physical tenant partitioning (PostgreSQL list partitions + SQL Server tenant-ordinal), and an authoritative wolverine_tenants registry that doubles as a dynamic tenant source and feeds CritterWatch tenant management. The behavior is checked against a port of Marten's conjoined-tenancy compliance battery. See the new ConjoinedMultiTenantedEfCore sample app and docs.

Messaging performance (GH-3490 / GH-3492)

A client-reported "Wolverine-over-Kafka is 3-12x slower than native" investigation turned into a measured optimization wave (methodology, rigs, and full ledgers are in the repo):

  • Sender batching now flushes within the batch timeout (JasperFx 2.30.1). The batch timer was a quiet-period debounce — a steady 8 msg/s stream at the default (100, 250ms) settings measured 5.8 seconds publish-to-consume p50; it now measures 136ms, bounded by the timeout. Affects every transport that sends through the batched sender (RabbitMQ routes were unaffected — they don't).
  • Durable (inbox-backed) listeners batch their inbox writes. Kafka: a 2,000 msg/s stream went from unbounded backlog to a steady 32ms delivery p50, max sustained durable throughput +83%. RabbitMQ: same load went from 14.7s-and-climbing to 0.8ms delivery p50, max sustained durable throughput +186% (1,086 → 3,101 msg/s). New MaximumMessagesToReceive listener knob on both (default 100; 1 restores strict message-at-a-time persistence).
  • Kafka hot path: incoming envelope mapping ~21% faster / ~28% less allocation per message; outgoing ~17% faster; inline senders no longer issue a blocking full-producer flush per send. RabbitMQ's mapper gets the same incoming fix.
  • Batch-arrival correctness: the Envelope[] arrival path now applies the same per-envelope guards as single-message arrival (interop serializer unwrap, dead-lettering of unidentifiable messages, expiry, drain latching), and batched inbox writes route to ancillary message stores correctly.
  • SQS: SendMessageBatch per-entry failures (throttling, oversize) are now routed to Wolverine's retry machinery instead of being silently dropped — a silent message-loss fix (GH-3493).
  • Out-of-the-box RabbitMQ consumption is Inline — one message at a time, which measures identically to an equivalent single-consumer raw-client loop. The new RabbitMQ "Performance Tuning" docs page covers scaling with BufferedInMemory() / ListenerCount() with measured numbers, and a matching page exists for Kafka.

Behavior changes to note

  • The per-message "successfully processed" log now defaults to Debug (was Information) — restore with opts.Policies.MessageSuccessLogLevel(LogLevel.Information).
  • wolverine-execution-time is now a floating-point histogram and no longer silently drops sub-millisecond executions (same name/unit; the point type changes).

Transports and messaging

  • Azure Service Bus: PrefetchCount on listeners and transport defaults (GH-3471)
  • SQS: native DelaySeconds for short scheduled sends on standard queues (GH-3472)
  • Pulsar: native scheduled delivery via DeliverAt (GH-3470)
  • Kafka: KIP-848 next-generation consumer rebalance protocol support (GH-3473)
  • Standardized diagnostic headers on dead-letter and retry moves across all transports (GH-3474)
  • SignalR cascading responses are no longer dropped when the HTTP context has already flushed (GH-3499)
  • The dead-letter metric only counts genuine moves to the error queue (#​3495)
  • Global partitioning docs now cover all eight sharded transports (GH-3466)
  • Tolerate empty agent URI lists in agent command serialization (GH-3460)

HTTP / gRPC

  • Code-first gRPC client streaming: IAsyncEnumerable<TRequest> -> Task<TResponse> handler shape (#​3500)
  • Proto-first gRPC client streaming via the new StreamAsync<TRequest, TResponse> overload (#​3459)

Dependencies

JasperFx 2.30.1 (sender-batching max-age fix), Weasel 9.18.1, Marten 9.16.1.

6.20.0

Wolverine 6.20.0

Dependency upgrades (critter stack)

  • Marten 9.16.0, Polecat 5.2.0, JasperFx 2.29.0, Weasel 9.17.0 (aligned across the stack).

Multi-tenancy & connection footprint

  • Scope tenant scheduled-job polling to the owning node (GH-3376) — under Wolverine-managed distribution, nodes no longer open a scheduled-job polling connection to every tenant database; the footprint scales with databases, not nodes × databases. Plus daemon tracker-subscription leak hygiene.
  • Polecat primary-store database-per-tenant (GH-3445) — IntegrateWithWolverine() now honors a database-per-tenant Polecat store and reads MainDatabaseConnectionString.
  • DatabaseServerId sourced from DatabaseDescriptor.Port instead of re-parsing (jasperfx#​514).

CritterWatch / connection state

  • Degrade-only connection state for Azure Service Bus, GCP Pub/Sub (GH-3237) and Kafka (GH-3454) listeners — heuristics only degrade to Reconnecting/Disconnected from real SDK callbacks; they never synthesize Connected, and resting state is Unknown.

Kafka

  • Read record timestamps into SentAt and expose record headers on raw-JSON listeners (GH-3407).
  • JsonSerializerOptions on raw-JSON endpoints now actually applies; PublishRawJson mapper registration fixed.
  • Bounded the listener shutdown drain instead of awaiting forever (GH-3434).

Sagas

  • SagaConcurrencyException now inherits JasperFx.ConcurrencyException (GH-3444) — existing OnException<ConcurrencyException>() policies now catch saga concurrency failures.
  • Lightweight RDBMS saga provider treated as a catch-all so Marten/EF Core win precedence in mixed-storage hosts (GH-3443).

Other

  • Ancillary Marten store now registers the event-subscription family interface aliases (GH-3438).
  • F# code generation improvements (#​3437); TrackedSession not-tracked vs not-routed fix (#​3435).
  • Buffered circuit-breaker tests re-scoped to the transport's non-durable contract (GH-3137); durability docs corrections.

6.19.0

CosmosDB

  • Add optimistic concurrency (ETag compare-and-swap) to saga persistence (GH-3414) @​mysticmind
  • CosmosDbConfiguration.PartitionSagasById(): opt-in, saga id becomes the document partition key (GH-3415) @​mysticmind
  • Refuse a CosmosClient whose serializer would drop a saga's id at host start; document the camelCase requirement (GH-3416) @​mysticmind

HTTP / OpenAPI

  • Describe the whole route table on a pre-start ApiExplorer read; minimal API and MVC endpoints were silently omitted (GH-3421) @​mysticmind
  • Type a Marten/Polecat aggregate-id route parameter as uuid instead of falling back to string (GH-3420) @​mysticmind

Durability / persistence

  • Dead letter recovered envelopes whose transport can't be resolved, instead of losing the rest of the batch and rethrowing forever (GH-3413) @​mysticmind
  • Measure and surface per-server database connection budgets: OTel gauges + IWolverineObserver.ConnectionBudget (GH-3397) @​jeremydmiller

Test infrastructure only

  • Stop IntegrationContext from disposing a class fixture it doesn't own; pin ApplicationAssembly in the CoreTests harness (GH-3423) @​jeremydmiller
  • Give the modular monolith fixture its own message storage schema (GH-3413) @​mysticmind
  • Stop using_dynamic_multi_tenancy from poisoning its own next run @​mysticmind

Milestone: https://github.com/JasperFx/wolverine/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A6.19.0
Full Changelog: JasperFx/wolverine@V6.18.0...V6.19.0

6.18.0

Wolverine 6.18.0

A security-relevant serialization fix, a startup-fatal codegen fix, a silently-dead-listener fix in RabbitMQ, the first F# saga codegen support of any persistence provider, and the CI split that makes "merge when green" mean something again.

If you use MassTransit interop over a durable listener, take this release. See the first section.

⚠️ Security-relevant: reserved envelope headers could be spoofed through the durable inbox

#​3408fixed in #​3411

EnvelopeSerializer wrote the typed envelope properties to the wire format and then appended every Envelope.Headers entry verbatim, with no reserved-key filter — and the appended entries came last. Because the reader parses reserved keys straight back into typed properties, a Headers entry under a reserved key silently overwrote the real property on the next read.

A value in envelope.Headers["tenant-id"] is inert while the envelope is in memory. It stops being inert the moment the envelope crosses the serializer — any durable listener, the inbox/outbox, or the scheduled-message store:

  1. Something puts tenant-id into envelope.Headers.
  2. The durable inbox persists the envelope; the header is appended after the (null) typed property.
  3. On read back, env.TenantId is set from it.

saga-id reaches another saga's state, and id rewrites Envelope.Id — the inbox's dedupe identity.

This was live, not theoretical. MassTransitEnvelope.TransferData already copies every incoming MassTransit header into envelope.Headers unfiltered (and by assignment, not TryAdd). Any Wolverine app doing MassTransit interop over a durable listener has had this path open. If that describes you, this release is the one to take.

The fix filters reserved keys on the write side, so the typed property stays authoritative and a reserved key sitting in Headers becomes a no-op. causation-id is deliberately not filtered — DeliveryOptions intentionally carries it as a loose header for Wolverine.Marten's OutboxedSessionFactory, and it is never promoted by the reader.

Startup-fatal codegen fix

#​3399fixed in #​3406 — invalid generated class name for batched (array) message types. This one prevents the application from starting.

Fixes

  • #​3388 (#​3400) — refuse a competing Marten daemon under Wolverine-managed event subscription distribution. A DaemonMode.Solo/HotCold daemon alongside managed distribution is now an actionable startup exception instead of two schedulers quietly fighting over the same shards.
  • CritterWatch #​698 (#​3396) — IAgentRuntime.ApplyRestrictionsAsync persisted the restriction and then never dispatched the commands it computed, so pausing an agent had no immediate effect. Reported by @​erdtsieck against a live cluster.
  • #​3385 (#​3403) — a header-identified saga invoked over a gRPC hop failed with an opaque Internal status. It now returns an actionable diagnostic telling you to put the saga identity on the request DTO.
  • #​3398 (#​3404) — [AsParameters] now rejects unparseable values in collection query parameters, closing the gap left by the scalar fix in #​3372.
  • #​3365 (#​3412) — the Polecat primary IEventStore bridge registered twice, so GetServices<IEventStore>() returned the same store instance two times and anything iterating it double-counted. Polecat's own AddPolecat() had started registering IEventStore and Wolverine was still bridging it as well.
  • #​3391 (#​3419) — RabbitMQ: a successful eager channel restart never re-consumed. A callback-exception restart could leave an open channel with zero consumers while reporting State = Connected — a silently dead listener. The listener now defers to ReconnectedAsync(), which re-declares and re-consumes. Also pins the ConnectionMonitor tracking invariant that #​3370 fixed but nothing guarded.

OpenAPI

#​3380 (#​3418) — OpenAPI parameters are now derived from the full binding chain rather than the handler signature alone. Two real defects closed:

  • Query/header values bound only by an After/Finally postprocessor were omitted from the operation entirely.
  • Route parameter types were read off resolved binding variables, so they degraded to the route constraint (or string) whenever the description was assembled before those frames resolved — which is exactly the build-time OpenAPI / openapi CLI path, because ASP.NET caches the first ApiExplorer read.

More importantly, this ships the OpenAPI shape-test harness that was missing. Adding a shape assertion is now one endpoint plus one [Fact], which is why this class of omission kept shipping unnoticed.

New: Azure Service Bus emulator support

#​3366 (#​3409) — the docs told you to call UseAzureServiceBusTesting(), which only ever existed in Wolverine's own test suite. It is now a real, shipping API:

... (truncated)

Commits viewable in compare view.

Pinned WolverineFx.FluentValidation at 6.21.0.

Release notes

Sourced from WolverineFx.FluentValidation's releases.

6.21.0

Wolverine 6.21.0 is a big one: conjoined multi-tenancy for EF Core, and a measured messaging-performance wave across Kafka and RabbitMQ.

Conjoined multi-tenancy for EF Core (#​3465)

Mark an EF Core entity with ITenanted (the marker shared critter-stack-wide from JasperFx.MultiTenancy) and register your DbContext with AddDbContextWithWolverineManagedConjoinedTenancy<T>(), and Wolverine gives you what Marten users have had for years: a mapped tenant_id column, a tenant-bound global query filter you can't forget, stamp-on-insert, CrossTenantWriteException on cross-tenant writes, conjoined sagas, opt-in Weasel-managed physical tenant partitioning (PostgreSQL list partitions + SQL Server tenant-ordinal), and an authoritative wolverine_tenants registry that doubles as a dynamic tenant source and feeds CritterWatch tenant management. The behavior is checked against a port of Marten's conjoined-tenancy compliance battery. See the new ConjoinedMultiTenantedEfCore sample app and docs.

Messaging performance (GH-3490 / GH-3492)

A client-reported "Wolverine-over-Kafka is 3-12x slower than native" investigation turned into a measured optimization wave (methodology, rigs, and full ledgers are in the repo):

  • Sender batching now flushes within the batch timeout (JasperFx 2.30.1). The batch timer was a quiet-period debounce — a steady 8 msg/s stream at the default (100, 250ms) settings measured 5.8 seconds publish-to-consume p50; it now measures 136ms, bounded by the timeout. Affects every transport that sends through the batched sender (RabbitMQ routes were unaffected — they don't).
  • Durable (inbox-backed) listeners batch their inbox writes. Kafka: a 2,000 msg/s stream went from unbounded backlog to a steady 32ms delivery p50, max sustained durable throughput +83%. RabbitMQ: same load went from 14.7s-and-climbing to 0.8ms delivery p50, max sustained durable throughput +186% (1,086 → 3,101 msg/s). New MaximumMessagesToReceive listener knob on both (default 100; 1 restores strict message-at-a-time persistence).
  • Kafka hot path: incoming envelope mapping ~21% faster / ~28% less allocation per message; outgoing ~17% faster; inline senders no longer issue a blocking full-producer flush per send. RabbitMQ's mapper gets the same incoming fix.
  • Batch-arrival correctness: the Envelope[] arrival path now applies the same per-envelope guards as single-message arrival (interop serializer unwrap, dead-lettering of unidentifiable messages, expiry, drain latching), and batched inbox writes route to ancillary message stores correctly.
  • SQS: SendMessageBatch per-entry failures (throttling, oversize) are now routed to Wolverine's retry machinery instead of being silently dropped — a silent message-loss fix (GH-3493).
  • Out-of-the-box RabbitMQ consumption is Inline — one message at a time, which measures identically to an equivalent single-consumer raw-client loop. The new RabbitMQ "Performance Tuning" docs page covers scaling with BufferedInMemory() / ListenerCount() with measured numbers, and a matching page exists for Kafka.

Behavior changes to note

  • The per-message "successfully processed" log now defaults to Debug (was Information) — restore with opts.Policies.MessageSuccessLogLevel(LogLevel.Information).
  • wolverine-execution-time is now a floating-point histogram and no longer silently drops sub-millisecond executions (same name/unit; the point type changes).

Transports and messaging

  • Azure Service Bus: PrefetchCount on listeners and transport defaults (GH-3471)
  • SQS: native DelaySeconds for short scheduled sends on standard queues (GH-3472)
  • Pulsar: native scheduled delivery via DeliverAt (GH-3470)
  • Kafka: KIP-848 next-generation consumer rebalance protocol support (GH-3473)
  • Standardized diagnostic headers on dead-letter and retry moves across all transports (GH-3474)
  • SignalR cascading responses are no longer dropped when the HTTP context has already flushed (GH-3499)
  • The dead-letter metric only counts genuine moves to the error queue (#​3495)
  • Global partitioning docs now cover all eight sharded transports (GH-3466)
  • Tolerate empty agent URI lists in agent command serialization (GH-3460)

HTTP / gRPC

  • Code-first gRPC client streaming: IAsyncEnumerable<TRequest> -> Task<TResponse> handler shape (#​3500)
  • Proto-first gRPC client streaming via the new StreamAsync<TRequest, TResponse> overload (#​3459)

Dependencies

JasperFx 2.30.1 (sender-batching max-age fix), Weasel 9.18.1, Marten 9.16.1.

6.20.0

Wolverine 6.20.0

Dependency upgrades (critter stack)

  • Marten 9.16.0, Polecat 5.2.0, JasperFx 2.29.0, Weasel 9.17.0 (aligned across the stack).

Multi-tenancy & connection footprint

  • Scope tenant scheduled-job polling to the owning node (GH-3376) — under Wolverine-managed distribution, nodes no longer open a scheduled-job polling connection to every tenant database; the footprint scales with databases, not nodes × databases. Plus daemon tracker-subscription leak hygiene.
  • Polecat primary-store database-per-tenant (GH-3445) — IntegrateWithWolverine() now honors a database-per-tenant Polecat store and reads MainDatabaseConnectionString.
  • DatabaseServerId sourced from DatabaseDescriptor.Port instead of re-parsing (jasperfx#​514).

CritterWatch / connection state

  • Degrade-only connection state for Azure Service Bus, GCP Pub/Sub (GH-3237) and Kafka (GH-3454) listeners — heuristics only degrade to Reconnecting/Disconnected from real SDK callbacks; they never synthesize Connected, and resting state is Unknown.

Kafka

  • Read record timestamps into SentAt and expose record headers on raw-JSON listeners (GH-3407).
  • JsonSerializerOptions on raw-JSON endpoints now actually applies; PublishRawJson mapper registration fixed.
  • Bounded the listener shutdown drain instead of awaiting forever (GH-3434).

Sagas

  • SagaConcurrencyException now inherits JasperFx.ConcurrencyException (GH-3444) — existing OnException<ConcurrencyException>() policies now catch saga concurrency failures.
  • Lightweight RDBMS saga provider treated as a catch-all so Marten/EF Core win precedence in mixed-storage hosts (GH-3443).

Other

  • Ancillary Marten store now registers the event-subscription family interface aliases (GH-3438).
  • F# code generation improvements (#​3437); TrackedSession not-tracked vs not-routed fix (#​3435).
  • Buffered circuit-breaker tests re-scoped to the transport's non-durable contract (GH-3137); durability docs corrections.

6.19.0

CosmosDB

  • Add optimistic concurrency (ETag compare-and-swap) to saga persistence (GH-3414) @​mysticmind
  • CosmosDbConfiguration.PartitionSagasById(): opt-in, saga id becomes the document partition key (GH-3415) @​mysticmind
  • Refuse a CosmosClient whose serializer would drop a saga's id at host start; document the camelCase requirement (GH-3416) @​mysticmind

HTTP / OpenAPI

  • Describe the whole route table on a pre-start ApiExplorer read; minimal API and MVC endpoints were silently omitted (GH-3421) @​mysticmind
  • Type a Marten/Polecat aggregate-id route parameter as uuid instead of falling back to string (GH-3420) @​mysticmind

Durability / persistence

  • Dead letter recovered envelopes whose transport can't be resolved, instead of losing the rest of the batch and rethrowing forever (GH-3413) @​mysticmind
  • Measure and surface per-server database connection budgets: OTel gauges + IWolverineObserver.ConnectionBudget (GH-3397) @​jeremydmiller

Test infrastructure only

  • Stop IntegrationContext from disposing a class fixture it doesn't own; pin ApplicationAssembly in the CoreTests harness (GH-3423) @​jeremydmiller
  • Give the modular monolith fixture its own message storage schema (GH-3413) @​mysticmind
  • Stop using_dynamic_multi_tenancy from poisoning its own next run @​mysticmind

Milestone: https://github.com/JasperFx/wolverine/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A6.19.0
Full Changelog: JasperFx/wolverine@V6.18.0...V6.19.0

6.18.0

Wolverine 6.18.0

A security-relevant serialization fix, a startup-fatal codegen fix, a silently-dead-listener fix in RabbitMQ, the first F# saga codegen support of any persistence provider, and the CI split that makes "merge when green" mean something again.

If you use MassTransit interop over a durable listener, take this release. See the first section.

⚠️ Security-relevant: reserved envelope headers could be spoofed through the durable inbox

#​3408fixed in #​3411

EnvelopeSerializer wrote the typed envelope properties to the wire format and then appended every Envelope.Headers entry verbatim, with no reserved-key filter — and the appended entries came last. Because the reader parses reserved keys straight back into typed properties, a Headers entry under a reserved key silently overwrote the real property on the next read.

A value in envelope.Headers["tenant-id"] is inert while the envelope is in memory. It stops being inert the moment the envelope crosses the serializer — any durable listener, the inbox/outbox, or the scheduled-message store:

  1. Something puts tenant-id into envelope.Headers.
  2. The durable inbox persists the envelope; the header is appended after the (null) typed property.
  3. On read back, env.TenantId is set from it.

saga-id reaches another saga's state, and id rewrites Envelope.Id — the inbox's dedupe identity.

This was live, not theoretical. MassTransitEnvelope.TransferData already copies every incoming MassTransit header into envelope.Headers unfiltered (and by assignment, not TryAdd). Any Wolverine app doing MassTransit interop over a durable listener has had this path open. If that describes you, this release is the one to take.

The fix filters reserved keys on the write side, so the typed property stays authoritative and a reserved key sitting in Headers becomes a no-op. causation-id is deliberately not filtered — DeliveryOptions intentionally carries it as a loose header for Wolverine.Marten's OutboxedSessionFactory, and it is never promoted by the reader.

Startup-fatal codegen fix

#​3399fixed in #​3406 — invalid generated class name for batched (array) message types. This one prevents the application from starting.

Fixes

  • #​3388 (#​3400) — refuse a competing Marten daemon under Wolverine-managed event subscription distribution. A DaemonMode.Solo/HotCold daemon alongside managed distribution is now an actionable startup exception instead of two schedulers quietly fighting over the same shards.
  • CritterWatch #​698 (#​3396) — IAgentRuntime.ApplyRestrictionsAsync persisted the restriction and then never dispatched the commands it computed, so pausing an agent had no immediate effect. Reported by @​erdtsieck against a live cluster.
  • #​3385 (#​3403) — a header-identified saga invoked over a gRPC hop failed with an opaque Internal status. It now returns an actionable diagnostic telling you to put the saga identity on the request DTO.
  • #​3398 (#​3404) — [AsParameters] now rejects unparseable values in collection query parameters, closing the gap left by the scalar fix in #​3372.
  • #​3365 (#​3412) — the Polecat primary IEventStore bridge registered twice, so GetServices<IEventStore>() returned the same store instance two times and anything iterating it double-counted. Polecat's own AddPolecat() had started registering IEventStore and Wolverine was still bridging it as well.
  • #​3391 (#​3419) — RabbitMQ: a successful eager channel restart never re-consumed. A callback-exception restart could leave an open channel with zero consumers while reporting State = Connected — a silently dead listener. The listener now defers to ReconnectedAsync(), which re-declares and re-consumes. Also pins the ConnectionMonitor tracking invariant that #​3370 fixed but nothing guarded.

OpenAPI

#​3380 (#​3418) — OpenAPI parameters are now derived from the full binding chain rather than the handler signature alone. Two real defects closed:

  • Query/header values bound only by an After/Finally postprocessor were omitted from the operation entirely.
  • Route parameter types were read off resolved binding variables, so they degraded to the route constraint (or string) whenever the description was assembled before those frames resolved — which is exactly the build-time OpenAPI / openapi CLI path, because ASP.NET caches the first ApiExplorer read.

More importantly, this ships the OpenAPI shape-test harness that was missing. Adding a shape assertion is now one endpoint plus one [Fact], which is why this class of omission kept shipping unnoticed.

New: Azure Service Bus emulator support

#​3366 (#​3409) — the docs told you to call UseAzureServiceBusTesting(), which only ever existed in Wolverine's own test suite. It is now a real, shipping API:

... (truncated)

Commits viewable in compare view.

Updated WolverineFx.Postgresql from 6.17.3 to 6.21.0.

Release notes

Sourced from WolverineFx.Postgresql's releases.

6.21.0

Wolverine 6.21.0 is a big one: conjoined multi-tenancy for EF Core, and a measured messaging-performance wave across Kafka and RabbitMQ.

Conjoined multi-tenancy for EF Core (#​3465)

Mark an EF Core entity with ITenanted (the marker shared critter-stack-wide from JasperFx.MultiTenancy) and register your DbContext with AddDbContextWithWolverineManagedConjoinedTenancy<T>(), and Wolverine gives you what Marten users have had for years: a mapped tenant_id column, a tenant-bound global query filter you can't forget, stamp-on-insert, CrossTenantWriteException on cross-tenant writes, conjoined sagas, opt-in Weasel-managed physical tenant partitioning (PostgreSQL list partitions + SQL Server tenant-ordinal), and an authoritative wolverine_tenants registry that doubles as a dynamic tenant source and feeds CritterWatch tenant management. The behavior is checked against a port of Marten's conjoined-tenancy compliance battery. See the new ConjoinedMultiTenantedEfCore sample app and docs.

Messaging performance (GH-3490 / GH-3492)

A client-reported "Wolverine-over-Kafka is 3-12x slower than native" investigation turned into a measured optimization wave (methodology, rigs, and full ledgers are in the repo):

  • Sender batching now flushes within the batch timeout (JasperFx 2.30.1). The batch timer was a quiet-period debounce — a steady 8 msg/s stream at the default (100, 250ms) settings measured 5.8 seconds publish-to-consume p50; it now measures 136ms, bounded by the timeout. Affects every transport that sends through the batched sender (RabbitMQ routes were unaffected — they don't).
  • Durable (inbox-backed) listeners batch their inbox writes. Kafka: a 2,000 msg/s stream went from unbounded backlog to a steady 32ms delivery p50, max sustained durable throughput +83%. RabbitMQ: same load went from 14.7s-and-climbing to 0.8ms delivery p50, max sustained durable throughput +186% (1,086 → 3,101 msg/s). New MaximumMessagesToReceive listener knob on both (default 100; 1 restores strict message-at-a-time persistence).
  • Kafka hot path: incoming envelope mapping ~21% faster / ~28% less allocation per message; outgoing ~17% faster; inline senders no longer issue a blocking full-producer flush per send. RabbitMQ's mapper gets the same incoming fix.
  • Batch-arrival correctness: the Envelope[] arrival path now applies the same per-envelope guards as single-message arrival (interop serializer unwrap, dead-lettering of unidentifiable messages, expiry, drain latching), and batched inbox writes route to ancillary message stores correctly.
  • SQS: SendMessageBatch per-entry failures (throttling, oversize) are now routed to Wolverine's retry machinery instead of being silently dropped — a silent message-loss fix (GH-3493).
  • Out-of-the-box RabbitMQ consumption is Inline — one message at a time, which measures identically to an equivalent single-consumer raw-client loop. The new RabbitMQ "Performance Tuning" docs page covers scaling with BufferedInMemory() / ListenerCount() with measured numbers, and a matching page exists for Kafka.

Behavior changes to note

  • The per-message "successfully processed" log now defaults to Debug (was Information) — restore with opts.Policies.MessageSuccessLogLevel(LogLevel.Information).
  • wolverine-execution-time is now a floating-point histogram and no longer silently drops sub-millisecond executions (same name/unit; the point type changes).

Transports and messaging

  • Azure Service Bus: PrefetchCount on listeners and transport defaults (GH-3471)
  • SQS: native DelaySeconds for short scheduled sends on standard queues (GH-3472)
  • Pulsar: native scheduled delivery via DeliverAt (GH-3470)
  • Kafka: KIP-848 next-generation consumer rebalance protocol support (GH-3473)
  • Standardized diagnostic headers on dead-letter and retry moves across all transports (GH-3474)
  • SignalR cascading responses are no longer dropped when the HTTP context has already flushed (GH-3499)
  • The dead-letter metric only counts genuine moves to the error queue (#​3495)
  • Global partitioning docs now cover all eight sharded transports (GH-3466)
  • Tolerate empty agent URI lists in agent command serialization (GH-3460)

HTTP / gRPC

  • Code-first gRPC client streaming: IAsyncEnumerable<TRequest> -> Task<TResponse> handler shape (#​3500)
  • Proto-first gRPC client streaming via the new StreamAsync<TRequest, TResponse> overload (#​3459)

Dependencies

JasperFx 2.30.1 (sender-batching max-age fix), Weasel 9.18.1, Marten 9.16.1.

6.20.0

Wolverine 6.20.0

Dependency upgrades (critter stack)

  • Marten 9.16.0, Polecat 5.2.0, JasperFx 2.29.0, Weasel 9.17.0 (aligned across the stack).

Multi-tenancy & connection footprint

  • Scope tenant scheduled-job polling to the owning node (GH-3376) — under Wolverine-managed distribution, nodes no longer open a scheduled-job polling connection to every tenant database; the footprint scales with databases, not nodes × databases. Plus daemon tracker-subscription leak hygiene.
  • Polecat primary-store database-per-tenant (GH-3445) — IntegrateWithWolverine() now honors a database-per-tenant Polecat store and reads MainDatabaseConnectionString.
  • DatabaseServerId sourced from DatabaseDescriptor.Port instead of re-parsing (jasperfx#​514).

CritterWatch / connection state

  • Degrade-only connection state for Azure Service Bus, GCP Pub/Sub (GH-3237) and Kafka (GH-3454) listeners — heuristics only degrade to Reconnecting/Disconnected from real SDK callbacks; they never synthesize Connected, and resting state is Unknown.

Kafka

  • Read record timestamps into SentAt and expose record headers on raw-JSON listeners (GH-3407).
  • JsonSerializerOptions on raw-JSON endpoints now actually applies; PublishRawJson mapper registration fixed.
  • Bounded the listener shutdown drain instead of awaiting forever (GH-3434).

Sagas

  • SagaConcurrencyException now inherits JasperFx.ConcurrencyException (GH-3444) — existing OnException<ConcurrencyException>() policies now catch saga concurrency failures.
  • Lightweight RDBMS saga provider treated as a catch-all so Marten/EF Core win precedence in mixed-storage hosts (GH-3443).

Other

  • Ancillary Marten store now registers the event-subscription family interface aliases (GH-3438).
  • F# code generation improvements (#​3437); TrackedSession not-tracked vs not-routed fix (#​3435).
  • Buffered circuit-breaker tests re-scoped to the transport's non-durable contract (GH-3137); durability docs corrections.

6.19.0

CosmosDB

  • Add optimistic concurrency (ETag compare-and-swap) to saga persistence (GH-3414) @​mysticmind
  • CosmosDbConfiguration.PartitionSagasById(): opt-in, saga id becomes the document partition key (GH-3415) @​mysticmind
  • Refuse a CosmosClient whose serializer would drop a saga's id at host start; document the camelCase requirement (GH-3416) @​mysticmind

HTTP / OpenAPI

  • Describe the whole route table on a pre-start ApiExplorer read; minimal API and MVC endpoints were silently omitted (GH-3421) @​mysticmind
  • Type a Marten/Polecat aggregate-id route parameter as uuid instead of falling back to string (GH-3420) @​mysticmind

Durability / persistence

  • Dead letter recovered envelopes whose transport can't be resolved, instead of losing the rest of the batch and rethrowing forever (GH-3413) @​mysticmind
  • Measure and surface per-server database connection budgets: OTel gauges + IWolverineObserver.ConnectionBudget (GH-3397) @​jeremydmiller

Test infrastructure only

  • Stop IntegrationContext from disposing a class fixture it doesn't own; pin ApplicationAssembly in the CoreTests harness (GH-3423) @​jeremydmiller
  • Give the modular monolith fixture its own message storage schema (GH-3413) @​mysticmind
  • Stop using_dynamic_multi_tenancy from poisoning its own next run @​mysticmind

Milestone: https://github.com/JasperFx/wolverine/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A6.19.0
Full Changelog: JasperFx/wolverine@V6.18.0...V6.19.0

6.18.0

Wolverine 6.18.0

A security-relevant serialization fix, a startup-fatal codegen fix, a silently-dead-listener fix in RabbitMQ, the first F# saga codegen support of any persistence provider, and the CI split that makes "merge when green" mean something again.

If you use MassTransit interop over a durable listener, take this release. See the first section.

⚠️ Security-relevant: reserved envelope headers could be spoofed through the durable inbox

#​3408fixed in #​3411

EnvelopeSerializer wrote the typed envelope properties to the wire format and then appended every Envelope.Headers entry verbatim, with no reserved-key filter — and the appended entries came last. Because the reader parses reserved keys straight back into typed properties, a Headers entry under a reserved key silently overwrote the real property on the next read.

A value in envelope.Headers["tenant-id"] is inert while the envelope is in memory. It stops being inert the moment the envelope crosses the serializer — any durable listener, the inbox/outbox, or the scheduled-message store:

  1. Something puts tenant-id into envelope.Headers.
  2. The durable inbox persists the envelope; the header is appended after the (null) typed property.
  3. On read back, env.TenantId is set from it.

saga-id reaches another saga's state, and id rewrites Envelope.Id — the inbox's dedupe identity.

This was live, not theoretical. MassTransitEnvelope.TransferData already copies every incoming MassTransit header into envelope.Headers unfiltered (and by assignment, not TryAdd). Any Wolverine app doing MassTransit interop over a durable listener has had this path open. If that describes you, this release is the one to take.

The fix filters reserved keys on the write side, so the typed property stays authoritative and a reserved key sitting in Headers becomes a no-op. causation-id is deliberately not filtered — DeliveryOptions intentionally carries it as a loose header for Wolverine.Marten's OutboxedSessionFactory, and it is never promoted by the reader.

Startup-fatal codegen fix

#​3399fixed in #​3406 — invalid generated class name for batched (array) message types. This one prevents the application from starting.

Fixes

  • #​3388 (#​3400) — refuse a competing Marten daemon under Wolverine-managed event subscription distribution. A DaemonMode.Solo/HotCold daemon alongside managed distribution is now an actionable startup exception instead of two schedulers quietly fighting over the same shards.
  • CritterWatch #​698 (#​3396) — IAgentRuntime.ApplyRestrictionsAsync persisted the restriction and then never dispatched the commands it computed, so pausing an agent had no immediate effect. Reported by @​erdtsieck against a live cluster.
  • #​3385 (#​3403) — a header-identified saga invoked over a gRPC hop failed with an opaque Internal status. It now returns an actionable diagnostic telling you to put the saga identity on the request DTO.
  • #​3398 (#​3404) — [AsParameters] now rejects unparseable values in collection query parameters, closing the gap left by the scalar fix in #​3372.
  • #​3365 (#​3412) — the Polecat primary IEventStore bridge registered twice, so GetServices<IEventStore>() returned the same store instance two times and anything iterating it double-counted. Polecat's own AddPolecat() had started registering IEventStore and Wolverine was still bridging it as well.
  • #​3391 (#​3419) — RabbitMQ: a successful eager channel restart never re-consumed. A callback-exception restart could leave an open channel with zero consumers while reporting State = Connected — a silently dead listener. The listener now defers to ReconnectedAsync(), which re-declares and re-consumes. Also pins the ConnectionMonitor tracking invariant that #​3370 fixed but nothing guarded.

OpenAPI

#​3380 (#​3418) — OpenAPI parameters are now derived from the full binding chain rather than the handler signature alone. Two real defects closed:

  • Query/header values bound only by an After/Finally postprocessor were omitted from the operation entirely.
  • Route parameter types were read off resolved binding variables, so they degraded to the route constraint (or string) whenever the description was assembled before those frames resolved — which is exactly the build-time OpenAPI / openapi CLI path, because ASP.NET caches the first ApiExplorer read.

More importantly, this ships the OpenAPI shape-test harness that was missing. Adding a shape assertion is now one endpoint plus one [Fact], which is why this class of omission kept shipping unnoticed.

New: Azure Service Bus emulator support

#​3366 (#​3409) — the docs told you to call UseAzureServiceBusTesting(), which only ever existed in Wolverine's own test suite. It is now a real, shipping API:

... (truncated)

Commits viewable in compare view.

Pinned WolverineFx.RabbitMQ at 6.21.0.

Release notes

Sourced from WolverineFx.RabbitMQ's releases.

6.21.0

Wolverine 6.21.0 is a big one: conjoined multi-tenancy for EF Core, and a measured messaging-performance wave across Kafka and RabbitMQ.

Conjoined multi-tenancy for EF Core (#​3465)

Mark an EF Core entity with ITenanted (the marker shared critter-stack-wide from JasperFx.MultiTenancy) and register your DbContext with AddDbContextWithWolverineManagedConjoinedTenancy<T>(), and Wolverine gives you what Marten users have had for years: a mapped tenant_id column, a tenant-bound global query filter you can't forget, stamp-on-insert, CrossTenantWriteException on cross-tenant writes, conjoined sagas, opt-in Weasel-managed physical tenant partitioning (PostgreSQL list partitions + SQL Server tenant-ordinal), and an authoritative wolverine_tenants registry that doubles as a dynamic tenant source and feeds CritterWatch tenant management. The behavior is checked against a port of Marten's conjoined-tenancy compliance battery. See the new ConjoinedMultiTenantedEfCore sample app and docs.

Messaging performance (GH-3490 / GH-3492)

A client-reported "Wolverine-over-Kafka is 3-12x slower than native" investigation turned into a measured optimization wave (methodology, rigs, and full ledgers are in the repo):

  • Sender batching now flushes within the batch timeout (JasperFx 2.30.1). The batch timer was a quiet-period debounce — a steady 8 msg/s stream at the default (100, 250ms) settings measured 5.8 seconds publish-to-consume p50; it now measures 136ms, bounded by the timeout. Affects every transport that sends through the batched sender (RabbitMQ routes were unaffected — they don't).
  • Durable (inbox-backed) listeners batch their inbox writes. Kafka: a 2,000 msg/s stream went from unbounded backlog to a steady 32ms delivery p50, max sustained durable throughput +83%. RabbitMQ: same load went from 14.7s-and-climbing to 0.8ms delivery p50, max sustained durable throughput +186% (1,086 → 3,101 msg/s). New MaximumMessagesToReceive listener knob on both (default 100; 1 restores strict message-at-a-time persistence).
  • Kafka hot path: incoming envelope mapping ~21% faster / ~28% less allocation per message; outgoing ~17% faster; inline senders no longer issue a blocking full-producer flush per send. RabbitMQ's mapper gets the same incoming fix.
  • Batch-arrival correctness: the Envelope[] arrival path now applies the same per-envelope guards as single-message arrival (interop serializer unwrap, dead-lettering of unidentifiable messages, expiry, drain latching), and batched inbox writes route to ancillary message stores correctly.
  • SQS: SendMessageBatch per-entry failures (throttling, oversize) are now routed to Wolverine's retry machinery instead of being silently dropped — a silent message-loss fix (GH-3493).
  • Out-of-the-box RabbitMQ consumption is Inline — one message at a time, which measures identically to an equivalent single-consumer raw-client loop. The new RabbitMQ "Performance Tuning" docs page covers scaling with BufferedInMemory() / ListenerCount() with measured numbers, and a matching page exists for Kafka.

Behavior changes to note

  • The per-message "successfully processed" log now defaults to Debug (was Information) — restore with opts.Policies.MessageSuccessLogLevel(LogLevel.Information).
  • wolverine-execution-time is now a floating-point histogram and no longer silently drops sub-millisecond executions (same name/unit; the point type changes).

Transports and messaging

  • Azure Service Bus: PrefetchCount on listeners and transport defaults (GH-3471)
  • SQS: native DelaySeconds for short scheduled sends on standard queues (GH-3472)
  • Pulsar: native scheduled delivery via DeliverAt (GH-3470)
  • Kafka: KIP-848 next-generation consumer rebalance protocol support (GH-3473)
  • Standardized diagnostic headers on dead-letter and retry moves across all transports (GH-3474)
  • SignalR cascading responses are no longer dropped when the HTTP context has already flushed (GH-3499)
  • The dead-letter metric only counts genuine moves to the error queue (#​3495)
  • Global partitioning docs now cover all eight sharded transports (GH-3466)
  • Tolerate empty agent URI lists in agent command serialization (GH-3460)

HTTP / gRPC

  • Code-first gRPC client streaming: IAsyncEnumerable<TRequest> -> Task<TResponse> handler shape (#​3500)
  • Proto-first gRPC client streaming via the new StreamAsync<TRequest, TResponse> overload (#​3459)

Dependencies

JasperFx 2.30.1 (sender-batching max-age fix), Weasel 9.18.1, Marten 9.16.1.

6.20.0

Wolverine 6.20.0

Dependency upgrades (critter stack)

  • Marten 9.16.0, Polecat 5.2.0, JasperFx 2.29.0, Weasel 9.17.0 (aligned across the stack).

Multi-tenancy & connection footprint

  • Scope tenant scheduled-job polling to the owning node (GH-3376) — under Wolverine-managed distribution, nodes no longer open a scheduled-job polling connection to every tenant database; the footprint scales with databases, not nodes × databases. Plus daemon tracker-subscri...

Description has been truncated

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file nuget Pull requests that update NuGet packages labels Jul 20, 2026
@dependabot
dependabot Bot requested a review from emeraldleaf as a code owner July 20, 2026 03:35
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file nuget Pull requests that update NuGet packages labels Jul 20, 2026
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@emeraldleaf

Copy link
Copy Markdown
Owner

@dependabot rebase

Bumps WolverineFx from 6.17.3 to 6.21.0
Bumps WolverineFx.EntityFrameworkCore from 6.17.3 to 6.21.0
Bumps WolverineFx.FluentValidation from 6.17.3 to 6.21.0
Bumps WolverineFx.Postgresql from 6.17.3 to 6.21.0
Bumps WolverineFx.RabbitMQ from 6.17.3 to 6.21.0
Bumps WolverineFx.RuntimeCompilation from 6.17.3 to 6.21.0
Bumps WolverineFx.SqlServer from 6.17.3 to 6.21.0

---
updated-dependencies:
- dependency-name: WolverineFx
  dependency-version: 6.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wolverine
- dependency-name: WolverineFx.EntityFrameworkCore
  dependency-version: 6.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wolverine
- dependency-name: WolverineFx.FluentValidation
  dependency-version: 6.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wolverine
- dependency-name: WolverineFx.Postgresql
  dependency-version: 6.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wolverine
- dependency-name: WolverineFx.RabbitMQ
  dependency-version: 6.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wolverine
- dependency-name: WolverineFx.RuntimeCompilation
  dependency-version: 6.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wolverine
- dependency-name: WolverineFx.SqlServer
  dependency-version: 6.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: wolverine
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot changed the title Bump the wolverine group with 7 updates Bump WolverineFx and 6 others Jul 20, 2026
@dependabot
dependabot Bot force-pushed the dependabot/nuget/wolverine-59db9e4210 branch from 6b5d713 to 848bc56 Compare July 20, 2026 03:54
@emeraldleaf
emeraldleaf merged commit d6a98b0 into main Jul 20, 2026
7 checks passed
@dependabot
dependabot Bot deleted the dependabot/nuget/wolverine-59db9e4210 branch July 20, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file nuget Pull requests that update NuGet packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant