Skip to content

GH-5160, GH-5165, GH-5166: [DeduplicatedWithResponse] ClaimInTransaction, plus two Marten commit-failure fixes - #5169

Merged
jeremydmiller merged 5 commits into
mainfrom
gh-5160-5165-5166-deduplication
Oct 23, 2026
Merged

jeremydmiller merged 5 commits into
mainfrom
gh-5160-5165-5166-deduplication

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #5160. Closes #5165. Closes #5166.

This PR fixes all three open issues from @uniquelau. It builds on their PR #5161, and its three commits for #5160 are included unchanged, under their authorship. This PR supersedes #5161. Laurence Gillian is a co-author of the commit for #5165 and #5166.

Merge with --merge, not --squash, so Laurence keeps authorship of the #5160 commits.

#5160: [DeduplicatedWithResponse(ClaimInTransaction = true)]

This is #5161 as written: commits 282abccd1, 2cfee6bed and 148fd57b2, with main merged in. #5161's description covers the design. In short:

  • It's opt-in, and only for Marten endpoints that commit through Wolverine's transactional middleware.
  • The claim and its response are written in the endpoint's own transaction.
  • A concurrent loser is answered from the winner's claim.

#5161's open question (should this be automatic, as [Deduplicated] is) is still open; this PR keeps it opt-in.

#5165: a failure after Marten's database commit released the claim

Marten runs session listeners' AfterCommitAsync inside SaveChangesAsync, after the database commit. Wolverine's outbox flush is one of those listeners. On the default claim-and-release path, a throw there left the work committed and released the claim, so the retry ran the endpoint again.

  • DatabaseCommitMarker (new, Wolverine.Marten) is a session listener inserted at index 0 of the session's listeners. Marten runs listeners in list order, so it records the commit before the outbox flush or any user listener can throw.
  • IDatabaseCommitHookProvider (new, internal) is a sibling seam to ITransactionalReplayableDeduplicationProvider. Only Marten implements it. The HTTP chain uses it to enlist the marker, with a callback that sets HttpHandler.MarkDeduplicatedDatabaseCommitted.
  • ReleaseUnansweredDeduplicatedResponseFrame gets a catch { KeepDeduplicatedClaimIfCommitted(ctx); throw; }, but only when the hook applies. On a failure over a committed database transaction, the claim is kept, unanswered. Repeats get 409 until it expires instead of running the work again. That is the same outcome as a failed response record today, and the fix [DeduplicatedWithResponse] on Marten: a failure inside SaveChangesAsync after the database commit releases the claim, so the retry runs the work again #5165 suggested.
  • A request that completes normally keeps its current rule. A 4xx answer or a null resource still gives the claim back.
  • Endpoints on other stores generate the same code as before.

#5166: a concurrent [WriteAggregate] loser under [Deduplicated] got a 500

RefuseDuplicateClaimAtCommitFrame only matches a unique violation on the deduplication table. A concurrent [WriteAggregate] loser fails on the stream version before it reaches the claim's INSERT, so it escaped as a 500.

  • Marten now uses its own commit-race wrapper, RefuseDuplicateMartenClaimAtCommitFrame. It keeps Deduplication should ride the native transaction on Marten, Polecat and Fisher — batched optimistic check, no compensating release #4505's classifier catch, and adds a second catch that runs only while the commit is in progress: CommitStarted && !Committed, from the same DatabaseCommitMarker.
    • In that window, it checks on a fresh query session (MartenDeduplicationFailures.IsClaimedAfterFailedCommitAsync) whether the key is now claimed.
    • If it is, the request gets the endpoint's configured refusal: 409 on HTTP, a discard on a handler.
    • If not, the original failure is rethrown unchanged.
  • Failures in the endpoint itself, or after the database commit, are never looked up, so a request's own committed claim can't be mistaken for a concurrent win.
  • No public interface changes. IMartenDeduplicator is untouched; the lookup is a static on MartenDeduplicationFailures.

Polecat probably has the same gap as #5166 (it uses the same core frame), but it is not touched here.

Tests

New tests:

Mutation checks, each applied and then reverted:

Run locally:

  • dotnet build wolverine.slnx -c Release -f net9.0: 0 errors
  • Wolverine.Http.Tests, FullyQualifiedName~eduplicat: 101/101
  • MartenTests, FullyQualifiedName~eduplicat: 17/17
  • Wolverine.Http.FSharpTests, including the coverage ratchet: 11/11

Docs: docs/guide/http/deduplication.md (claim-and-release on Marten) and docs/guide/durability/idempotency.md (the [WriteAggregate] race).

🤖 Generated with Claude Code

https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U

