Skip to content

JasperFx 2.97.0 - #5299

Merged
jeremydmiller merged 1 commit into
mainfrom
bump-jasperfx-2.97.0
Oct 30, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
bump-jasperfx-2.97.0

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Takes JasperFx 2.97.0, which carries the BatchingChannel.TriggerBatch fix (JasperFx/jasperfx#1095) and IReadOnlyEventStore.QueryRawEvents (JasperFx/jasperfx#1094). The SQS, RabbitMQ, Pulsar and NATS transports call TriggerBatch, and its existing behaviour is unchanged. No code change here. The solution compiles locally; CI runs the suites.

🤖 Generated with Claude Code

Carries the BatchingChannel.TriggerBatch fix (#1095), which the
SQS, RabbitMQ, Pulsar and NATS transports call, and
IReadOnlyEventStore.QueryRawEvents (#1094).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit 300c5e9 into main Oct 30, 2026
48 checks passed
This was referenced Nov 1, 2026
github-actions Bot pushed a commit to MrD4rkne/oweme-ecosystem that referenced this pull request Nov 6, 2026
Updated [Scalar.AspNetCore](https://github.com/scalar/scalar) from
2.18.3 to 2.18.7.

<details>
<summary>Release notes</summary>

_Sourced from [Scalar.AspNetCore's
releases](https://github.com/scalar/scalar/releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/scalar/scalar/commits).
</details>

Updated [WolverineFx](http://github.com/jasperfx/wolverine) from 6.56.0
to 6.61.0.

<details>
<summary>Release notes</summary>

_Sourced from [WolverineFx's
releases](http://github.com/jasperfx/wolverine/releases)._

## 6.61.0

This is a minor release. Upgrading is a package bump only: there is no
schema change and no breaking API change.

## New

- **GH-5320** (#​5326): Recurring schedules can now run as often as
every second, and a schedule can skip an occurrence while the previous
one is still running.
- `CronSchedule.MinimumCadence` is now one second, for six-field cron
expressions such as `* * * * * *`. Anything faster than five seconds
always runs on a timer agent, and `UseScheduledMessages()` refuses it.
- The new `SkipWhileRunning()` on a recurring schedule invokes each
occurrence instead of publishing it. An occurrence that comes due while
the previous one is still running is skipped, and skipped occurrences
are not made up later. Skips are logged and counted in the new
`wolverine-recurring-occurrences-skipped` metric.
- With a local handler, the occurrence runs on the node that owns the
schedule's timer agent, so overlap is prevented across the cluster.
Because the occurrence is invoked, other subscribers don't receive it,
and it has no inbox row, queue error policy or dead letter queue. A
failed occurrence is logged and the next one runs on schedule. With a
handler on another node, the wait is capped at 5 seconds.
  - A one-second schedule writes about 86,400 deduplication rows a day.
- **GH-5317** (#​5323):
`PauseThenCatchUpOnAllMartenDaemonActivityAsync()` and the
tracked-session option `PauseThenCatchUpOnAllMartenDaemonActivity()`
catch up the main Marten store and every ancillary store registered with
`IntegrateWithWolverine()` in one call. A store with no events is
skipped.
- **GH-5311** (#​5324): A failed handler attempt now records the
OpenTelemetry `exception` event (`exception.type`, `exception.message`,
`exception.stacktrace`) on its span, instead of only the exception's
type name. Exception messages can carry application data, so you can
turn this off with `opts.Tracking.RecordExceptionsOnSpans = false`.
- **GH-5339** (#​5341): `EntityDescriptor.Ddl` now carries each EF Core
entity's `CREATE TABLE` script, with its keys, foreign keys and indexes,
the same way `DocumentMappingDescriptor.Ddl` does for documents. It's
generated from the model, so no connection is opened, and it's the
model's script rather than the database's. Non-relational providers and
view-mapped entities leave it empty. Needs JasperFx 2.99.0.
- **GH-5332** (#​5338): Wolverine HTTP endpoints now tag the request
span with `http.route` and `http.request.method`, the OpenTelemetry
semantic convention names, alongside `handler.type`. Both are constants
in the generated code. Pre-generated code should be regenerated.
- Dead letter queries can filter their time range on the failure time
with the new `DeadLetterEnvelopeQuery.RangeOn`
(`DeadLetterTimeField.SentAt` or `FailedAt`) (#​5330). `FailedAt` falls
back to `sent_at` when `failed_at` is null, and applies to query,
replay, discard and archive. The default stays `SentAt`. Supported by
the PostgreSQL, SQL Server, MySQL, SQLite, Oracle, CosmosDb and RavenDb
stores.

## Fixes

- **GH-5310** (#​5328): With Marten's `TrackConnections` turned on, the
handler, saga and stream tags (`handler.type`, `message.handler`,
`wolverine.saga.*`, `wolverine.stream.*`) landed on the
`marten.connection` span instead of the message's own span. They now
stay on the message span, and on the request span for HTTP endpoints.
Pre-generated code should be regenerated.
- **GH-5312** (#​5327): A durable `ScheduleRetry(...)` lost the link to
the attempt that failed, because the stored envelope body wasn't
rewritten when the retry was scheduled. Every message store now rewrites
the body when it reschedules an envelope.
- **GH-5313** (#​5321): Azure Service Bus queues and subscriptions and
Amazon SQS queues now report `DeadLetterStorageMode.NativeWithRecovery`
when `EnableDeadLetterQueueRecovery()` drains them, so CritterWatch's
startup check no longer warns about them.
- **GH-5314** (#​5325): With RabbitMQ's
`EnableDeadLetterQueueRecovery()`, stopping the host could throw
`AlreadyClosedException` when two stops ran at once, as they do under
`WebApplicationFactory`. Stopping the dead letter queue listener is now
idempotent and never throws.
- **GH-5315** (#​5322): `FindSingleTrackedMessageOfType<T>()` now counts
envelopes rather than distinct message values, so publishing two equal
records fails the check. A message sent to several destinations still
counts once. Use `tracked.Sent.SingleMessage<T>()` for a check per
destination.
- **GH-5318** (#​5319): The testing docs now explain that the pause in
`PauseThenCatchUpOnMartenDaemonActivity` waits out the high water
agent's current poll tick. The fix is JasperFx/jasperfx#​1100.
- **GH-5331** (#​5336): With runtime code generation, each saga message
disappeared from `ServiceCapabilities.Sagas` once it had been handled,
and the saga disappeared once every message had been. The saga report,
including each store's `GetRegisteredSagasAsync`, now reads the same
before and after the chains compile.
- **GH-5334** (#​5337): In `Balanced` mode, lifting a pause on a URI
that no agent owns, such as a listening endpoint, waited out
`AgentRestrictionConfirmationTimeout` and then threw
`AgentRestrictionsNotConfirmedException`. It now returns at once. Agent
URIs are still confirmed as before.
- Sessions the application opens itself and enrolls with
`IPolecatOutbox.Enroll` or `IFisherOutbox.Enroll` never flushed their
outgoing messages on commit (#​5309). Durable messages waited until a
restart and buffered messages were lost. They now flush on commit, the
same as handler sessions, and the incoming envelope is marked handled
once instead of twice.

## Dependencies

- JasperFx 2.99.0 (#​5333 took 2.98.0, #​5341 2.99.0).
- Weasel 9.48.0 and MySqlConnector 2.6.1 (#​5335). MySqlConnector 2.6.1
closes GHSA-473q-m89c-ghf8 for `WolverineFx.MySql`. On SQL Server, a
`DateTime` parameter in a message store command now goes out as
`datetime2` instead of `datetime`. Every envelope time column is
`datetimeoffset`, so no column is converted either way.

Thanks to @​uniquelau for #​5313, #​5314, #​5315, #​5317 and #​5318, and
to @​lahma for #​5330.

**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.60.0...V6.61.0


## 6.60.0

## What's Changed
* JasperFx 2.97.0 by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5299
* GH-5296 Query saga instances by saga type: filters, ordering, paging
and counts by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5301
* GH-5297 IDocumentStoreDiagnostics for EF Core DbContexts by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5302
* GH-5295 Explicit IDbContextOutbox<T> binds the ancillary store by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5303
* GH-5298 Lightweight saga storage honours the AddSagaType table name by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5304
* GH-5300 CosmosDB saga storage: ISagaStoreDiagnostics read and
QuerySagasAsync by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5305


**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.59.1...V6.60.0

## 6.59.1

This is a patch release with two fixes. Upgrading is a package bump
only: there is no schema change and no API change.

## SQL Server: the inbox and outbox seek on their indexes again

- **GH-5292** (#​5294): Several of the SQL Server message store's
per-envelope statements were scanning the whole envelope table. That
cost grows with the size of the inbox and outbox, and those statements
run once per message. The cause was how two kinds of value were bound as
parameters.
- **Envelope ids went out as `sql_variant`.** The statements that mark
envelopes as handled, reassign them, delete them and move them to the
dead letter queue all filter on the envelope id. Weasel's generic
command builder has no mapping for `Guid`, so it bound each id as
`DbType.Object`, which SqlClient sends as `sql_variant`. SQL Server then
had to compare `CONVERT_IMPLICIT(sql_variant, [id])` on every row, which
rules out a seek on the `uniqueidentifier` primary key. Ids are now
bound as `uniqueidentifier`, so each statement is a primary key seek.
- **Envelope addresses went out as `nvarchar`.** `received_at` on the
inbox and `destination` on the outbox are `varchar(250)` columns, but
statements that filter on them bound the address as `nvarchar`. Under a
SQL collation such as `SQL_Latin1_General_CP1_CI_AS`, the default on
Azure SQL, that converts the column and defeats the index. The
statements affected are the per-envelope updates and deletes, inbox
recovery's page load, outbox loads by destination, the scheduled message
updates and the duplicate check. They now bind the address as `varchar`.
- **`DateTime` parameters kept only the date.** The same generic mapping
typed `DateTime` as `DbType.Date`. The message store now lets SqlClient
type both values itself.
- The new test checks SQL Server's plan cache after running each of
these statements. It requires that no plan contains `sql_variant` and
that no plan converts an indexed envelope column. The full
`SqlServerTests` suite passes (721 tests, 2 skipped).
- PostgreSQL, MySQL, SQLite and Oracle bind exactly as they did before.
- The underlying Weasel mappings are tracked as JasperFx/weasel#​758
(`Guid` as `sql_variant`) and JasperFx/weasel#​759 (`DateTime` as
`date`). Wolverine works around both on its own, so this release doesn't
wait for a Weasel release.

## The `run` command no longer blocks a thread pool thread

- **JasperFx 2.96.2** (JasperFx/jasperfx#​1093, fixes
JasperFx/jasperfx#​1092): Wolverine now depends on JasperFx 2.96.2.
Before this release, the `run` command waited for shutdown by blocking a
thread pool thread for the whole life of the application. That matters
when a container's CPU limit is below one core. `ProcessorCount` is then
1, so the thread pool's minimum is one thread, and the `run` command
held it. After that, every burst of work had to wait for the pool to add
a thread, about 500 ms each time. The reporter saw a 200 ms `Task.Delay`
loop wake up as much as about 950 ms late on Azure Container Apps.
- The `run` command now awaits shutdown instead of blocking. It still
listens for the same three signals: `ApplicationStopping`, Ctrl+C, and
the assembly load context unloading.
- When the host is already started (the `AutoStartHost` /
`WebApplicationFactory` path), `RunJasperFxCommands()` used to block the
calling thread. It now returns a task that completes when the host shuts
down.
- This applies to every application started with `RunJasperFxCommands()`
or `RunWolverineCommands()`, whatever its persistence or transport.

**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.59.0...V6.59.1


## 6.59.0

## New

- **GH-5288** (#​5289): Scheduled message management now knows about
sagas. `ScheduledMessageSummary.SagaId` reports the saga id carried by
each scheduled envelope. The new `ScheduledMessageQuery.SagaId` limits
`QueryAsync` and `CancelAsync` to one saga's scheduled messages, such as
its pending timeouts.
- There is no schema change. The filter reads every scheduled message
that matches the rest of the query and pages the saga's messages in
memory, so `PageSize` and `TotalCount` stay exact. On a large scheduled
backlog, narrow the query with `MessageType` or an execution-time window
as well.
- A `CancelAsync` with a `SagaId` that matches nothing cancels nothing.
- Supported by PostgreSQL, SQL Server, MySQL, SQLite, Oracle, RavenDB
and CosmosDB. RavenDB and CosmosDB now treat `PageSize <= 0` as "read
every match", as the RDBMS stores already did.
- **#​5287**: `WolverineFx.AI`'s `AddLlmCallouts()` now works under
`TypeLoadMode.Static` when the application's pre-generated code was
written without it. A library can ship its own static codegen and a host
can add LLM callouts on top. In Static mode, callouts run on a pre-built
handler that needs no generated code. Every other mode is unchanged.
- New `LlmCalloutOptions.ConfigureChain(Action<HandlerChain>)`
customizes the callout chain in any mode. `IHandlerPolicy` doesn't reach
the pre-built handler.
- `DeduplicateCallouts` is refused at startup in Static mode, because
deduplication is woven into generated code.

## Fixes

- **GH-5286** (#​5290): A send that `IHost.StopAsync()` cancels
mid-flight is no longer logged as a send failure. Azure Service Bus
hands the runtime's cancellation token to the SDK, so an orderly
shutdown used to produce two Error lines for a batch that was never
lost. A durable agent now logs it at Information, because the envelopes
stay in the outbox and are recovered on restart. A non-durable agent
logs it at Warning, because those messages were not sent. A shutdown
cancellation no longer counts toward the circuit breaker or the sending
failure policies. A cancellation while the application is still running,
or any other exception during shutdown, is still logged at Error.


## 6.58.0

## New

- **GH-5279** (#​5280, #​5282): New opt-in
`Durability.UseTransactionScopedDurabilityPassLock` for PostgreSQL. The
serverless durability pass takes a transaction-scoped advisory lock
(`pg_try_advisory_xact_lock`) instead of a session lock. This makes the
pass lock safe behind a transaction-mode pooler such as PgBouncer or
Supabase's pooler, where a session lock can leak onto a pooled
connection or be held by two passes at once. #​5282 hardens it:
  - the lock connection stays out of an ambient `TransactionScope`
- an `idle_in_transaction_session_timeout` can no longer drop the lock
in the middle of a pass
- a lock transaction that ends before the pass does is reported, and its
pooled connection is discarded
  - a session unlock that fails is logged as a warning

Wolverine also logs a one-time warning when the store doesn't support
the setting, or when extension steps such as Marten's projection
catch-up run under the open lock transaction. That open transaction can
stall Marten's gap detection, which is tracked in JasperFx/marten#​5791.
See the serverless guide.

## Fixes

- **GH-5276** (#​5277, thanks @​3nth): The HTTP transport's batch
endpoint now answers 503 when a durable local queue cannot write the
batch to its inbox. Before, it answered 200 while the messages were
dropped, so a durable outbox deleted messages that were never stored on
either side. The sender now keeps the batch and retries it.
- **GH-5278** (#​5281): A durable TCP listener now answers
`ProcessingFailure` instead of `Received` when its inbox could not store
a batch. The sender keeps the batch and retries it, and a late duplicate
is deduplicated by id. gRPC listeners can't be durable, so they aren't
affected. A guard test now pins that.
- **#​5275** (thanks @​lahma): With
`Durability.EnableInboxPartitioning`, PostgreSQL's durable inbox now
refuses a redelivery whose identity was already handled. Before, the
kept Handled row sat in a different partition and didn't collide, so the
redelivery was handled a second time. This completes the stable-id
deduplication from 6.57.0 for partitioned inboxes.


## 6.57.0

## New

- **GH-5244** (#​5272): NServiceBus interop now supports XML. Many
NServiceBus systems still use the NServiceBus `XmlSerializer`, and they
no longer have to switch to JSON before a migration to Wolverine can
start. Every NServiceBus interop endpoint (Rabbit MQ, Azure Service Bus,
Amazon SQS and SNS, and the SQL Server and PostgreSQL NServiceBus
transports) now reads both JSON and XML, choosing per message from the
`NServiceBus.ContentType` header. To send XML, use the new
`NServiceBusXmlSerializer`:
`.UseNServiceBusInterop().DefaultSerializer(new
NServiceBusXmlSerializer())`. Because one endpoint reads both formats,
NServiceBus endpoints can also move from XML to JSON gradually. See [XML
Serialization with
NServiceBus](https://wolverinefx.net/tutorials/interop.html#xml-serialization-with-nservicebus).
- **#​5268** (thanks @​lahma) and **#​5273**: A durable listener now
gives a message from a non-Wolverine producer, which has no Wolverine
id, a stable envelope id derived from the broker's own identity for that
message. A redelivery is then deduplicated by the durable inbox instead
of being handled twice. This covers Kafka (topic, partition and offset),
NATS JetStream, Pulsar, Redis Streams, Amazon SQS and GCP Pub/Sub pull
subscriptions. On SQS and Pub/Sub it applies in every endpoint mode.
#​5273 also makes raw JSON work on durable SQS listeners. A duplicate
the durable inbox refuses is now logged at `Information` instead of as
an error.
- **GH-5260** (#​5261): New opt-in
`WolverineHttpOptions.RejectUnparseableHeaderValues` answers 400 with
ProblemDetails when a `[FromHeader]` value can't be parsed.

## Fixes

- **GH-5244** (#​5272): NServiceBus replies to Wolverine over Rabbit MQ
now go to the queue marked `UseForReplies()`. Wolverine used to give
NServiceBus its own `wolverine.response.{guid}` system queue as the
reply address, which NServiceBus can't reach, so every reply ended up in
NServiceBus's error queue.
- **GH-5247** (#​5248): `DurabilityAgent.StopAsync()` now cancels its
in-flight durability command and waits for it to finish. Before, a
command still running at shutdown could outlive the host and fail
against a disposed data source.
- **GH-5253** (#​5256, thanks @​thomhurst): A leadership lock
acquisition cancelled by node shutdown is no longer logged as an error.
- **GH-5254** (#​5265): Shutdown now waits for handlers still running on
a buffered local queue.
- **GH-5257** (#​5266): Stopping a listener now interrupts an inline
retry cooldown instead of waiting it out, and the retry doesn't run
after the stop.
- **GH-5262** (#​5263): Lightweight EF Core handlers now collect domain
events before their save, so the events are published atomically with
it.
- **#​5245** (thanks @​ahyousif): Managed EF Core tenancy combined with
`WithDbContextAbstraction` now reuses the tenant DbContext, so
repository writes, the save and outbox enrollment all use one context.
This could previously fail code generation.
- **#​5250** (thanks @​johnkaram02): A request that repeats a
`[FromHeader]` header binds the comma-joined values instead of answering
500.
- **#​5252** (thanks @​johnkaram02) and **#​5259**: Additional error
actions such as `AndPauseProcessing()` chained after
`ScheduleRetryIndefinitely()` / `RequeueIndefinitely()` now apply,
including to the open-ended attempts after the listed delays.

## Dependencies

- JasperFx 2.96.1 and JasperFx.RuntimeCompiler 5.0.1 (#​5258)
- Weasel 9.47.1 (#​5269)

## Docs and build

- The Best Practices guide is rewritten (#​5270).
- New docs for stable envelope ids from non-Wolverine producers, and for
deduplicating CloudEvents (#​5273).
- `./build.sh LocalGate` runs every CI suite on one machine (GH-5125,
#​5246). The CI suites are rebalanced to stay under the 20 minute job
limit (#​5267), and the scaffold tests now pass on Windows (GH-5255,
#​5264).


Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.56.0...V6.61.0).
</details>

Updated
[WolverineFx.FluentValidation](http://github.com/jasperfx/wolverine)
from 6.56.0 to 6.61.0.

<details>
<summary>Release notes</summary>

_Sourced from [WolverineFx.FluentValidation's
releases](http://github.com/jasperfx/wolverine/releases)._

## 6.61.0

This is a minor release. Upgrading is a package bump only: there is no
schema change and no breaking API change.

## New

- **GH-5320** (#​5326): Recurring schedules can now run as often as
every second, and a schedule can skip an occurrence while the previous
one is still running.
- `CronSchedule.MinimumCadence` is now one second, for six-field cron
expressions such as `* * * * * *`. Anything faster than five seconds
always runs on a timer agent, and `UseScheduledMessages()` refuses it.
- The new `SkipWhileRunning()` on a recurring schedule invokes each
occurrence instead of publishing it. An occurrence that comes due while
the previous one is still running is skipped, and skipped occurrences
are not made up later. Skips are logged and counted in the new
`wolverine-recurring-occurrences-skipped` metric.
- With a local handler, the occurrence runs on the node that owns the
schedule's timer agent, so overlap is prevented across the cluster.
Because the occurrence is invoked, other subscribers don't receive it,
and it has no inbox row, queue error policy or dead letter queue. A
failed occurrence is logged and the next one runs on schedule. With a
handler on another node, the wait is capped at 5 seconds.
  - A one-second schedule writes about 86,400 deduplication rows a day.
- **GH-5317** (#​5323):
`PauseThenCatchUpOnAllMartenDaemonActivityAsync()` and the
tracked-session option `PauseThenCatchUpOnAllMartenDaemonActivity()`
catch up the main Marten store and every ancillary store registered with
`IntegrateWithWolverine()` in one call. A store with no events is
skipped.
- **GH-5311** (#​5324): A failed handler attempt now records the
OpenTelemetry `exception` event (`exception.type`, `exception.message`,
`exception.stacktrace`) on its span, instead of only the exception's
type name. Exception messages can carry application data, so you can
turn this off with `opts.Tracking.RecordExceptionsOnSpans = false`.
- **GH-5339** (#​5341): `EntityDescriptor.Ddl` now carries each EF Core
entity's `CREATE TABLE` script, with its keys, foreign keys and indexes,
the same way `DocumentMappingDescriptor.Ddl` does for documents. It's
generated from the model, so no connection is opened, and it's the
model's script rather than the database's. Non-relational providers and
view-mapped entities leave it empty. Needs JasperFx 2.99.0.
- **GH-5332** (#​5338): Wolverine HTTP endpoints now tag the request
span with `http.route` and `http.request.method`, the OpenTelemetry
semantic convention names, alongside `handler.type`. Both are constants
in the generated code. Pre-generated code should be regenerated.
- Dead letter queries can filter their time range on the failure time
with the new `DeadLetterEnvelopeQuery.RangeOn`
(`DeadLetterTimeField.SentAt` or `FailedAt`) (#​5330). `FailedAt` falls
back to `sent_at` when `failed_at` is null, and applies to query,
replay, discard and archive. The default stays `SentAt`. Supported by
the PostgreSQL, SQL Server, MySQL, SQLite, Oracle, CosmosDb and RavenDb
stores.

## Fixes

- **GH-5310** (#​5328): With Marten's `TrackConnections` turned on, the
handler, saga and stream tags (`handler.type`, `message.handler`,
`wolverine.saga.*`, `wolverine.stream.*`) landed on the
`marten.connection` span instead of the message's own span. They now
stay on the message span, and on the request span for HTTP endpoints.
Pre-generated code should be regenerated.
- **GH-5312** (#​5327): A durable `ScheduleRetry(...)` lost the link to
the attempt that failed, because the stored envelope body wasn't
rewritten when the retry was scheduled. Every message store now rewrites
the body when it reschedules an envelope.
- **GH-5313** (#​5321): Azure Service Bus queues and subscriptions and
Amazon SQS queues now report `DeadLetterStorageMode.NativeWithRecovery`
when `EnableDeadLetterQueueRecovery()` drains them, so CritterWatch's
startup check no longer warns about them.
- **GH-5314** (#​5325): With RabbitMQ's
`EnableDeadLetterQueueRecovery()`, stopping the host could throw
`AlreadyClosedException` when two stops ran at once, as they do under
`WebApplicationFactory`. Stopping the dead letter queue listener is now
idempotent and never throws.
- **GH-5315** (#​5322): `FindSingleTrackedMessageOfType<T>()` now counts
envelopes rather than distinct message values, so publishing two equal
records fails the check. A message sent to several destinations still
counts once. Use `tracked.Sent.SingleMessage<T>()` for a check per
destination.
- **GH-5318** (#​5319): The testing docs now explain that the pause in
`PauseThenCatchUpOnMartenDaemonActivity` waits out the high water
agent's current poll tick. The fix is JasperFx/jasperfx#​1100.
- **GH-5331** (#​5336): With runtime code generation, each saga message
disappeared from `ServiceCapabilities.Sagas` once it had been handled,
and the saga disappeared once every message had been. The saga report,
including each store's `GetRegisteredSagasAsync`, now reads the same
before and after the chains compile.
- **GH-5334** (#​5337): In `Balanced` mode, lifting a pause on a URI
that no agent owns, such as a listening endpoint, waited out
`AgentRestrictionConfirmationTimeout` and then threw
`AgentRestrictionsNotConfirmedException`. It now returns at once. Agent
URIs are still confirmed as before.
- Sessions the application opens itself and enrolls with
`IPolecatOutbox.Enroll` or `IFisherOutbox.Enroll` never flushed their
outgoing messages on commit (#​5309). Durable messages waited until a
restart and buffered messages were lost. They now flush on commit, the
same as handler sessions, and the incoming envelope is marked handled
once instead of twice.

## Dependencies

- JasperFx 2.99.0 (#​5333 took 2.98.0, #​5341 2.99.0).
- Weasel 9.48.0 and MySqlConnector 2.6.1 (#​5335). MySqlConnector 2.6.1
closes GHSA-473q-m89c-ghf8 for `WolverineFx.MySql`. On SQL Server, a
`DateTime` parameter in a message store command now goes out as
`datetime2` instead of `datetime`. Every envelope time column is
`datetimeoffset`, so no column is converted either way.

Thanks to @​uniquelau for #​5313, #​5314, #​5315, #​5317 and #​5318, and
to @​lahma for #​5330.

**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.60.0...V6.61.0


## 6.60.0

## What's Changed
* JasperFx 2.97.0 by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5299
* GH-5296 Query saga instances by saga type: filters, ordering, paging
and counts by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5301
* GH-5297 IDocumentStoreDiagnostics for EF Core DbContexts by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5302
* GH-5295 Explicit IDbContextOutbox<T> binds the ancillary store by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5303
* GH-5298 Lightweight saga storage honours the AddSagaType table name by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5304
* GH-5300 CosmosDB saga storage: ISagaStoreDiagnostics read and
QuerySagasAsync by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5305


**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.59.1...V6.60.0

## 6.59.1

This is a patch release with two fixes. Upgrading is a package bump
only: there is no schema change and no API change.

## SQL Server: the inbox and outbox seek on their indexes again

- **GH-5292** (#​5294): Several of the SQL Server message store's
per-envelope statements were scanning the whole envelope table. That
cost grows with the size of the inbox and outbox, and those statements
run once per message. The cause was how two kinds of value were bound as
parameters.
- **Envelope ids went out as `sql_variant`.** The statements that mark
envelopes as handled, reassign them, delete them and move them to the
dead letter queue all filter on the envelope id. Weasel's generic
command builder has no mapping for `Guid`, so it bound each id as
`DbType.Object`, which SqlClient sends as `sql_variant`. SQL Server then
had to compare `CONVERT_IMPLICIT(sql_variant, [id])` on every row, which
rules out a seek on the `uniqueidentifier` primary key. Ids are now
bound as `uniqueidentifier`, so each statement is a primary key seek.
- **Envelope addresses went out as `nvarchar`.** `received_at` on the
inbox and `destination` on the outbox are `varchar(250)` columns, but
statements that filter on them bound the address as `nvarchar`. Under a
SQL collation such as `SQL_Latin1_General_CP1_CI_AS`, the default on
Azure SQL, that converts the column and defeats the index. The
statements affected are the per-envelope updates and deletes, inbox
recovery's page load, outbox loads by destination, the scheduled message
updates and the duplicate check. They now bind the address as `varchar`.
- **`DateTime` parameters kept only the date.** The same generic mapping
typed `DateTime` as `DbType.Date`. The message store now lets SqlClient
type both values itself.
- The new test checks SQL Server's plan cache after running each of
these statements. It requires that no plan contains `sql_variant` and
that no plan converts an indexed envelope column. The full
`SqlServerTests` suite passes (721 tests, 2 skipped).
- PostgreSQL, MySQL, SQLite and Oracle bind exactly as they did before.
- The underlying Weasel mappings are tracked as JasperFx/weasel#​758
(`Guid` as `sql_variant`) and JasperFx/weasel#​759 (`DateTime` as
`date`). Wolverine works around both on its own, so this release doesn't
wait for a Weasel release.

## The `run` command no longer blocks a thread pool thread

- **JasperFx 2.96.2** (JasperFx/jasperfx#​1093, fixes
JasperFx/jasperfx#​1092): Wolverine now depends on JasperFx 2.96.2.
Before this release, the `run` command waited for shutdown by blocking a
thread pool thread for the whole life of the application. That matters
when a container's CPU limit is below one core. `ProcessorCount` is then
1, so the thread pool's minimum is one thread, and the `run` command
held it. After that, every burst of work had to wait for the pool to add
a thread, about 500 ms each time. The reporter saw a 200 ms `Task.Delay`
loop wake up as much as about 950 ms late on Azure Container Apps.
- The `run` command now awaits shutdown instead of blocking. It still
listens for the same three signals: `ApplicationStopping`, Ctrl+C, and
the assembly load context unloading.
- When the host is already started (the `AutoStartHost` /
`WebApplicationFactory` path), `RunJasperFxCommands()` used to block the
calling thread. It now returns a task that completes when the host shuts
down.
- This applies to every application started with `RunJasperFxCommands()`
or `RunWolverineCommands()`, whatever its persistence or transport.

**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.59.0...V6.59.1


## 6.59.0

## New

- **GH-5288** (#​5289): Scheduled message management now knows about
sagas. `ScheduledMessageSummary.SagaId` reports the saga id carried by
each scheduled envelope. The new `ScheduledMessageQuery.SagaId` limits
`QueryAsync` and `CancelAsync` to one saga's scheduled messages, such as
its pending timeouts.
- There is no schema change. The filter reads every scheduled message
that matches the rest of the query and pages the saga's messages in
memory, so `PageSize` and `TotalCount` stay exact. On a large scheduled
backlog, narrow the query with `MessageType` or an execution-time window
as well.
- A `CancelAsync` with a `SagaId` that matches nothing cancels nothing.
- Supported by PostgreSQL, SQL Server, MySQL, SQLite, Oracle, RavenDB
and CosmosDB. RavenDB and CosmosDB now treat `PageSize <= 0` as "read
every match", as the RDBMS stores already did.
- **#​5287**: `WolverineFx.AI`'s `AddLlmCallouts()` now works under
`TypeLoadMode.Static` when the application's pre-generated code was
written without it. A library can ship its own static codegen and a host
can add LLM callouts on top. In Static mode, callouts run on a pre-built
handler that needs no generated code. Every other mode is unchanged.
- New `LlmCalloutOptions.ConfigureChain(Action<HandlerChain>)`
customizes the callout chain in any mode. `IHandlerPolicy` doesn't reach
the pre-built handler.
- `DeduplicateCallouts` is refused at startup in Static mode, because
deduplication is woven into generated code.

## Fixes

- **GH-5286** (#​5290): A send that `IHost.StopAsync()` cancels
mid-flight is no longer logged as a send failure. Azure Service Bus
hands the runtime's cancellation token to the SDK, so an orderly
shutdown used to produce two Error lines for a batch that was never
lost. A durable agent now logs it at Information, because the envelopes
stay in the outbox and are recovered on restart. A non-durable agent
logs it at Warning, because those messages were not sent. A shutdown
cancellation no longer counts toward the circuit breaker or the sending
failure policies. A cancellation while the application is still running,
or any other exception during shutdown, is still logged at Error.


## 6.58.0

## New

- **GH-5279** (#​5280, #​5282): New opt-in
`Durability.UseTransactionScopedDurabilityPassLock` for PostgreSQL. The
serverless durability pass takes a transaction-scoped advisory lock
(`pg_try_advisory_xact_lock`) instead of a session lock. This makes the
pass lock safe behind a transaction-mode pooler such as PgBouncer or
Supabase's pooler, where a session lock can leak onto a pooled
connection or be held by two passes at once. #​5282 hardens it:
  - the lock connection stays out of an ambient `TransactionScope`
- an `idle_in_transaction_session_timeout` can no longer drop the lock
in the middle of a pass
- a lock transaction that ends before the pass does is reported, and its
pooled connection is discarded
  - a session unlock that fails is logged as a warning

Wolverine also logs a one-time warning when the store doesn't support
the setting, or when extension steps such as Marten's projection
catch-up run under the open lock transaction. That open transaction can
stall Marten's gap detection, which is tracked in JasperFx/marten#​5791.
See the serverless guide.

## Fixes

- **GH-5276** (#​5277, thanks @​3nth): The HTTP transport's batch
endpoint now answers 503 when a durable local queue cannot write the
batch to its inbox. Before, it answered 200 while the messages were
dropped, so a durable outbox deleted messages that were never stored on
either side. The sender now keeps the batch and retries it.
- **GH-5278** (#​5281): A durable TCP listener now answers
`ProcessingFailure` instead of `Received` when its inbox could not store
a batch. The sender keeps the batch and retries it, and a late duplicate
is deduplicated by id. gRPC listeners can't be durable, so they aren't
affected. A guard test now pins that.
- **#​5275** (thanks @​lahma): With
`Durability.EnableInboxPartitioning`, PostgreSQL's durable inbox now
refuses a redelivery whose identity was already handled. Before, the
kept Handled row sat in a different partition and didn't collide, so the
redelivery was handled a second time. This completes the stable-id
deduplication from 6.57.0 for partitioned inboxes.


## 6.57.0

## New

- **GH-5244** (#​5272): NServiceBus interop now supports XML. Many
NServiceBus systems still use the NServiceBus `XmlSerializer`, and they
no longer have to switch to JSON before a migration to Wolverine can
start. Every NServiceBus interop endpoint (Rabbit MQ, Azure Service Bus,
Amazon SQS and SNS, and the SQL Server and PostgreSQL NServiceBus
transports) now reads both JSON and XML, choosing per message from the
`NServiceBus.ContentType` header. To send XML, use the new
`NServiceBusXmlSerializer`:
`.UseNServiceBusInterop().DefaultSerializer(new
NServiceBusXmlSerializer())`. Because one endpoint reads both formats,
NServiceBus endpoints can also move from XML to JSON gradually. See [XML
Serialization with
NServiceBus](https://wolverinefx.net/tutorials/interop.html#xml-serialization-with-nservicebus).
- **#​5268** (thanks @​lahma) and **#​5273**: A durable listener now
gives a message from a non-Wolverine producer, which has no Wolverine
id, a stable envelope id derived from the broker's own identity for that
message. A redelivery is then deduplicated by the durable inbox instead
of being handled twice. This covers Kafka (topic, partition and offset),
NATS JetStream, Pulsar, Redis Streams, Amazon SQS and GCP Pub/Sub pull
subscriptions. On SQS and Pub/Sub it applies in every endpoint mode.
#​5273 also makes raw JSON work on durable SQS listeners. A duplicate
the durable inbox refuses is now logged at `Information` instead of as
an error.
- **GH-5260** (#​5261): New opt-in
`WolverineHttpOptions.RejectUnparseableHeaderValues` answers 400 with
ProblemDetails when a `[FromHeader]` value can't be parsed.

## Fixes

- **GH-5244** (#​5272): NServiceBus replies to Wolverine over Rabbit MQ
now go to the queue marked `UseForReplies()`. Wolverine used to give
NServiceBus its own `wolverine.response.{guid}` system queue as the
reply address, which NServiceBus can't reach, so every reply ended up in
NServiceBus's error queue.
- **GH-5247** (#​5248): `DurabilityAgent.StopAsync()` now cancels its
in-flight durability command and waits for it to finish. Before, a
command still running at shutdown could outlive the host and fail
against a disposed data source.
- **GH-5253** (#​5256, thanks @​thomhurst): A leadership lock
acquisition cancelled by node shutdown is no longer logged as an error.
- **GH-5254** (#​5265): Shutdown now waits for handlers still running on
a buffered local queue.
- **GH-5257** (#​5266): Stopping a listener now interrupts an inline
retry cooldown instead of waiting it out, and the retry doesn't run
after the stop.
- **GH-5262** (#​5263): Lightweight EF Core handlers now collect domain
events before their save, so the events are published atomically with
it.
- **#​5245** (thanks @​ahyousif): Managed EF Core tenancy combined with
`WithDbContextAbstraction` now reuses the tenant DbContext, so
repository writes, the save and outbox enrollment all use one context.
This could previously fail code generation.
- **#​5250** (thanks @​johnkaram02): A request that repeats a
`[FromHeader]` header binds the comma-joined values instead of answering
500.
- **#​5252** (thanks @​johnkaram02) and **#​5259**: Additional error
actions such as `AndPauseProcessing()` chained after
`ScheduleRetryIndefinitely()` / `RequeueIndefinitely()` now apply,
including to the open-ended attempts after the listed delays.

## Dependencies

- JasperFx 2.96.1 and JasperFx.RuntimeCompiler 5.0.1 (#​5258)
- Weasel 9.47.1 (#​5269)

## Docs and build

- The Best Practices guide is rewritten (#​5270).
- New docs for stable envelope ids from non-Wolverine producers, and for
deduplicating CloudEvents (#​5273).
- `./build.sh LocalGate` runs every CI suite on one machine (GH-5125,
#​5246). The CI suites are rebalanced to stay under the 20 minute job
limit (#​5267), and the scaffold tests now pass on Windows (GH-5255,
#​5264).


Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.56.0...V6.61.0).
</details>

Updated
[WolverineFx.RuntimeCompilation](http://github.com/jasperfx/wolverine)
from 6.56.0 to 6.61.0.

<details>
<summary>Release notes</summary>

_Sourced from [WolverineFx.RuntimeCompilation's
releases](http://github.com/jasperfx/wolverine/releases)._

## 6.61.0

This is a minor release. Upgrading is a package bump only: there is no
schema change and no breaking API change.

## New

- **GH-5320** (#​5326): Recurring schedules can now run as often as
every second, and a schedule can skip an occurrence while the previous
one is still running.
- `CronSchedule.MinimumCadence` is now one second, for six-field cron
expressions such as `* * * * * *`. Anything faster than five seconds
always runs on a timer agent, and `UseScheduledMessages()` refuses it.
- The new `SkipWhileRunning()` on a recurring schedule invokes each
occurrence instead of publishing it. An occurrence that comes due while
the previous one is still running is skipped, and skipped occurrences
are not made up later. Skips are logged and counted in the new
`wolverine-recurring-occurrences-skipped` metric.
- With a local handler, the occurrence runs on the node that owns the
schedule's timer agent, so overlap is prevented across the cluster.
Because the occurrence is invoked, other subscribers don't receive it,
and it has no inbox row, queue error policy or dead letter queue. A
failed occurrence is logged and the next one runs on schedule. With a
handler on another node, the wait is capped at 5 seconds.
  - A one-second schedule writes about 86,400 deduplication rows a day.
- **GH-5317** (#​5323):
`PauseThenCatchUpOnAllMartenDaemonActivityAsync()` and the
tracked-session option `PauseThenCatchUpOnAllMartenDaemonActivity()`
catch up the main Marten store and every ancillary store registered with
`IntegrateWithWolverine()` in one call. A store with no events is
skipped.
- **GH-5311** (#​5324): A failed handler attempt now records the
OpenTelemetry `exception` event (`exception.type`, `exception.message`,
`exception.stacktrace`) on its span, instead of only the exception's
type name. Exception messages can carry application data, so you can
turn this off with `opts.Tracking.RecordExceptionsOnSpans = false`.
- **GH-5339** (#​5341): `EntityDescriptor.Ddl` now carries each EF Core
entity's `CREATE TABLE` script, with its keys, foreign keys and indexes,
the same way `DocumentMappingDescriptor.Ddl` does for documents. It's
generated from the model, so no connection is opened, and it's the
model's script rather than the database's. Non-relational providers and
view-mapped entities leave it empty. Needs JasperFx 2.99.0.
- **GH-5332** (#​5338): Wolverine HTTP endpoints now tag the request
span with `http.route` and `http.request.method`, the OpenTelemetry
semantic convention names, alongside `handler.type`. Both are constants
in the generated code. Pre-generated code should be regenerated.
- Dead letter queries can filter their time range on the failure time
with the new `DeadLetterEnvelopeQuery.RangeOn`
(`DeadLetterTimeField.SentAt` or `FailedAt`) (#​5330). `FailedAt` falls
back to `sent_at` when `failed_at` is null, and applies to query,
replay, discard and archive. The default stays `SentAt`. Supported by
the PostgreSQL, SQL Server, MySQL, SQLite, Oracle, CosmosDb and RavenDb
stores.

## Fixes

- **GH-5310** (#​5328): With Marten's `TrackConnections` turned on, the
handler, saga and stream tags (`handler.type`, `message.handler`,
`wolverine.saga.*`, `wolverine.stream.*`) landed on the
`marten.connection` span instead of the message's own span. They now
stay on the message span, and on the request span for HTTP endpoints.
Pre-generated code should be regenerated.
- **GH-5312** (#​5327): A durable `ScheduleRetry(...)` lost the link to
the attempt that failed, because the stored envelope body wasn't
rewritten when the retry was scheduled. Every message store now rewrites
the body when it reschedules an envelope.
- **GH-5313** (#​5321): Azure Service Bus queues and subscriptions and
Amazon SQS queues now report `DeadLetterStorageMode.NativeWithRecovery`
when `EnableDeadLetterQueueRecovery()` drains them, so CritterWatch's
startup check no longer warns about them.
- **GH-5314** (#​5325): With RabbitMQ's
`EnableDeadLetterQueueRecovery()`, stopping the host could throw
`AlreadyClosedException` when two stops ran at once, as they do under
`WebApplicationFactory`. Stopping the dead letter queue listener is now
idempotent and never throws.
- **GH-5315** (#​5322): `FindSingleTrackedMessageOfType<T>()` now counts
envelopes rather than distinct message values, so publishing two equal
records fails the check. A message sent to several destinations still
counts once. Use `tracked.Sent.SingleMessage<T>()` for a check per
destination.
- **GH-5318** (#​5319): The testing docs now explain that the pause in
`PauseThenCatchUpOnMartenDaemonActivity` waits out the high water
agent's current poll tick. The fix is JasperFx/jasperfx#​1100.
- **GH-5331** (#​5336): With runtime code generation, each saga message
disappeared from `ServiceCapabilities.Sagas` once it had been handled,
and the saga disappeared once every message had been. The saga report,
including each store's `GetRegisteredSagasAsync`, now reads the same
before and after the chains compile.
- **GH-5334** (#​5337): In `Balanced` mode, lifting a pause on a URI
that no agent owns, such as a listening endpoint, waited out
`AgentRestrictionConfirmationTimeout` and then threw
`AgentRestrictionsNotConfirmedException`. It now returns at once. Agent
URIs are still confirmed as before.
- Sessions the application opens itself and enrolls with
`IPolecatOutbox.Enroll` or `IFisherOutbox.Enroll` never flushed their
outgoing messages on commit (#​5309). Durable messages waited until a
restart and buffered messages were lost. They now flush on commit, the
same as handler sessions, and the incoming envelope is marked handled
once instead of twice.

## Dependencies

- JasperFx 2.99.0 (#​5333 took 2.98.0, #​5341 2.99.0).
- Weasel 9.48.0 and MySqlConnector 2.6.1 (#​5335). MySqlConnector 2.6.1
closes GHSA-473q-m89c-ghf8 for `WolverineFx.MySql`. On SQL Server, a
`DateTime` parameter in a message store command now goes out as
`datetime2` instead of `datetime`. Every envelope time column is
`datetimeoffset`, so no column is converted either way.

Thanks to @​uniquelau for #​5313, #​5314, #​5315, #​5317 and #​5318, and
to @​lahma for #​5330.

**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.60.0...V6.61.0


## 6.60.0

## What's Changed
* JasperFx 2.97.0 by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5299
* GH-5296 Query saga instances by saga type: filters, ordering, paging
and counts by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5301
* GH-5297 IDocumentStoreDiagnostics for EF Core DbContexts by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5302
* GH-5295 Explicit IDbContextOutbox<T> binds the ancillary store by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5303
* GH-5298 Lightweight saga storage honours the AddSagaType table name by
@​jeremydmiller in https://github.com/JasperFx/wolverine/pull/5304
* GH-5300 CosmosDB saga storage: ISagaStoreDiagnostics read and
QuerySagasAsync by @​jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5305


**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.59.1...V6.60.0

## 6.59.1

This is a patch release with two fixes. Upgrading is a package bump
only: there is no schema change and no API change.

## SQL Server: the inbox and outbox seek on their indexes again

- **GH-5292** (#​5294): Several of the SQL Server message store's
per-envelope statements were scanning the whole envelope table. That
cost grows with the size of the inbox and outbox, and those statements
run once per message. The cause was how two kinds of value were bound as
parameters.
- **Envelope ids went out as `sql_variant`.** The statements that mark
envelopes as handled, reassign them, delete them and move them to the
dead letter queue all filter on the envelope id. Weasel's generic
command builder has no mapping for `Guid`, so it bound each id as
`DbType.Object`, which SqlClient sends as `sql_variant`. SQL Server then
had to compare `CONVERT_IMPLICIT(sql_variant, [id])` on every row, which
rules out a seek on the `uniqueidentifier` primary key. Ids are now
bound as `uniqueidentifier`, so each statement is a primary key seek.
- **Envelope addresses went out as `nvarchar`.** `received_at` on the
inbox and `destination` on the outbox are `varchar(250)` columns, but
statements that filter on them bound the address as `nvarchar`. Under a
SQL collation such as `SQL_Latin1_General_CP1_CI_AS`, the default on
Azure SQL, that converts the column and defeats the index. The
statements affected are the per-envelope updates and deletes, inbox
recovery's page load, outbox loads by destination, the scheduled message
updates and the duplicate check. They now bind the address as `varchar`.
- **`DateTime` parameters kept only the date.** The same generic mapping
typed `DateTime` as `DbType.Date`. The message store now lets SqlClient
type both values itself.
- The new test checks SQL Server's plan cache after running each of
these statements. It requires that no plan contains `sql_variant` and
that no plan converts an indexed envelope column. The full
`SqlServerTests` suite passes (721 tests, 2 skipped).
- PostgreSQL, MySQL, SQLite and Oracle bind exactly as they did before.
- The underlying Weasel mappings are tracked as JasperFx/weasel#​758
(`Guid` as `sql_variant`) and JasperFx/weasel#​759 (`DateTime` as
`date`). Wolverine works around both on its own, so this release doesn't
wait for a Weasel release.

## The `run` command no longer blocks a thread pool thread

- **JasperFx 2.96.2** (JasperFx/jasperfx#​1093, fixes
JasperFx/jasperfx#​1092): Wolverine now depends on JasperFx 2.96.2.
Before this release, the `run` command waited for shutdown by blocking a
thread pool thread for the whole life of the application. That matters
when a container's CPU limit is below one core. `ProcessorCount` is then
1, so the thread pool's minimum is one thread, and the `run` command
held it. After that, every burst of work had to wait for the pool to add
a thread, about 500 ms each time. The reporter saw a 200 ms `Task.Delay`
loop wake up as much as about 950 ms late on Azure Container Apps.
- The `run` command now awaits shutdown instead of blocking. It still
listens for the same three signals: `ApplicationStopping`, Ctrl+C, and
the assembly load context unloading.
- When the host is already started (the `AutoStartHost` /
`WebApplicationFactory` path), `RunJasperFxCommands()` used to block the
calling thread. It now returns a task that completes when the host shuts
down.
- This applies to every application started with `RunJasperFxCommands()`
or `RunWolverineCommands()`, whatever its persistence or transport.

**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.59.0...V6.59.1


## 6.59.0

## New

- **GH-5288** (#​5289): Scheduled message management now knows about
sagas. `ScheduledMessageSummary.SagaId` reports the saga id carried by
each scheduled envelope. The new `ScheduledMessageQuery.SagaId` limits
`QueryAsync` and `CancelAsync` to one saga's scheduled messages, such as
its pending timeouts.
- There is no schema change. The filter reads every scheduled message
that matches the rest of the query and pages the saga's messages in
memory, so `PageSize` and `TotalCount` stay exact. On a large scheduled
backlog, narrow the query with `MessageType` or an execution-time window
as well.
- A `CancelAsync` with a `SagaId` that matches nothing cancels nothing.
- Supported by PostgreSQL, SQL Server, MySQL, SQLite, Oracle, RavenDB
and CosmosDB. RavenDB and CosmosDB now treat `PageSize <= 0` as "read
every match", as the RDBMS stores already did.
- **#​5287**: `WolverineFx.AI`'s `AddLlmCallouts()` now works under
`TypeLoadMode.Static` when the application's pre-generated code was
written without it. A library can ship its own static codegen and a host
can add LLM callouts on top. In Static mode, callouts run on a pre-built
handler that needs no generated code. Every other mode is unchanged.
- New `LlmCalloutOptions.ConfigureChain(Action<HandlerChain>)`
customizes the callout chain in any mode. `IHandlerPolicy` doesn't reach
the pre-built handler.
- `DeduplicateCallouts` is refused at startup in Static mode, because
deduplication is woven into generated code.

## Fixes

- **GH-5286** (#​5290): A send that `IHost.StopAsync()` cancels
mid-flight is no longer logged as a send failure. Azure Service Bus
hands the runtime's cancellation token to the SDK, so an orderly
shutdown used to produce two Error lines for a batch that was never
lost. A durable agent now logs it at Information, because the envelopes
stay in the outbox and are recovered on restart. A non-durable agent
logs it at Warning, because those messages were not sent. A shutdown
cancellation no longer counts toward the circuit breaker or the sending
failure policies. A cancellation while the application is still running,
or any other exception during shutdown, is still logged at Error.


## 6.58.0

## New

- **GH-5279** (#​5280, #​5282): New opt-in
`Durability.UseTransactionScopedDurabilityPassLock` for PostgreSQL. The
serverless durability pass takes a transaction-scoped advisory lock
(`pg_try_advisory_xact_lock`) instead of a session lock. This makes the
pass lock safe behind a transaction-mode pooler such as PgBouncer or
Supabase's pooler, where a session lock can leak onto a pooled
connection or be held by two passes at once. #​5282 hardens it:
  - the lock connection stays out of an ambient `TransactionScope`
- an `idle_in_transaction_session_timeout` can no longer drop the lock
in the middle of a pass
- a lock transaction that ends before the pass does is reported, and its
pooled connection is discarded
  - a session unlock that fails is logged as a warning

Wolverine also logs a one-time warning when the store doesn't support
the setting, or when extension steps such as Marten's projection
catch-up run under the open lock transaction. That open transaction can
stall Marten's gap detection, which is tracked in JasperFx/marten#​5791.
See the serverless guide.

## Fixes

- **GH-5276** (#​5277, thanks @​3nth): The HTTP transport's batch
endpoint now answers 503 when a durable local queue cannot write the
batch to its inbox. Before, it answered 200 while the messages were
dropped, so a durable outbox deleted messages that were never stored on
either side. The sender now keeps the batch and retries it.
- **GH-5278** (#​5281): A durable TCP listener now answers
`ProcessingFailure` instead of `Received` when its inbox could not store
a batch. The sender keeps the batch and retries it, and a late duplicate
is deduplicated by id. gRPC listeners can't be durable, so they aren't
affected. A guard test now pins that.
- **#​5275** (thanks @​lahma): With
`Durability.EnableInboxPartitioning`, PostgreSQL's durable inbox now
refuses a redelivery whose identity was already handled. Before, the
kept Handled row sat in a different partition and didn't collide, so the
redelivery was handled a second time. This completes the stable-id
deduplication from 6.57.0 for partitioned inboxes.


## 6.57.0

## New

- **GH-5244** (#​5272): NServiceBus interop now supports XML. Many
NServiceBus systems still use the NServiceBus `XmlSerializer`, and they
no longer have to switch to JSON before a migration to Wolverine can
start. Every NServiceBus interop endpoint (Rabbit MQ, Azure Service Bus,
Amazon SQS and SNS, and the SQL Server and PostgreSQL NServiceBus
transports) now reads both JSON and XML, choosing per message from the
`NServiceBus.ContentType` header. To send XML, use the new
`NServiceBusXmlSerializer`:
`.UseNServiceBusInterop().DefaultSerializer(new
NServiceBusXmlSerializer())`. Because one endpoint reads both formats,
NServiceBus endpoints can also move from XML to JSON gradually. See [XML
Serialization with
NServiceBus](https://wolverinefx.net/tutorials/interop.html#xml-serialization-with-nservicebus).
- **#​5268** (thanks @​lahma) and **#​5273**: A durable listener now
gives a message from a non-Wolverine producer, which has no Wolverine
id, a stable envelope id derived from the broker's own identity for that
message. A redelivery is then deduplicated by the durable inbox instead
of being handled twice. This covers Kafka (topic, partition and offset),
NATS JetStream, Pulsar, Redis Streams, Amazon SQS and GCP Pub/Sub pull
subscriptions. On SQS and Pub/Sub it applies in every endpoint mode.
#​5273 also makes raw JSON work on durable SQS listeners. A duplicate
the durable inbox refuses is now logged at `Information` instead of as
an error.
- **GH-5260** (#​5261): New opt-in
`WolverineHttpOptions.RejectUnparseableHeaderValues` answers 400 with
ProblemDetails when a `[FromHeader]` value can't be parsed.

## Fixes

- **GH-5244** (#​5272): NServiceBus replies to Wolverine over Rabbit MQ
now go to the queue marked `UseForReplies()`. Wolverine used to give
NServiceBus its own `wolverine.response.{guid}` system queue as the
reply address, which NServiceBus can't reach, so every reply ended up in
NServiceBus's error queue.
- **GH-5247** (#​5248): `DurabilityAgent.StopAsync()` now cancels its
in-flight durability command and waits for it to finish. Before, a
command still running at shutdown could outlive the host and fail
against a disposed data source.
- **GH-5253** (#​5256, thanks @​thomhurst): A leadership lock
acquisition cancelled by node shutdown is no longer logged as an error.
- **GH-5254** (#​5265): Shutdown now waits for handlers still running on
a buffered local queue.
- **GH-5257** (#​5266): Stopping a listener now interrupts an inline
retry cooldown instead of waiting it out, and the retry doesn't run
after the stop.
- **GH-5262** (#​5263): Lightweight EF Core handlers now collect domain
events before their save, so the events are published atomically with
it.
- **#​5245** (thanks @​ahyousif): Managed EF Core tenancy combined with
`WithDbContextAbstraction` now reuses the tenant DbContext, so
repository writes, the save and outbox enrollment all use one context.
This could previously fail code generation.
- **#​5250** (thanks @​johnkaram02): A request that repeats a
`[FromHeader]` header binds the comma-joined values instead of answering
500.
- **#​5252** (thanks @​johnkaram02) and **#​5259**: Additional error
actions such as `AndPauseProcessing()` chained after
`ScheduleRetryIndefinitely()` / `RequeueIndefinitely()` now apply,
including to the open-ended attempts after the listed delays.

## Dependencies

- JasperFx 2.96.1 and JasperFx.RuntimeCompiler 5.0.1 (#​5258)
- Weasel 9.47.1 (#​5269)

## Docs and build

- The Best Practices guide is rewritten (#​5270).
- New docs for stable envelope ids from non-Wolverine producers, and for
deduplicating CloudEvents (#​5273).
- `./build.sh LocalGate` runs every CI suite on one machine (GH-5125,
#​5246). The CI suites are rebalanced to stay under the 20 minute job
limit (#​5267), and the scaffold tests now pass on Windows (GH-5255,
#​5264).


Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.56.0...V6.61.0).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Chores**
* Updated internal package versions. No end-user-visible changes are
included in this update.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant