Repository navigation
GH-5205, GH-5206: [DeduplicatedWithResponse] claim-in-transaction gaps ([WriteAggregate] T / [BoundaryModel], and repeats refused over committed work) - #5208
Merged
Conversation
…egate itself [DeduplicatedWithResponse(ClaimInTransaction = true)], and [Deduplicated] on Marten, ask GetPersistenceProviders for the provider that owns the chain's transaction. A [WriteAggregate] that binds the aggregate rather than IEventStream<T> leaves nothing for Marten's CanApply to see: it ignores the aggregate handler workflow on purpose, because the workflow applies its store's transaction itself (GH-3893). The lookup found no provider, and the endpoint fell back to claim-and-release although it commits through a Marten session. GetPersistenceProviders now falls back to the store AggregateHandling applied, only when ProvidersClaiming finds nothing. AutoApplyTransactions, [Transactional] and CanApply are unchanged, so no chain that resolves an owner today changes, and a mixed-store chain gets no second claimant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion too [DcbModel] and [BoundaryModel] apply their store's transaction themselves, as the aggregate handler workflow does, without recording an AggregateHandling, so a [Deduplicated] chain whose only tie to Marten was a boundary model still fell back to claim-and-release. BoundaryHandlingTag now records the provider, and AggregateHandling.TransactionOwner consults it alongside the workflow's. Also replaces the GH-???? placeholders with GH-5205. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
…d work is answered from its claim On the ClaimInTransaction path a repeat looks its claim up once, at the front. One that missed it could then read the first request's committed work and fail over it in Load/Before/Validate (a 409 admission rule, a 404, a throw) before its commit, so the race handling never ran and the client was refused for a request that had succeeded. The claim commits in the same transaction as that work, so a second lookup finds it. Once the front lookup has found nothing, the generated code arms WatchForDeduplicatedRefusal, which holds the response back in a buffer until the commit. A wrapper that BuildEndpoint puts around every [DeduplicatedWithResponse] endpoint then looks a failure before the commit (400 and up, or a throw) up again, and answers it from a claim if there is one: the stored response, or 422 for different bytes. Otherwise the endpoint's own answer goes out unchanged. A failure of the commit itself is still decided by AnswerDeduplicatedResponseRaceFrame, so one that finds this request's own claim is still rethrown. The wrapper is runtime code, so the C# and F# generated code only gain a one-line call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
… claim-and-release GH-5205's fallback also reaches Polecat and Fisher, whose CanApply ignores the aggregate handler workflow the same way Marten's does: a [Deduplicated] handler with [WriteAggregate] of the aggregate itself now rides their transaction too. And on Marten's default claim-and-release path the GH-5165 commit hook is now attached to such an endpoint, so a failure inside SaveChangesAsync after the database commit keeps the claim. Tests for each; all three Marten ones fail without the fallback. GH-5206: a concurrent request with the same key but different bytes, refused over the first's committed work, is answered 422 as any such repeat is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
…mmitted work is refused as a duplicate The same race as [DeduplicatedWithResponse(ClaimInTransaction)], on an HTTP [Deduplicated] endpoint whose claim rides its own transaction (Marten, Polecat, Fisher). A concurrent repeat missed the up-front check, read the first request's committed work and failed over it before its commit, so it answered its own refusal (a 409 admission rule, a 404, a 500 from a throw) instead of the endpoint's duplicate answer. EF Core claims inside its transaction before the handler runs, so a concurrent repeat already waits on the first one's row; it is unchanged. The watch from the previous commit now takes either answer. TransactionalDeduplication gains an optional Commit frame, which those three providers set; the HTTP chain arms WatchForDuplicateRefusal after the queued claim and marks the commit as started just before it, from which point the commit race wrapper decides as before. A failure before that is looked up again through IDeduplicationClaimLookup, a new optional interface the RDBMS deduplication store implements, and answered with the duplicate status and problem detail the up-front check gives. A custom IMessageDeduplicator, or a store without the lookup, keeps the old behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
A [WriteAggregate] of the aggregate itself with ClaimInTransaction, and the refusal recheck for both [DeduplicatedWithResponse] and [Deduplicated], on a [MartenStore] endpoint: each claim lands in the ancillary store's message tables and is looked up again there, never in the main store's. The four recheck tests fail when the lookup is pointed at the main store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
…dpoints Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
Contributor
|
One regression the GH-5205 fallback introduces, in a setup that works on A host with Answering with the registered provider of that type fixes it: var applied = AggregateHandling.TransactionOwner(chain)?.GetType();
return ordered.FirstOrDefault(x => x.GetType() == applied) ?? _nullo;( It's an undocumented setup, so take it or leave it. |
…low's store [WriteAggregate] names a new provider instance whether or not its store is integrated with Wolverine. With Marten registered but not integrated, and the messages persisted in PostgreSQL, a [Deduplicated] handler taking [WriteAggregate] T failed codegen on IMartenDeduplicator where main kept claim-and-release. The fallback now answers with the registered provider of that store's type, or none. Reported on #5208 by @uniquelau, with the fix. Co-authored-by: Laurence Gillian <lau@eddlondon.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
This was referenced Oct 26, 2026
This was referenced Oct 29, 2026
github-actions Bot
pushed a commit
to MrD4rkne/oweme-ecosystem
that referenced
this pull request
Oct 30, 2026
Updated [Aspire.Hosting.AppHost](https://github.com/microsoft/aspire)
from 13.6.3 to 13.6.4.
<details>
<summary>Release notes</summary>
_Sourced from [Aspire.Hosting.AppHost's
releases](https://github.com/microsoft/aspire/releases)._
## 13.6.4
## What's New in Aspire 13.6.4
Patch release for Aspire 13.6 that improves resource startup
reliability, fixes Azure publishing with parameterized enum properties
in TypeScript AppHosts, and tightens CLI diagnostic log permissions on
Unix.
### 🐛 Fixes
- 🖥️ **Resources could fail to start after a DCP create request timed
out** — AppHost now checks whether a resource was created before
retrying a timed-out or HTTP 504 request, avoiding a premature
`FailedToStart` state when DCP completed the operation. API operations
default to a 40-second timeout with an additional 20-second allowance
before the first successful operation, and resource creation has a
two-minute recovery budget. These limits can be configured through
`DcpPublisher:KubernetesApiTimeout`,
`DcpPublisher:KubernetesInitializationAdditionalTimeout`, and
`DcpPublisher:KubernetesCreateRecoveryTimeout`. Established watch and
log streams are not limited by these operation timeouts. Addresses
startup timeouts reported in 13.6. Fixes
[#20691](https://github.com/microsoft/aspire/issues/20691).
([#20868](https://github.com/microsoft/aspire/pull/20868), backport of
[#20752](https://github.com/microsoft/aspire/pull/20752), @danegsta)
- ☁️ **TypeScript AppHosts could fail to publish parameterized Azure
enum properties** — Assigning a `bicep().parameter(...)` expression to
an enum-valued provisioning property, such as an Azure Storage SKU name,
now works instead of failing with a type-assignment error. Type checking
for literal values and incompatible expression types is preserved. Fixes
a scenario introduced with the Azure provisioning proxy feature in 13.6.
([#20767](https://github.com/microsoft/aspire/pull/20767), backport of
[#20557](https://github.com/microsoft/aspire/pull/20557),
@sebastienros)
- 🔒 **CLI diagnostic logs now use owner-only permissions on Unix** — Log
files are restricted to owner read/write access (`0600`), including
reused files, and the dedicated Aspire logs directory uses owner-only
access (`0700`), repairing existing permissions when needed. Newly
created custom log directories also use `0700`; existing custom parent
directories and Windows permission behavior are unchanged. Diagnostic
logs can contain sensitive data, including dashboard login URLs, so
review them before sharing.
([#21282](https://github.com/microsoft/aspire/pull/21282), backport of
[#21276](https://github.com/microsoft/aspire/pull/21276), @mitchdenny)
---
*Full Changelog:
[v13.6.3...v13.6.4](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4)*
*Full commit:
[c89352703f78ae46de90fbf751ed8b52b77b89bf](https://github.com/microsoft/aspire/commit/c89352703f78ae46de90fbf751ed8b52b77b89bf)*
Commits viewable in [compare
view](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4).
</details>
Updated [Aspire.Hosting.PostgreSQL](https://github.com/microsoft/aspire)
from 13.6.3 to 13.6.4.
<details>
<summary>Release notes</summary>
_Sourced from [Aspire.Hosting.PostgreSQL's
releases](https://github.com/microsoft/aspire/releases)._
## 13.6.4
## What's New in Aspire 13.6.4
Patch release for Aspire 13.6 that improves resource startup
reliability, fixes Azure publishing with parameterized enum properties
in TypeScript AppHosts, and tightens CLI diagnostic log permissions on
Unix.
### 🐛 Fixes
- 🖥️ **Resources could fail to start after a DCP create request timed
out** — AppHost now checks whether a resource was created before
retrying a timed-out or HTTP 504 request, avoiding a premature
`FailedToStart` state when DCP completed the operation. API operations
default to a 40-second timeout with an additional 20-second allowance
before the first successful operation, and resource creation has a
two-minute recovery budget. These limits can be configured through
`DcpPublisher:KubernetesApiTimeout`,
`DcpPublisher:KubernetesInitializationAdditionalTimeout`, and
`DcpPublisher:KubernetesCreateRecoveryTimeout`. Established watch and
log streams are not limited by these operation timeouts. Addresses
startup timeouts reported in 13.6. Fixes
[#20691](https://github.com/microsoft/aspire/issues/20691).
([#20868](https://github.com/microsoft/aspire/pull/20868), backport of
[#20752](https://github.com/microsoft/aspire/pull/20752), @danegsta)
- ☁️ **TypeScript AppHosts could fail to publish parameterized Azure
enum properties** — Assigning a `bicep().parameter(...)` expression to
an enum-valued provisioning property, such as an Azure Storage SKU name,
now works instead of failing with a type-assignment error. Type checking
for literal values and incompatible expression types is preserved. Fixes
a scenario introduced with the Azure provisioning proxy feature in 13.6.
([#20767](https://github.com/microsoft/aspire/pull/20767), backport of
[#20557](https://github.com/microsoft/aspire/pull/20557),
@sebastienros)
- 🔒 **CLI diagnostic logs now use owner-only permissions on Unix** — Log
files are restricted to owner read/write access (`0600`), including
reused files, and the dedicated Aspire logs directory uses owner-only
access (`0700`), repairing existing permissions when needed. Newly
created custom log directories also use `0700`; existing custom parent
directories and Windows permission behavior are unchanged. Diagnostic
logs can contain sensitive data, including dashboard login URLs, so
review them before sharing.
([#21282](https://github.com/microsoft/aspire/pull/21282), backport of
[#21276](https://github.com/microsoft/aspire/pull/21276), @mitchdenny)
---
*Full Changelog:
[v13.6.3...v13.6.4](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4)*
*Full commit:
[c89352703f78ae46de90fbf751ed8b52b77b89bf](https://github.com/microsoft/aspire/commit/c89352703f78ae46de90fbf751ed8b52b77b89bf)*
Commits viewable in [compare
view](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4).
</details>
Updated
[Microsoft.AspNetCore.Authentication.JwtBearer](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.AspNetCore.Authentication.JwtBearer's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.AspNetCore.Mvc.Testing's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated [Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.AspNetCore.OpenApi's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated [Microsoft.Bcl.TimeProvider](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Bcl.TimeProvider's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore](https://github.com/dotnet/dotnet) from
10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore.Abstractions](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore.Abstractions's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore.Design](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore.Design's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore.Tools](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore.Tools's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.ApiDescription.Server](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.ApiDescription.Server's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Caching.Memory](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Caching.Memory's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Configuration.EnvironmentVariables](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Configuration.EnvironmentVariables's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Configuration.Json](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Configuration.Json's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.DependencyInjection.Abstractions](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.DependencyInjection.Abstractions's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from
[Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Hosting.Abstractions](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Hosting.Abstractions's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated [Microsoft.Extensions.Http](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Http's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Testing.Extensions.TrxReport](https://github.com/microsoft/testfx)
from 2.5.1 to 2.5.2.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Testing.Extensions.TrxReport's
releases](https://github.com/microsoft/testfx/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/microsoft/testfx/commits).
</details>
Updated [WolverineFx](http://github.com/jasperfx/wolverine) from 6.51.3
to 6.56.0.
<details>
<summary>Release notes</summary>
_Sourced from [WolverineFx's
releases](http://github.com/jasperfx/wolverine/releases)._
## 6.56.0
## New
- **GH-5238** (#5242): `opts.UseHttpTransport()` registers the HTTP
transport inside `UseWolverine()`. `http`/`https` URIs then resolve in
`ListenForMessagesFrom()`, `PublishMessage<T>().To(uri)`, and runtime
`EndpointFor()` / `ToDestination()`. Before this, the transport was only
registered by `ToHttpEndpoint()`, and any other `http` URI failed with
`Unknown Transport scheme 'http'`. `/_wolverine/invoke` now answers 415
instead of 500 for a content type with no registered serializer.
## Fixes
- **GH-5235** (#5239): Host shutdown now stops the durability metrics
sweeper and waits for any query it has in flight before stores are
disposed. Removing a durability agent also waits for its in-flight
query. A metrics query could previously outlive `IHost.StopAsync()` and
throw `ObjectDisposedException` against a disposed `NpgsqlDataSource`.
- **GH-5236** (#5240): A Kafka listener with a durable inbox no longer
crashes the process with a stack overflow when the inbox database is
unavailable. `DeferAsync()` used to call back into the receiver inline,
so the failed inbox write and the defer recursed. A deferred Kafka
envelope now goes back to the receiver on its own task after a short
pause, and only while the listener is running. After a stop or
inbox-recovery pause, its uncommitted offset brings it back. Because
Kafka has no native requeue, a `Requeue` error policy on a Kafka
endpoint now redelivers after about one second instead of immediately.
- **GH-5237** (#5241): A CloudEvents HTTP endpoint now fails a
single-envelope send that the receiver rejects. It used to count the
send as delivered, so a durable endpoint aimed at `/_wolverine/invoke`
could delete an outgoing envelope that was never received. HTTP
transport responses are now disposed.
## 6.55.1
## Fixes
- **GH-5230** (#5232): A database operation batch cancelled by
Wolverine's own shutdown is no longer wrapped in
`DatabaseBatchCommandException` and logged as an Error. Genuine
SQL/connection failures are still reported.
- **GH-5231** (#5233): An `HttpEndpoint` aimed at `/_wolverine/invoke`
in its default (buffered) mode now delivers by posting each envelope
individually, instead of receiving a 415 for every batch and retrying
forever. `/batch/{queue}` and `.SendInline()` endpoints are unchanged.
## 6.55.0
## Highlights
**WolverineFx.Bobcat is out of alpha, on Bobcat 1.0.0** (#5222,
#5229). Bobcat 1.0.0 shipped tonight, and `WolverineFx.Bobcat` and
`WolverineFx.Bobcat.EventModel` now publish as stable **6.55.0** instead
of `-alpha.1`. `WolverineFx.Bobcat.Http` stays `6.55.0-alpha.1` until
Alba 9 ships stable, because a stable package cannot depend on Alba
9.0.0-rc.2. A spec project that `write-specs` generates with no
`--wolverine-version` pins the stable `WolverineFx.Bobcat` (#5222,
#5223).
**`MessageCausedBy` names your handler** (#5224, #5225).
`IWolverineObserver.MessageCausedBy` reported the generated handler
class
(`Internal.Generated.WolverineHandlers.StartTripHandler939323516`). It
now reports the handler you wrote (`TripService.StartTripHandler`), the
same string as the `message.handler` span tag, resolved at codegen time.
Code generated by an earlier version keeps working through the old
overload.
**CosmosDB sagas start with a conditional create** (#5221, thanks
@thechucklingatom). A second start message for an existing saga id used
to silently overwrite the running saga. It now fails with the
`SagaConcurrencyException` that Marten, Azure Blob and S3 already raise,
so one `OnException<SagaConcurrencyException>()` policy covers every
provider.
## Fixes
* GH-5217: `ListeningAgent.StartAsync` is serialized, and deliveries are
deferred to a drained `BufferedReceiver` (#5228)
* GH-5218: an unresolvable tenant is treated as tenant-scoped on the
single-envelope inbox write (#5227)
* GH-5220: Azure Service Bus listeners join their receive loop before
disposing the `CancellationTokenSource` (#5226)
* GH-5215: reserved-word schema names are quoted in the remaining RDBMS
and PostgreSQL SQL (#5216, thanks @marc-RocketRez)
* GH-5213: a later pause supersedes an earlier pause's restart (#5214)
* GH-5209: the dead letter history table round-trips with no schema
drift (#5212)
## Dependencies
* JasperFx 2.96.0 (jasperfx#1083: an Observed vs Derived `Origin` no
longer raises a `SourceDisagreement`)
* Weasel 9.47.0 (#5219)
* Bobcat 1.0.0
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.54.0...V6.55.0
## 6.54.0
## Highlights
**Dead letter failure groups** (#5209, #5210). Wolverine now
fingerprints every message as it moves to dead letter storage, so dead
letters can be managed one failure group at a time:
* The default `IDeadLetterFingerprinter` hashes the exception type and
the first stack frame from application code. Register your own to
replace it.
* `DeadLetterEnvelopeQuery` gains `FailureGroup`,
`ExceptionMessageMatch` (Exact / Contains), `TenantId` and `Archived`.
* A new `SummarizeAllAsync` overload groups by failure group, message
type or endpoint, with first and last failure times.
* `ReplayMatchingAsync`, `DiscardMatchingAsync`, `ArchiveMatchingAsync`
and `RestoreMatchingAsync` report how many dead letters they affected.
* A message that fails again after a replay reports its `ReplayCount`,
`LastReplayedAt` and `FirstFailedAt`.
* Archived dead letters drop out of summaries, queries and replays until
restored, and can be expired separately with
`DurabilitySettings.ArchivedDeadLetterRetention`.
* `IDeadLetters.Capabilities` tells you what a store supports.
All changes are additive. The relational stores pick up seven nullable
columns on `wolverine_dead_letters`, an index and a new
`wolverine_dead_letter_history` table through the normal schema
migration. See the [dead letter storage
guide](https://wolverinefx.net/guide/durability/dead-letter-storage.html).
**Deduplication fixes** (#5205, #5206, #5208), and **Bobcat
1.0.0-rc.4** dependency pins (#5211). The `WolverineFx.Bobcat*`
packages remain alpha (`6.54.0-alpha.1`).
## What's Changed
* GH-5205, GH-5206: [DeduplicatedWithResponse] claim-in-transaction gaps
([WriteAggregate] T / [BoundaryModel], and repeats refused over
committed work) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5208
* Bobcat 1.0.0-rc.4 by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5211
* GH-5209 Dead letters: failure fingerprints, operation counts, replay
history, archive/restore and tenant scoping by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5210
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.53.0...V6.54.0
## 6.53.0
## What's Changed
* GH-5202: keep the ASB response queue identity stable during startup by
@DeandreT in https://github.com/JasperFx/wolverine/pull/5203
## New Contributors
* @DeandreT made their first contribution in
https://github.com/JasperFx/wolverine/pull/5203
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.52.0...V6.53.0
## 6.52.0
Wolverine 6.52.0 is a feature release. The headline items are
Aspire-style registration by connection-string name for every transport
and every message store, timer agents for short-cadence recurring
schedules, and the 6.x groundwork for Wolverine 7.0's
one-chain-per-handler model. There are no breaking API changes. Two
behavior changes are worth knowing about up front: recurring schedules
that fire more often than every 15 minutes now run on a timer agent (opt
out per schedule with `UseScheduledMessages()`), and outgoing envelopes
a transport cannot map are dead-lettered instead of being logged and
deleted.
Many thanks to everyone who reported issues and sent fixes: @uniquelau,
@johnkaram02, @erdtsieck, @ahmetvayhan and @pritamrao.
## Aspire parity: named connections
* Named-connection overloads for every transport (Aspire parity,
GH-5126) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5159
* Message storage by connection-string name for every RDBMS provider
(PostgreSQL, SQL Server, MySQL, SQLite, Oracle), `NpgsqlDataSource` /
`MySqlDataSource` from the container, named static tenants, a Named
Connection Strings table in `describe`, and docs that lead with the
named form (GH-5127, GH-5156) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5174
* Aspire samples moved to Aspire 13.6.3, added to `wolverine.slnx`, with
a ServiceDefaults project for traces, metrics and `/health` (GH-5124) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5182
## Messaging and error handling
* Timer agents for short-cadence recurring schedules: one agent per
schedule under `opts.Schedules.TimerThreshold` (15 minutes), same dedup
id as the scheduled path, pause and resume through
`IRecurringScheduleControl` (GH-5168) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5183
* `DoThenRetry(action, maxAttempts)` error policy that works both inline
and from a queue (GH-5068) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5179
* Unmappable outgoing envelopes are dead-lettered on Azure Service Bus,
SNS and Kafka instead of being reported as sent (GH-5055) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5185
* Durable outbox coalesces stores and deletes per tenant database, so
one slow tenant no longer holds back every tenant's sends (GH-5171) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5180
* 6.x prep for HandlerChain 1:1 in 7.0:
`MultipleHandlerBehavior.ClassicCombineIntoOneLogicalHandler` is
`[Obsolete]` (still the default), a startup warning for combined
handlers, opt-in `AssertNoCombinedHandlers()`, fixes for latent
`Separated` bugs, a `handler.type` OpenTelemetry tag on every chain, and
logical deduplication scoped per chain (GH-5133) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5184
## HTTP
* `[DeduplicatedWithResponse]` `ClaimInTransaction`, plus two Marten
commit-failure fixes (GH-5160, GH-5165, GH-5166) by @jeremydmiller with
@uniquelau in https://github.com/JasperFx/wolverine/pull/5169
* `WolverineHttpOptions.DefaultClaimInTransaction`, an application-wide
default (GH-5160) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5173
* A string response declares its UTF-8 byte count as `Content-Length`
(GH-5187) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5188
* Bind array and list query parameters whose `[FromQuery]` name is not a
C# identifier (GH-5189) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5190
## Operability and diagnostics
* `ServiceCapabilities` reads each store on its own and reports
`FailedSections`, so a failed section is distinguishable from an empty
one (GH-5162) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5175
* `AssignmentGrid.Agent.UnplacedReason` (`AwaitingCapacity`,
`NoCapableNode`, `Paused`, `TakeoverHold`, `Superseded`), a capacity log
line and an optional `NodeAcceptThreshold` (GH-5163) by @jeremydmiller
in https://github.com/JasperFx/wolverine/pull/5176 and
https://github.com/JasperFx/wolverine/pull/5186
* Header-equality filter on `DeadLetterEnvelopeQuery`, honored by bulk
replay and discard, across every dead letter store (GH-5164) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5181
* `db-list` / `db-dump` / `db-patch` discover the Oracle message store
(GH-5172) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5177
## Documentation
* The remaining serverless and Cosmos DB documentation (GH-5054) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5178
* Memory sizing for managed event subscription distribution at tenants x
projections scale: set `MaximumHopperSize` to about 2 x `BatchSize`
(GH-5170, follow-up JasperFx/jasperfx#1076) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5199
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.51.3...V6.52.0
Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.51.3...V6.56.0).
</details>
Updated
[WolverineFx.FluentValidation](http://github.com/jasperfx/wolverine)
from 6.51.3 to 6.56.0.
<details>
<summary>Release notes</summary>
_Sourced from [WolverineFx.FluentValidation's
releases](http://github.com/jasperfx/wolverine/releases)._
## 6.56.0
## New
- **GH-5238** (#5242): `opts.UseHttpTransport()` registers the HTTP
transport inside `UseWolverine()`. `http`/`https` URIs then resolve in
`ListenForMessagesFrom()`, `PublishMessage<T>().To(uri)`, and runtime
`EndpointFor()` / `ToDestination()`. Before this, the transport was only
registered by `ToHttpEndpoint()`, and any other `http` URI failed with
`Unknown Transport scheme 'http'`. `/_wolverine/invoke` now answers 415
instead of 500 for a content type with no registered serializer.
## Fixes
- **GH-5235** (#5239): Host shutdown now stops the durability metrics
sweeper and waits for any query it has in flight before stores are
disposed. Removing a durability agent also waits for its in-flight
query. A metrics query could previously outlive `IHost.StopAsync()` and
throw `ObjectDisposedException` against a disposed `NpgsqlDataSource`.
- **GH-5236** (#5240): A Kafka listener with a durable inbox no longer
crashes the process with a stack overflow when the inbox database is
unavailable. `DeferAsync()` used to call back into the receiver inline,
so the failed inbox write and the defer recursed. A deferred Kafka
envelope now goes back to the receiver on its own task after a short
pause, and only while the listener is running. After a stop or
inbox-recovery pause, its uncommitted offset brings it back. Because
Kafka has no native requeue, a `Requeue` error policy on a Kafka
endpoint now redelivers after about one second instead of immediately.
- **GH-5237** (#5241): A CloudEvents HTTP endpoint now fails a
single-envelope send that the receiver rejects. It used to count the
send as delivered, so a durable endpoint aimed at `/_wolverine/invoke`
could delete an outgoing envelope that was never received. HTTP
transport responses are now disposed.
## 6.55.1
## Fixes
- **GH-5230** (#5232): A database operation batch cancelled by
Wolverine's own shutdown is no longer wrapped in
`DatabaseBatchCommandException` and logged as an Error. Genuine
SQL/connection failures are still reported.
- **GH-5231** (#5233): An `HttpEndpoint` aimed at `/_wolverine/invoke`
in its default (buffered) mode now delivers by posting each envelope
individually, instead of receiving a 415 for every batch and retrying
forever. `/batch/{queue}` and `.SendInline()` endpoints are unchanged.
## 6.55.0
## Highlights
**WolverineFx.Bobcat is out of alpha, on Bobcat 1.0.0** (#5222,
#5229). Bobcat 1.0.0 shipped tonight, and `WolverineFx.Bobcat` and
`WolverineFx.Bobcat.EventModel` now publish as stable **6.55.0** instead
of `-alpha.1`. `WolverineFx.Bobcat.Http` stays `6.55.0-alpha.1` until
Alba 9 ships stable, because a stable package cannot depend on Alba
9.0.0-rc.2. A spec project that `write-specs` generates with no
`--wolverine-version` pins the stable `WolverineFx.Bobcat` (#5222,
#5223).
**`MessageCausedBy` names your handler** (#5224, #5225).
`IWolverineObserver.MessageCausedBy` reported the generated handler
class
(`Internal.Generated.WolverineHandlers.StartTripHandler939323516`). It
now reports the handler you wrote (`TripService.StartTripHandler`), the
same string as the `message.handler` span tag, resolved at codegen time.
Code generated by an earlier version keeps working through the old
overload.
**CosmosDB sagas start with a conditional create** (#5221, thanks
@thechucklingatom). A second start message for an existing saga id used
to silently overwrite the running saga. It now fails with the
`SagaConcurrencyException` that Marten, Azure Blob and S3 already raise,
so one `OnException<SagaConcurrencyException>()` policy covers every
provider.
## Fixes
* GH-5217: `ListeningAgent.StartAsync` is serialized, and deliveries are
deferred to a drained `BufferedReceiver` (#5228)
* GH-5218: an unresolvable tenant is treated as tenant-scoped on the
single-envelope inbox write (#5227)
* GH-5220: Azure Service Bus listeners join their receive loop before
disposing the `CancellationTokenSource` (#5226)
* GH-5215: reserved-word schema names are quoted in the remaining RDBMS
and PostgreSQL SQL (#5216, thanks @marc-RocketRez)
* GH-5213: a later pause supersedes an earlier pause's restart (#5214)
* GH-5209: the dead letter history table round-trips with no schema
drift (#5212)
## Dependencies
* JasperFx 2.96.0 (jasperfx#1083: an Observed vs Derived `Origin` no
longer raises a `SourceDisagreement`)
* Weasel 9.47.0 (#5219)
* Bobcat 1.0.0
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.54.0...V6.55.0
## 6.54.0
## Highlights
**Dead letter failure groups** (#5209, #5210). Wolverine now
fingerprints every message as it moves to dead letter storage, so dead
letters can be managed one failure group at a time:
* The default `IDeadLetterFingerprinter` hashes the exception type and
the first stack frame from application code. Register your own to
replace it.
* `DeadLetterEnvelopeQuery` gains `FailureGroup`,
`ExceptionMessageMatch` (Exact / Contains), `TenantId` and `Archived`.
* A new `SummarizeAllAsync` overload groups by failure group, message
type or endpoint, with first and last failure times.
* `ReplayMatchingAsync`, `DiscardMatchingAsync`, `ArchiveMatchingAsync`
and `RestoreMatchingAsync` report how many dead letters they affected.
* A message that fails again after a replay reports its `ReplayCount`,
`LastReplayedAt` and `FirstFailedAt`.
* Archived dead letters drop out of summaries, queries and replays until
restored, and can be expired separately with
`DurabilitySettings.ArchivedDeadLetterRetention`.
* `IDeadLetters.Capabilities` tells you what a store supports.
All changes are additive. The relational stores pick up seven nullable
columns on `wolverine_dead_letters`, an index and a new
`wolverine_dead_letter_history` table through the normal schema
migration. See the [dead letter storage
guide](https://wolverinefx.net/guide/durability/dead-letter-storage.html).
**Deduplication fixes** (#5205, #5206, #5208), and **Bobcat
1.0.0-rc.4** dependency pins (#5211). The `WolverineFx.Bobcat*`
packages remain alpha (`6.54.0-alpha.1`).
## What's Changed
* GH-5205, GH-5206: [DeduplicatedWithResponse] claim-in-transaction gaps
([WriteAggregate] T / [BoundaryModel], and repeats refused over
committed work) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5208
* Bobcat 1.0.0-rc.4 by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5211
* GH-5209 Dead letters: failure fingerprints, operation counts, replay
history, archive/restore and tenant scoping by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5210
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.53.0...V6.54.0
## 6.53.0
## What's Changed
* GH-5202: keep the ASB response queue identity stable during startup by
@DeandreT in https://github.com/JasperFx/wolverine/pull/5203
## New Contributors
* @DeandreT made their first contribution in
https://github.com/JasperFx/wolverine/pull/5203
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.52.0...V6.53.0
## 6.52.0
Wolverine 6.52.0 is a feature release. The headline items are
Aspire-style registration by connection-string name for every transport
and every message store, timer agents for short-cadence recurring
schedules, and the 6.x groundwork for Wolverine 7.0's
one-chain-per-handler model. There are no breaking API changes. Two
behavior changes are worth knowing about up front: recurring schedules
that fire more often than every 15 minutes now run on a timer agent (opt
out per schedule with `UseScheduledMessages()`), and outgoing envelopes
a transport cannot map are dead-lettered instead of being logged and
deleted.
Many thanks to everyone who reported issues and sent fixes: @uniquelau,
@johnkaram02, @erdtsieck, @ahmetvayhan and @pritamrao.
## Aspire parity: named connections
* Named-connection overloads for every transport (Aspire parity,
GH-5126) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5159
* Message storage by connection-string name for every RDBMS provider
(PostgreSQL, SQL Server, MySQL, SQLite, Oracle), `NpgsqlDataSource` /
`MySqlDataSource` from the container, named static tenants, a Named
Connection Strings table in `describe`, and docs that lead with the
named form (GH-5127, GH-5156) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5174
* Aspire samples moved to Aspire 13.6.3, added to `wolverine.slnx`, with
a ServiceDefaults project for traces, metrics and `/health` (GH-5124) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5182
## Messaging and error handling
* Timer agents for short-cadence recurring schedules: one agent per
schedule under `opts.Schedules.TimerThreshold` (15 minutes), same dedup
id as the scheduled path, pause and resume through
`IRecurringScheduleControl` (GH-5168) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5183
* `DoThenRetry(action, maxAttempts)` error policy that works both inline
and from a queue (GH-5068) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5179
* Unmappable outgoing envelopes are dead-lettered on Azure Service Bus,
SNS and Kafka instead of being reported as sent (GH-5055) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5185
* Durable outbox coalesces stores and deletes per tenant database, so
one slow tenant no longer holds back every tenant's sends (GH-5171) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5180
* 6.x prep for HandlerChain 1:1 in 7.0:
`MultipleHandlerBehavior.ClassicCombineIntoOneLogicalHandler` is
`[Obsolete]` (still the default), a startup warning for combined
handlers, opt-in `AssertNoCombinedHandlers()`, fixes for latent
`Separated` bugs, a `handler.type` OpenTelemetry tag on every chain, and
logical deduplication scoped per chain (GH-5133) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5184
## HTTP
* `[DeduplicatedWithResponse]` `ClaimInTransaction`, plus two Marten
commit-failure fixes (GH-5160, GH-5165, GH-5166) by @jeremydmiller with
@uniquelau in https://github.com/JasperFx/wolverine/pull/5169
* `WolverineHttpOptions.DefaultClaimInTransaction`, an application-wide
default (GH-5160) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5173
* A string response declares its UTF-8 byte count as `Content-Length`
(GH-5187) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5188
* Bind array and list query parameters whose `[FromQuery]` name is not a
C# identifier (GH-5189) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5190
## Operability and diagnostics
* `ServiceCapabilities` reads each store on its own and reports
`FailedSections`, so a failed section is distinguishable from an empty
one (GH-5162) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5175
* `AssignmentGrid.Agent.UnplacedReason` (`AwaitingCapacity`,
`NoCapableNode`, `Paused`, `TakeoverHold`, `Superseded`), a capacity log
line and an optional `NodeAcceptThreshold` (GH-5163) by @jeremydmiller
in https://github.com/JasperFx/wolverine/pull/5176 and
https://github.com/JasperFx/wolverine/pull/5186
* Header-equality filter on `DeadLetterEnvelopeQuery`, honored by bulk
replay and discard, across every dead letter store (GH-5164) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5181
* `db-list` / `db-dump` / `db-patch` discover the Oracle message store
(GH-5172) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5177
## Documentation
* The remaining serverless and Cosmos DB documentation (GH-5054) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5178
* Memory sizing for managed event subscription distribution at tenants x
projections scale: set `MaximumHopperSize` to about 2 x `BatchSize`
(GH-5170, follow-up JasperFx/jasperfx#1076) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5199
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.51.3...V6.52.0
Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.51.3...V6.56.0).
</details>
Updated
[WolverineFx.RuntimeCompilation](http://github.com/jasperfx/wolverine)
from 6.51.3 to 6.56.0.
<details>
<summary>Release notes</summary>
_Sourced from [WolverineFx.RuntimeCompilation's
releases](http://github.com/jasperfx/wolverine/releases)._
## 6.56.0
## New
- **GH-5238** (#5242): `opts.UseHttpTransport()` registers the HTTP
transport inside `UseWolverine()`. `http`/`https` URIs then resolve in
`ListenForMessagesFrom()`, `PublishMessage<T>().To(uri)`, and runtime
`EndpointFor()` / `ToDestination()`. Before this, the transport was only
registered by `ToHttpEndpoint()`, and any other `http` URI failed with
`Unknown Transport scheme 'http'`. `/_wolverine/invoke` now answers 415
instead of 500 for a content type with no registered serializer.
## Fixes
- **GH-5235** (#5239): Host shutdown now stops the durability metrics
sweeper and waits for any query it has in flight before stores are
disposed. Removing a durability agent also waits for its in-flight
query. A metrics query could previously outlive `IHost.StopAsync()` and
throw `ObjectDisposedException` against a disposed `NpgsqlDataSource`.
- **GH-5236** (#5240): A Kafka listener with a durable inbox no longer
crashes the process with a stack overflow when the inbox database is
unavailable. `DeferAsync()` used to call back into the receiver inline,
so the failed inbox write and the defer recursed. A deferred Kafka
envelope now goes back to the receiver on its own task after a short
pause, and only while the listener is running. After a stop or
inbox-recovery pause, its uncommitted offset brings it back. Because
Kafka has no native requeue, a `Requeue` error policy on a Kafka
endpoint now redelivers after about one second instead of immediately.
- **GH-5237** (#5241): A CloudEvents HTTP endpoint now fails a
single-envelope send that the receiver rejects. It used to count the
send as delivered, so a durable endpoint aimed at `/_wolverine/invoke`
could delete an outgoing envelope that was never received. HTTP
transport responses are now disposed.
## 6.55.1
## Fixes
- **GH-5230** (#5232): A database operation batch cancelled by
Wolverine's own shutdown is no longer wrapped in
`DatabaseBatchCommandException` and logged as an Error. Genuine
SQL/connection failures are still reported.
- **GH-5231** (#5233): An `HttpEndpoint` aimed at `/_wolverine/invoke`
in its default (buffered) mode now delivers by posting each envelope
individually, instead of receiving a 415 for every batch and retrying
forever. `/batch/{queue}` and `.SendInline()` endpoints are unchanged.
## 6.55.0
## Highlights
**WolverineFx.Bobcat is out of alpha, on Bobcat 1.0.0** (#5222,
#5229). Bobcat 1.0.0 shipped tonight, and `WolverineFx.Bobcat` and
`WolverineFx.Bobcat.EventModel` now publish as stable **6.55.0** instead
of `-alpha.1`. `WolverineFx.Bobcat.Http` stays `6.55.0-alpha.1` until
Alba 9 ships stable, because a stable package cannot depend on Alba
9.0.0-rc.2. A spec project that `write-specs` generates with no
`--wolverine-version` pins the stable `WolverineFx.Bobcat` (#5222,
#5223).
**`MessageCausedBy` names your handler** (#5224, #5225).
`IWolverineObserver.MessageCausedBy` reported the generated handler
class
(`Internal.Generated.WolverineHandlers.StartTripHandler939323516`). It
now reports the handler you wrote (`TripService.StartTripHandler`), the
same string as the `message.handler` span tag, resolved at codegen time.
Code generated by an earlier version keeps working through the old
overload.
**CosmosDB sagas start with a conditional create** (#5221, thanks
@thechucklingatom). A second start message for an existing saga id used
to silently overwrite the running saga. It now fails with the
`SagaConcurrencyException` that Marten, Azure Blob and S3 already raise,
so one `OnException<SagaConcurrencyException>()` policy covers every
provider.
## Fixes
* GH-5217: `ListeningAgent.StartAsync` is serialized, and deliveries are
deferred to a drained `BufferedReceiver` (#5228)
* GH-5218: an unresolvable tenant is treated as tenant-scoped on the
single-envelope inbox write (#5227)
* GH-5220: Azure Service Bus listeners join their receive loop before
disposing the `CancellationTokenSource` (#5226)
* GH-5215: reserved-word schema names are quoted in the remaining RDBMS
and PostgreSQL SQL (#5216, thanks @marc-RocketRez)
* GH-5213: a later pause supersedes an earlier pause's restart (#5214)
* GH-5209: the dead letter history table round-trips with no schema
drift (#5212)
## Dependencies
* JasperFx 2.96.0 (jasperfx#1083: an Observed vs Derived `Origin` no
longer raises a `SourceDisagreement`)
* Weasel 9.47.0 (#5219)
* Bobcat 1.0.0
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.54.0...V6.55.0
## 6.54.0
## Highlights
**Dead letter failure groups** (#5209, #5210). Wolverine now
fingerprints every message as it moves to dead letter storage, so dead
letters can be managed one failure group at a time:
* The default `IDeadLetterFingerprinter` hashes the exception type and
the first stack frame from application code. Register your own to
replace it.
* `DeadLetterEnvelopeQuery` gains `FailureGroup`,
`ExceptionMessageMatch` (Exact / Contains), `TenantId` and `Archived`.
* A new `SummarizeAllAsync` overload groups by failure group, message
type or endpoint, with first and last failure times.
* `ReplayMatchingAsync`, `DiscardMatchingAsync`, `ArchiveMatchingAsync`
and `RestoreMatchingAsync` report how many dead letters they affected.
* A message that fails again after a replay reports its `ReplayCount`,
`LastReplayedAt` and `FirstFailedAt`.
* Archived dead letters drop out of summaries, queries and replays until
restored, and can be expired separately with
`DurabilitySettings.ArchivedDeadLetterRetention`.
* `IDeadLetters.Capabilities` tells you what a store supports.
All changes are additive. The relational stores pick up seven nullable
columns on `wolverine_dead_letters`, an index and a new
`wolverine_dead_letter_history` table through the normal schema
migration. See the [dead letter storage
guide](https://wolverinefx.net/guide/durability/dead-letter-storage.html).
**Deduplication fixes** (#5205, #5206, #5208), and **Bobcat
1.0.0-rc.4** dependency pins (#5211). The `WolverineFx.Bobcat*`
packages remain alpha (`6.54.0-alpha.1`).
## What's Changed
* GH-5205, GH-5206: [DeduplicatedWithResponse] claim-in-transaction gaps
([WriteAggregate] T / [BoundaryModel], and repeats refused over
committed work) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5208
* Bobcat 1.0.0-rc.4 by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5211
* GH-5209 Dead letters: failure fingerprints, operation counts, replay
history, archive/restore and tenant scoping by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5210
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.53.0...V6.54.0
## 6.53.0
## What's Changed
* GH-5202: keep the ASB response queue identity stable during startup by
@DeandreT in https://github.com/JasperFx/wolverine/pull/5203
## New Contributors
* @DeandreT made their first contribution in
https://github.com/JasperFx/wolverine/pull/5203
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.52.0...V6.53.0
## 6.52.0
Wolverine 6.52.0 is a feature release. The headline items are
Aspire-style registration by connection-string name for every transport
and every message store, timer agents for short-cadence recurring
schedules, and the 6.x groundwork for Wolverine 7.0's
one-chain-per-handler model. There are no breaking API changes. Two
behavior changes are worth knowing about up front: recurring schedules
that fire more often than every 15 minutes now run on a timer agent (opt
out per schedule with `UseScheduledMessages()`), and outgoing envelopes
a transport cannot map are dead-lettered instead of being logged and
deleted.
Many thanks to everyone who reported issues and sent fixes: @uniquelau,
@johnkaram02, @erdtsieck, @ahmetvayhan and @pritamrao.
## Aspire parity: named connections
* Named-connection overloads for every transport (Aspire parity,
GH-5126) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5159
* Message storage by connection-string name for every RDBMS provider
(PostgreSQL, SQL Server, MySQL, SQLite, Oracle), `NpgsqlDataSource` /
`MySqlDataSource` from the container, named static tenants, a Named
Connection Strings table in `describe`, and docs that lead with the
named form (GH-5127, GH-5156) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5174
* Aspire samples moved to Aspire 13.6.3, added to `wolverine.slnx`, with
a ServiceDefaults project for traces, metrics and `/health` (GH-5124) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5182
## Messaging and error handling
* Timer agents for short-cadence recurring schedules: one agent per
schedule under `opts.Schedules.TimerThreshold` (15 minutes), same dedup
id as the scheduled path, pause and resume through
`IRecurringScheduleControl` (GH-5168) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5183
* `DoThenRetry(action, maxAttempts)` error policy that works both inline
and from a queue (GH-5068) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5179
* Unmappable outgoing envelopes are dead-lettered on Azure Service Bus,
SNS and Kafka instead of being reported as sent (GH-5055) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5185
* Durable outbox coalesces stores and deletes per tenant database, so
one slow tenant no longer holds back every tenant's sends (GH-5171) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5180
* 6.x prep for HandlerChain 1:1 in 7.0:
`MultipleHandlerBehavior.ClassicCombineIntoOneLogicalHandler` is
`[Obsolete]` (still the default), a startup warning for combined
handlers, opt-in `AssertNoCombinedHandlers()`, fixes for latent
`Separated` bugs, a `handler.type` OpenTelemetry tag on every chain, and
logical deduplication scoped per chain (GH-5133) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5184
## HTTP
* `[DeduplicatedWithResponse]` `ClaimInTransaction`, plus two Marten
commit-failure fixes (GH-5160, GH-5165, GH-5166) by @jeremydmiller with
@uniquelau in https://github.com/JasperFx/wolverine/pull/5169
* `WolverineHttpOptions.DefaultClaimInTransaction`, an application-wide
default (GH-5160) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5173
* A string response declares its UTF-8 byte count as `Content-Length`
(GH-5187) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5188
* Bind array and list query parameters whose `[FromQuery]` name is not a
C# identifier (GH-5189) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5190
## Operability and diagnostics
* `ServiceCapabilities` reads each store on its own and reports
`FailedSections`, so a failed section is distinguishable from an empty
one (GH-5162) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5175
* `AssignmentGrid.Agent.UnplacedReason` (`AwaitingCapacity`,
`NoCapableNode`, `Paused`, `TakeoverHold`, `Superseded`), a capacity log
line and an optional `NodeAcceptThreshold` (GH-5163) by @jeremydmiller
in https://github.com/JasperFx/wolverine/pull/5176 and
https://github.com/JasperFx/wolverine/pull/5186
* Header-equality filter on `DeadLetterEnvelopeQuery`, honored by bulk
replay and discard, across every dead letter store (GH-5164) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5181
* `db-list` / `db-dump` / `db-patch` discover the Oracle message store
(GH-5172) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5177
## Documentation
* The remaining serverless and Cosmos DB documentation (GH-5054) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5178
* Memory sizing for managed event subscription distribution at tenants x
projections scale: set `MaximumHopperSize` to about 2 x `BatchSize`
(GH-5170, follow-up JasperFx/jasperfx#1076) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5199
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.51.3...V6.52.0
Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.51.3...V6.56.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 versions of platform and application components, along with
testing tools. Other package settings remain unchanged. These updates do
not add or remove user-facing features.
<!-- 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>
This was referenced Nov 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #5205. Closes #5206. Also fixes the same refusal race for HTTP
[Deduplicated], and covers all of it on an ancillary store.GH-5205:
[WriteAggregate] T(and[BoundaryModel]) chains find their store's transactionThe first commit is @uniquelau's fix from
lg/fix/claim-in-transaction-write-aggregate, cherry-picked with authorship kept.GetPersistenceProvidersfalls back to the store the aggregate handler workflow applied when no provider'sCanApplyclaims the chain.AutoApplyTransactions,[Transactional]andCanApplyare unchanged.Follow-ups from a closer look:
[DcbModel]/[BoundaryModel]had the same gap. They apply their store's transaction themselves but record noAggregateHandling.BoundaryHandlingTagnow carries the provider, andAggregateHandling.TransactionOwnerconsults it too. New test:MartenTests.Dcb.deduplication_rides_a_boundary_model_transaction.CanApplyignores the workflow just as Marten's does, so a[Deduplicated]handler with[WriteAggregate] Tnow rides their transaction too. Pinned by new codegen and replay tests inPolecatTestsandFisherTests.ClaimInTransactionfor[DeduplicatedWithResponse]is still Marten-only, as documented.[WriteAggregate] Ton the default claim-and-release path. New test: a failure insideSaveChangesAsyncafter the database commit keeps the claim, so the retry gets 409 rather than running the work again.[WriteAggregate]names a new provider whether or not its store is integrated, so withAddMarten(...)minusIntegrateWithWolverine()andPersistMessagesWithPostgresql, a[Deduplicated]handler with[WriteAggregate] Tfailed codegen onIMartenDeduplicator. The fallback now answers with the registered provider of that store's type, or none, so that host keeps claim-and-release as onmain. New test:MartenTests.deduplication_without_marten_integration, which fails with the earlier fallback. That commit credits @uniquelau as co-author.GH-????placeholders now say [DeduplicatedWithResponse] on Marten: ClaimInTransaction falls back to claim-and-release when [WriteAggregate] binds the aggregate rather than IEventStream<T> #5205.All three new Marten tests fail with the fallback removed.
GH-5206: a concurrent repeat refused over the first request's committed work is answered from its claim
On the
ClaimInTransactionpath, a repeat could miss the claim at the front of the chain and then read the first request's committed work. IfLoad/Before/Validaterefused it over that work (a 409 admission rule, a 404, a throw), it never reached the commit the race frame wraps. The client got the refusal for a request that had succeeded.The claim commits in the same transaction as that work, so a second lookup finds it. This PR adds that lookup:
WatchForDeduplicatedRefusal. That holds the response back in a buffer until the commit, which releases it.BuildEndpointwraps every[DeduplicatedWithResponse]endpoint. When the request fails before its commit (status ≥ 400, or a throw), the wrapper looks the claim up again. If it finds one, it answers from it: the stored response, or 422 for different bytes. Otherwise the endpoint's own answer goes out unchanged.AnswerDeduplicatedResponseRaceFrame. One that finds the request's own claim committed (a listener throwing after the database commit) is still rethrown.MemoryStreamper keyed request on these endpoints until commit. One extra read, only on a failure before the commit.The "Claiming inside the transaction" docs bullet is updated to match. Please check its wording, since it reverses what the docs said before.
New tests in
deduplicated_with_response_on_marten, with a barrier that lets both requests miss the claim deterministically:The same race on HTTP
[Deduplicated]An HTTP
[Deduplicated]endpoint whose claim rides its own transaction (Marten, Polecat, Fisher) checks optimistically up front too. A concurrent repeat refused over the first request's committed work therefore answered with its own refusal: a 409 from the rule, a 404, or a 500 from a throw. It should get the endpoint's duplicate answer.[DeduplicatedWithResponse], refuse as a duplicate for[Deduplicated].TransactionalDeduplicationgains an optionalCommitframe, which the three providers set. The HTTP chain armsWatchForDuplicateRefusalafter the queued claim, and callsMarkDeduplicationCommitStartedjust before the commit. From that point the existing commit-race wrapper decides, as before.IDeduplicationClaimLookup. That's a new optional interface, so nothing breaks; the RDBMS dedup store implements it with aselect count(*)keyed the same way as its other statements. A match is answered with the same status and problem detail the up-front check gives.IMessageDeduplicator, or a store without the lookup, keeps the old behaviour. EF Core claims inside its transaction before the handler runs, so a concurrent repeat already waits on the first one's row.durability/idempotency.md, next to the commit-race paragraph.Tests:
deduplication_on_a_marten_transaction: the race with ProblemDetails and with a throw, and a refusal with no claim. Both race tests fail with the watch disabled.SaveChangesAsync.IsClaimedAsyncin both comparison modes, run against PostgreSQL, SQL Server, SQLite and MySQL.Ancillary store
deduplication_races_on_an_ancillary_storeuses a[MartenStore(typeof(INkThingStore))]host with the ancillary store in its own schema:ClaimInTransactionwith[WriteAggregate]of the aggregate itself. A concurrent race (the loser fails on the stream version and is answered from the winner's ancillary claim), then a replay. The claim is in the ancillary store, not the main one, andQueueClaimis generated.[DeduplicatedWithResponse]: the refusal recheck, with ProblemDetails and with a throw.[Deduplicated]: the refusal recheck, with the claim counted in the ancillary schema and absent from the main one.The four recheck tests fail when the lookup is pointed at the main store.
Verification
dotnet build wolverine.slnx -c Release -f net9.0: 0 errors, 0 warnings.Not covered
select count(*)through the same store, but I didn't run it locally.🤖 Generated with Claude Code
https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U