uniquelau and others added 5 commits October 23, 2026 13:49
… the claim and its response inside the endpoint's transaction

An opt-in ClaimInTransaction on [DeduplicatedWithResponse]. On an endpoint
that commits through a Marten session with Wolverine's transactional
middleware, on a single-database store with the PostgreSQL message store in
the same database, the claim and its stored response are written as one row,
queued onto the session before SaveChangesAsync. A rollback, or a crash
before the commit, leaves no claim; a commit leaves it answered. There is no
release and no record left to fail afterwards. The default is unchanged.

- Before the endpoint runs, the chain answers from any committed claim.
- A commit that fails is answered from a claim a concurrent request committed
  meanwhile, on the claim's primary key or on the endpoint's own work (a
  stream version conflict); otherwise it is rethrown. A failure after this
  request's own database commit (the outbox flush runs inside
  SaveChangesAsync on Marten) is told apart by the claim token and rethrown.
- An endpoint that asks for it but cannot take it keeps claim-and-release
  and logs a warning, including one whose response is produced after the
  commit (UpdatedAggregate). The same-database check is repeated at runtime
  for pre-generated code.
- The provider seam (ITransactionalReplayableDeduplicationProvider) and the
  ownership query are internal; no existing interface changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxXDGaqdYTzZ54ZmdzUwH4
…lure after the database commit

From the Copilot review on #5161, both reproduced before fixing.

- MartenDeduplicatedResponses cached the claims table by the session's
  NpgsqlDataSource alone. An application that hands one data source to its
  main and ancillary stores (StoreOptions.Connection(dataSource)) got the
  first store's table for both: the ancillary claim landed where it is
  never read, and the repeat ran the endpoint again and failed at commit.
  The cache is now keyed by data source and store marker.
- A Marten session listener that throws from AfterCommitAsync fails
  SaveChangesAsync after the database commit. The exception now has a test:
  it reaches the caller, the answered claim and the work stay committed
  once, and the retry replays without running the endpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxXDGaqdYTzZ54ZmdzUwH4
…e, as [Deduplicated] does (GH-4505)

Aligns the in-transaction path with [Deduplicated] on Marten.

- The existing-claim lookup now goes through the Marten session, supplied by
  the provider as TransactionalDeduplication.Check is. It is a query handler
  returning the stored claim, and it joins the chain's batched query when
  the key is required, so a [WriteAggregate] endpoint pays one round trip
  for the lookup and FetchForWriting together. An optional key stays
  standalone, so a request without one costs no round trip.
- The runtime seam is now IMartenDeduplicatedResponses, a public interface
  with an internal implementation, as IMartenDeduplicator is.
- HTTP computes the hashed storage id and answers from the provider's
  lookup; the separate-connection lookup frame is gone.

The race handling deliberately stays different. A probe of [Deduplicated]
showed that its concurrent [WriteAggregate] loser fails on the stream
version before reaching the claim, and gets a 500 rather than the 409.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxXDGaqdYTzZ54ZmdzUwH4
…e SaveChangesAsync can still throw

Marten runs its session listeners' AfterCommitAsync inside SaveChangesAsync, after the
database commit, Wolverine's outbox flush among them. From outside the call a failure
there looked the same as a failed commit. A DatabaseCommitMarker listener, inserted at
the front of the session's listeners, now records when the commit started and when the
database commit succeeded.

GH-5165: on a claim-and-release [DeduplicatedWithResponse] Marten endpoint, a failure
after the database commit released the claim, so the client's retry ran the committed
work again. The chain now hears the commit (internal IDatabaseCommitHookProvider seam,
Marten only) and keeps the claim unanswered on a failure over committed work: repeats
get 409 until it expires, the same as a failed record. A request that completes
normally keeps the existing rule.

GH-5166: on [Deduplicated] with Marten (GH-4505), a concurrent [WriteAggregate] loser
fails on the stream version before reaching the claim's INSERT, so the unique-violation
classifier never matched and it escaped as a 500. A commit that fails for any other
reason is now checked on a fresh query session: if the key is claimed, it gets the same
refusal (409 / discard), otherwise the failure is rethrown unaltered. Failures before
the commit or after the database commit are never looked up.

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
@uniquelau

Copy link
Copy Markdown
Contributor

Thanks for picking these up, and for keeping the #5160 commits as they were. Two things for #5169, both with a follow-up I can send once it has merged.

1. On the ClaimInTransaction path, a version Marten assigns during the commit isn't in the replay.

The response is serialized just before SaveChangesAsync. Marten writes a document's Guid [Version] and its IRevisioned revision onto the object during the commit, so the first client gets the real values and every repeat gets the pre-commit ones. I ran this on #5169's head (a3d808f6c), with [DeduplicatedWithResponse(DeduplicationScope.User, ClaimInTransaction = true)] on an endpoint that Store()s the document and returns it:

Returned document First response Replay
Guid [Version] "version":"01a16935-d45f-…" "version":"00000000-0000-0000-0000-000000000000"
IRevisioned "version":1 "version":0

The #5161 description lists this as a trade-off, but I don't think it's one worth keeping: a client using that version for its next write would be handed a stale one on every retry. A probe of Marten's operation lifecycle showed the Guid version is set before the batch executes, but the numeric revision only after its command has run. So no single write inside the transaction can carry both.

The fix I'd send keeps the claim in the transaction and records the response again after the commit, as claim-and-release does today. It overwrites the stored response only when the commit changed it, using a token-guarded internal store call, so no public interface changes.

2. For #5165, an alternative that replays instead of refusing.

With DatabaseCommitMarker, a failure after the database commit keeps the claim unanswered, so repeats get 409 until it expires. That's the fix the issue suggested, and it's simpler.

I have a local branch that does it differently. On a Marten chain it queues the response record onto the session, the way FlushOutgoingMessagesOnCommit queues the inbox mark-handled UPDATE. The claim is answered if and only if the work commits, and the retry is replayed rather than refused. The default path then needs the same after-commit correction as point 1, which makes it more code than your marker.

The 409 is a sound answer, so I'm only raising the replay as an option. If you'd like it, I'll rebase it onto #5169 as a follow-up.

@uniquelau

Copy link
Copy Markdown
Contributor

A review of a3d808f6c. The listener ordering, a failed commit still releasing the claim, the fresh-session lookup (right store and table, ancillary included) and the separation from ClaimInTransaction all check out. Five things, each marked with how far I verified it:

1. #5166 under EventAppendMode.Rich: a loser that saves after the winner committed still gets a 500. Reproduced.
DatabaseCommitMarker marks the commit as started in BeforeSaveChangesAsync. Marten runs EventGraph.ProcessEventsAsync before that (DocumentSessionBase.SaveChanges.cs, line 37 against 58). Under Rich mode, StreamAction.PrepareEvents throws EventStreamUnexpectedMaxEventIdException in process, so the new catch's CommitStarted filter is false.
I ran it on this branch with the winner committing fully before the loser called SaveChangesAsync:

AppendMode Winner Loser Events committed
default (QuickWithServerTimestamps) 200 409 1
Quick 200 409 1
Rich 200 500 (EventStreamUnexpectedMaxEventIdException) 1

The default is fine. Rich mode is a common opt-in, though, and idempotency.md states the fix generally.
Suggested fix: mark "commit started" from the generated code, immediately before the SaveChangesAsync frame, rather than from the listener.

2. Marten runs Wolverine's own message batch before the listener loop. Ordering confirmed in Marten 9.51's source; not run.
_messageBatch.AfterCommitAsync (line 74) runs before the AfterCommitAsync loop (line 89). Under ServerlessWithOutbox, that batch is MessageContext.SendAllOutgoingMessagesInlineAsync, so a failing inline send there isn't seen by the marker.

Suggested fix: record "committed" from the earliest post-COMMIT hook Marten exposes, if there is a public one; otherwise note the gap.

3. A user's OnException handler sits inside the new catch. Ordering confirmed by reading the code; not run.
GetOrCreateTryCatchFinallyFrame inserts at Middleware[0] while policies apply, and the deduplication frames go to Middleware[0] later, in AssembleTypes. An OnException handler that turns the post-commit failure into a ProblemDetails therefore returns normally, and the finally (and OnStarting) see work-not-done and release the claim.
Suggested fix: have the release guards honour the committed flag too, at least for a 5xx.

4. EnlistDatabaseCommitHookFrame has no GenerateFSharpCode. Confirmed.
The other frames on a Marten claim-and-release [DeduplicatedWithResponse] chain all emit F#, so F# generation of such an endpoint now hits the default throw. FSharpCoverageRatchet only scans Wolverine and Wolverine.Http, so it can't catch this.

5. The marker counts every commit in the request, not just the middleware's. Plausible; not run.
The flags are never reset. An endpoint that calls SaveChangesAsync itself for an earlier write, followed by a middleware commit that fails in the database, keeps the claim unanswered. The client then gets 409s for the whole window, though the main work rolled back.
Suggested fix: enlist the marker just before the commit frame, or reset the flags in BeforeSaveChangesAsync.

@jeremydmiller
jeremydmiller merged commit f79581e into main Oct 23, 2026
48 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment