Skip to content

Bump the ef-core group with 2 updates - #191

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/api/ef-core-d0d0ed3abf
Open

Bump the ef-core group with 2 updates#191
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/api/ef-core-d0d0ed3abf

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 3, 2026

Copy link
Copy Markdown

Updated OpenIddict.EntityFrameworkCore from 7.6.0 to 7.6.1.

Release notes

Sourced from OpenIddict.EntityFrameworkCore's releases.

7.6.1

This release introduces the following changes:

  • The GitHub web provider was fixed to support the iss parameter now returned by GitHub. As part of this change, the issuer was also changed to https://github.com/login/oauth, which is the value now officially used by GitHub.

  • The ReplaceDefault*Entity() methods exposed by OpenIddictMongoDbBuilder now register the stores as singleton services instead of scoped services.

  • The client system integration was updated to always attach an IASWebAuthenticationPresentationContextProviding object to the ASWebAuthenticationSession on Mac Catalyst and macOS, matching the logic already used on iOS (thanks @​amirburbea for reporting this issue).

Commits viewable in compare view.

Updated WolverineFx.EntityFrameworkCore from 6.21.0 to 6.33.0.

Release notes

Sourced from WolverineFx.EntityFrameworkCore's releases.

6.33.0

The headline is a new package. WolverineFx.AI makes a one shot LLM call an ordinary Wolverine message: durable, outbox enrolled, retried by the same rules as everything else, and testable without a model anywhere in sight.

WolverineFx.AI (new package)

An LlmCallout is a message. Return one from a handler next to your storage action and it is enrolled in that handler's outbox, so a callout cannot fire for a transaction that did not commit and cannot be lost to a restart in between. The model's answer comes back as an ordinary cascading message, with an ordinary handler, an ordinary retry policy, and its own place in the correlation chain. (closes #​4227)

  • Spend guardrails as middleware on the callout queue. LlmBudget.MaximumPromptCharacters refuses a runaway prompt before your provider is ever called; MaximumTokensPerWindow refuses callouts once the node has burned its allowance. Both dead letter rather than retry, and so does an answer that cannot be parsed into the response type you asked for -- retrying either is the runaway spend the budget exists to stop.
  • A scripted IChatClient for testing. StubChatClient exercises a callout's whole round trip with no key, no network and no model.
  • Trim and AOT clean, guarded by a Wolverine.AI.AotSmoke project under TrimMode=full that CI runs. (closes #​4230)
  • Documentation for tuning it, new in this release: how to control parallelism against your provider, why the answer has its own queue with its own settings, and how to bring your own error handling on both sides.

The package references only the Microsoft.Extensions.AI abstractions, never a vendor SDK. The provider -- Anthropic, OpenAI, Azure, Ollama -- and any middleware over it stay your choice.

Fixes

  • A node no longer sweeps up its own in-flight stop as a wedged shard. An event-subscription agent could end up running on two nodes at once while wolverine_nodes credited only one, so nothing in the system could ever stop the extra copy. (closes #​4240)
  • Node record descriptions no longer overflow the column and fail the insert. An AssignmentChanged description carries an agent URI, a schema name and a destination node, which on a real cluster overran the description column and failed the whole AgentCommand batch behind it. MySQL was worst hit at VARCHAR(255). (closes #​4246)
  • DataAnnotations validation works with ServiceLocationPolicy.NotAllowed -- on HTTP endpoints and, now, on message handlers. Under the Wolverine 6 default this made the validation middleware unusable and threw at bootstrap. (closes #​4238)
  • A failed EF Core rollback no longer displaces the exception that caused it. (closes #​4239)
  • Wolverine parameter attributes work on gRPC before/after hooks -- [Entity], [All], [Queryable], [WriteAggregate] and the rest. (closes #​3935)
  • An application-wide default duplicate status code via opts.DefaultDuplicateStatusCode, and deduplication refusals now advertise their problem document in OpenAPI.
  • Concurrent IHost.StopAsync no longer tears the agents down twice.

Upgrade note

6.33.0 requires Weasel 9.30.0, and that raises the GH-4246 fix from "new databases only" to "existing ones too": the schema differ now compares character lengths, so a widened varchar is no longer invisible to it and an existing table is corrected in place by an ALTER TABLE ... MODIFY that keeps its rows.

Worth knowing before you upgrade: that comparison runs in both directions. Width drift that was previously invisible now generates ALTERs, and a model narrower than an existing column will emit a narrowing ALTER that can fail on real data. Sizes that are not character lengths -- a MySQL int(11) display width, a decimal precision, a datetime fsp -- are still ignored.

Dependencies

JasperFx 2.60.0, Marten 9.30.0, Polecat 5.21.1, Fisher 1.0.6, Weasel 9.30.0.

6.32.0

See CHANGELOG.md for the full entries.

New packages

WolverineFx.AmazonS3 and WolverineFx.AzureBlobStorage carry document and saga persistence plus the claim check store that used to ship separately. Registration is explicit per type — Store<T>() and Saga<T>() are separate calls and each refuses the other's type — and saga writes are guarded by conditional requests, surfacing as SagaConcurrencyException so one OnException<ConcurrencyException> policy still covers every store. (#​4160, originally #​4165 by Anne Erdtsieck.)

WolverineFx.ClaimCheck.AmazonS3 is deprecated. The namespace is unchanged, so migration is a package reference swap — but keeping both referenced produces ambiguous-type errors.

Redis document and saga persistence folds into the existing WolverineFx.Redis rather than a new package, with saga concurrency implemented as a Lua compare-and-swap.

Fixes

  • A shutting-down node no longer dead-letters work whose handler never ran (#​4213). Core, so every transport.
  • Scheduled promotion matches the whole message identity on SQLite, SQL Server, MySQL and Oracle rather than PostgreSQL alone (#​4216) — including a not-yet-due scheduled message being promoted and executed early.
  • A redelivered inbox row can be retired when its identity is already handled under EnableInboxPartitioning (#​4216); previously it could not be retired at all.
  • A listener whose broker entity was deleted underneath it now heals instead of retrying once a second forever (#​4215).
  • Terminal settle failures are classified on the retry block, closing a gap where two of four Azure Service Bus listeners had no classification at all (#​4012).
  • Scope priming no longer manufactures a Marten session for every handler that service-locates anything (#​4198). Requires JasperFx 2.58.0 or later.
  • A duplicated scheduled identity no longer wedges promotion on a partitioned PostgreSQL inbox (#​4202).
  • AddStopConditionIfNull accepts the null identity its signature declares (#​4161).

Diagnostics

  • NativeAck and partitioned listeners report ceilings, per-lane depth and duplicate-suppression counts (#​4199). BufferLimit is now null on the modes that never enforced it, with the broker's prefetch window reported as InFlightLimit.
  • MaximumBrokerRedeliveries is documented as the delivery count it actually is (#​4216). Behaviour unchanged.

Event model

  • A stream-appending handler's return value is reported as a reply rather than an emitted event (#​4204).
  • A generic message type's slice reads the way source spells it, which also stops two relays with different payloads colliding on one slice (#​4205).

Also

  • Explicit per-provider entity attributes, starting with [FromMarten] and [FromEfCore] (#​4214).
  • RabbitMQ documentation for AddResourceSetupOnStartup and AutoProvision (#​4223).

6.31.0

Logical message deduplication

Envelope.Id identifies one delivery. That is the right identity for "the broker handed me this twice" and the wrong one for "the operator clicked Rebuild twice" — those are different deliveries of the same intent, so each carries a different Envelope.Id and every one gets through.

6.31.0 promotes Envelope.DeduplicationId into a first-class logical id, with storage, enforcement, and a retention policy behind it.

opts.Durability.EnableMessageDeduplication = true;   // provisions wolverine_deduplication
opts.Durability.DeduplicationWindow = 24.Hours();    // this IS the guarantee

[Deduplicated]
public static void Handle(RebuildProjection command) { }

It is opt-in throughout — leaving it off means no schema change at all on upgrade. Storage is a separate wolverine_deduplication table rather than a column on the inbox, because under EnableInboxPartitioning the inbox is PARTITION BY LIST (status) and marking an envelope handled moves the row between partitions, which would let one logical id exist as both Incoming and Handled — silently, and only for users who enabled partitioning. Claiming is an INSERT that either succeeds or trips the primary key, never a SELECT-then-INSERT.

Refusals differ per chain type: a message handler discards and acks, HTTP returns 409 with ProblemDetails (configurable to 2xx where a replay is benign), gRPC returns AlreadyExists / InvalidArgument per AIP-193. Storage on PostgreSQL, SQL Server, MySQL and SQLite.

Deriving the id from the message

The publishing side does not have to remember DeliveryOptions.DeduplicationId at every call site. A message type declares its own logical identity once, the way it already declares a topic name with [Topic] or a saga id with [SagaIdentity]:

public record ArchiveInvoice([property: DeduplicationIdentity] string InvoiceNumber, DateOnly AsOf);

[DeduplicationIdentity(nameof(ReceiveShipment.ShipmentId))]   // a contract whose members you cannot decorate
public record ReceiveShipment(Guid ShipmentId, string Warehouse);

// or configured, for composed ids and generated message types
opts.MessageDeduplication.ByMessage<RebuildProjection>(x => $"{x.ProjectionName}|{x.OccurrenceUtc:O}");
opts.MessageDeduplication.ByMemberNamed("IdempotencyKey", "DeduplicationId");
opts.Policies.ForMessagesOfType<CreateOrder>().DeduplicateBy(x => $"{x.Sku}|{x.Quantity}");

These are IEnvelopeRule at the message type level, resolved once when the route is built rather than per message. An explicit DeliveryOptions.DeduplicationId always wins, then configured rules, then the attribute.

Fixes

  • Broker startup is bounded by a clock. A host starting against a dead broker took 21m38s to fail — long enough to look like a hang and to blow past any orchestrator's startup probe. (#​4116)
  • ListeningAgent sees past its receiver wrappers. ReceiverWithRules — installed by a bare endpoint-level MessageType or TenantId — is unconditionally an ILocalQueue, so a wrapped NativeAck or Inline receiver took the wrong branch and threw on the durability agent's re-entry path. The same blindness meant a terminally faulted receiver reported healthy forever on exactly the endpoints most likely to be non-trivially configured. (#​4188, #​4191)
  • A locally-owned shard that stopped with nothing to report is restarted. It was invisible to both recovery paths because each deferred to the other; the status was correct, the assignment was correct, and nothing joined the two. Reached through the console's Rebuild, which completed, acked success, and left the shard dead. (#​4193)
  • A NativeAck or Inline listener reports its real queue depth and last receipt instead of 0. (#​4186)
  • Event Model derivation stops claiming TriggerLabel, which was beating overlay declarations and minting a SourceDisagreement hotspot per labelled route; and a collection response now reads its element type instead of reporting an assembly-qualified CLR string as a canvas node. (#​4181, #​4182)
  • An agent command is never forwarded to the node it is already on. (#​4184)

Dependencies

  • JasperFx, JasperFx.Events and the two source generator packages to 2.57.2.

Full changelog: JasperFx/wolverine@V6.30.3...V6.31.0
... (truncated)

6.30.3

Patch release. Requires JasperFx 2.57.1, which ships the code-generation half of two of these fixes.

Several of these failed silently — a host that started clean, passed health checks, and did less than it appeared to. Worth a look if any of the shapes below match your application.

Code generation and service location

  • ServiceProviderSource.IsolatedAndScoped is now honored by Wolverine.HTTP (#​4171). An endpoint or middleware asking for an IServiceProvider always received httpContext.RequestServices, whatever you configured. Note the consequence: asking for an IServiceProvider in an endpoint is service location and now registers as such, so under ServiceLocationPolicy.NotAllowed those endpoints will throw where they previously slipped past the policy unnoticed. Message handlers have always behaved this way.

  • Scope priming now fires for every chain that service-locates, not only those naming an IServiceProvider (#​4171). If a chain reached service location solely through an opaque scoped/transient registration, its child scope was never primed — so a service-located IMessageContext, IMessageBus, or Marten IDocumentSession was a second, un-enrolled instance rather than the one the handler already owned. Handlers and HTTP endpoints are both covered now.

  • Lazy<T> dependencies resolve through their registration (#​4159). An open-generic registration such as TryAddScoped(typeof(Lazy<>), typeof(LazyResolver<>)) was ignored whenever the closed type was itself concrete, and new Lazy<IFoo>() was emitted instead. That compiles and can never work — the first .Value throws MissingMemberException for any service without a public parameterless constructor. Relatedly, AlwaysUseServiceLocationFor(typeof(Lazy<>)) accepted an open generic and then matched nothing; it now matches that generic's closed forms.

Sagas

  • ResequencerSaga advances LastSequence when a message is handled, not when it is published (#​4172). A replayed message could let a queue backlog walk past the ordering guard while it was still in flight, reordering the handled sequence.

  • An already-sequenced arrival is observable and overridable (#​4175). A message whose order the saga had already passed was handled again in silence. The new shouldHandleAlreadySequenced hook logs a warning by default — behavior is unchanged — and can be overridden to discard, raise a metric, or throw.

Startup

  • AutoCreate.None no longer pays for a full schema diff at startup (#​4166).

Full changelog: JasperFx/wolverine@V6.30.2...V6.30.3

6.30.2

This addresses an issue encountered by a JasperFx client hitting a sudden crunch of messages being enqueued into local queues. Not something we expect to be common at all, but now we're better anyway!

What's Changed

Full Changelog: JasperFx/wolverine@V6.30.1...V6.30.2

6.30.1

There's some CritterWatch related functionality smuggled in here for our forthcoming Event Modeling visualization. Otherwise, this is mostly a ton of fine grained improvements for CI or message broker usage problems detected by dogfooding and some "Mr. AI tool, go try to identify potential problems" action

What's Changed

Full Changelog: JasperFx/wolverine@V6.30.0...V6.30.1

6.30.0

Wolverine 6.30.0 is a large release built around one headline feature — a new endpoint mode — plus the usual crop of transport fixes, and a couple of long-standing multi-tenancy and HTTP gaps closed.

EndpointMode.NativeAck

The main event. Buffered's throughput and partitioning with Inline's no-loss guarantee, and no database required.

A broker delivery is held unacknowledged while the envelope flows through an in-memory, optionally group-partitioned execution block, and is settled natively when the handler pipeline terminates. Nothing is acknowledged ahead of its handler, so work parked in a lane when a node goes away comes back rather than vanishing.

opts.ListenToRabbitQueue("orders")
    .ProcessInParallelWithNativeAcks();

The guarantee, stated exactly: no two messages sharing a group id execute concurrently. Ordering is per-slot best-effort, not per-group guaranteed; redelivery may reorder. Anything needing strict order under failure keeps the durable inbox.

Transport support is opt-in and default-closed — a transport must explicitly claim the mode, because most settlement models cannot express out-of-order completion. Adopted by RabbitMQ, Amazon SQS, Azure Service Bus, NATS JetStream, Redis Streams, Pulsar and GCP Pub/Sub (#​3708, #​4046, #​4047, #​4050, #​4051, #​4052, #​4053).

Supporting work in the same wave:

  • Lease renewal for queued envelopes on clocked transports — SQS, ASB, JetStream and Pub/Sub run a clock on an unsettled delivery, and the risk window is lane queue time plus handler time (#​4048).
  • In-memory idempotency guard, an opt-in duplicate filter for a mode with no inbox row to deduplicate against (#​3710).
  • Global partitioning across sharded queues (#​3709).
  • Listener mode coherence validation, which caught that RabbitMQ queues default to Inline — so sharded topologies were silently unpartitioned without an explicit BufferedInMemory() (#​3712, #​4022).
  • A five-node chaos reproduction under webhook flood, measuring the real duplicate rate on abrupt node loss (#​3713).

Multi-tenancy

  • Conjoined EF Core tenancy now works when Marten owns the message store via IntegrateWithWolverine(). Marten hands Wolverine an NpgsqlDataSource rather than a connection string, and NpgsqlDataSource.ConnectionString deliberately omits the password — so there is a new DbDataSource overload of AddDbContextWithWolverineManagedConjoinedTenancy that carries credentials through intact. A second defect on the same path is fixed too: IntegrateWithWolverine() never registered the tenant partitioning provider, so PartitionPerTenant() failed (#​4044).

HTTP and event sourcing

  • [StreamState] and [StreamEvents] — new parameter attributes for handlers whose read is the raw stream rather than the folded aggregate, for timeline and audit shaped endpoints that [ReadModel] cannot express. Store-agnostic across Marten, Polecat and Fisher; Marten batches both fetches into a single round trip (#​3627).
  • Marten concurrency conflicts as 409 — a documented, tested recipe for mapping optimistic-concurrency failures on [WriteAggregate] endpoints to ProblemDetails instead of an unhandled 500. Note that StreamLockedException derives from MartenException, not ConcurrencyException, so catching only the latter silently leaves FetchForExclusiveWriting returning 500s (#​3764).
  • Event Model slices per routeHttpChainDescriptor and GrpcRpcDescriptor now carry the slice the route is, so a consumer walking endpoint by endpoint sees it next to the route rather than only through the assembled model (#​4000).

Transport fixes

  • Pulsar: requeue, scheduled retry and dead-letter routing implemented (#​3797). A global failure rule was silently disabling every user error policy application-wide (#​4075). Hot-tail listeners silently dropped deferred messages in every mode (#​4060).
  • GCP Pub/Sub: listener shutdown could hang on in-flight callbacks (#​4065); exhausting MaxTotalAckExtension silently delivered a concurrent duplicate rather than reporting anything (#​4066); effective listener concurrency was not what the configuration implied, and the flow-control bound is global per SubscriberClient rather than per inner client (#​4067). PubsubTopicOptions.OrderBy gained a configuration surface (#​4087).
  • Redis: DeleteStreamEntryOnAck silently never acked on Redis < 8.2, where XACKDEL is unsupported (#​4058).
  • Ack reliability: a shared ack-attempt budget across stacked retry blocks, and terminal-failure classification for Azure Service Bus and SQS so a permanent settle failure stops rather than burning the whole budget (#​4012).

Upgrading

Additive. EndpointMode.NativeAck is opt-in per endpoint and default-closed per transport, and MaximumBrokerRedeliveries defaults to off. Requires JasperFx 2.55.0.

6.29.2

A fix release. Four changes, three of them reported bugs.

RavenDB users should take this one

ClearAllAsync deleted node records by tracked entity from a session that had never loaded them, so a Solo-mode start after a Balanced-mode run threw InvalidOperationException: WolverineNode is not associated with the session on every stale node and the application could not start at all. The workaround of clearing WolverineNodes by hand in RavenDB Studio is no longer needed. (#​3993, closes #​3986)

The compliance coverage written for that fix caught a second provider: SQLite orphaned every agent assignment row, because its assignment table has no ON DELETE CASCADE (PostgreSQL, Sql Server and Oracle do). The orphans stay invisible until a node re-registers under the same id — the GH-3604 ejection path — where it returns owning agents it was never reassigned. The underlying gap was that NodePersistenceCompliance never exercised ClearAllAsync at all, which is how two providers shipped it broken. It does now.

Agents no longer stall on a node that cannot build them

When IAgentFamily.BuildAgentAsync threw, the leader saw only an unconfirmed agent — which it deliberately does not treat as a failure — so the assignment stood and the same agent was requested on the same failing node forever. Reported as a 54-minute fleet-wide projection stall on a blue/green cluster with disjoint projection versions. Consecutive failed starts are now counted on the node that catches them and feed into the existing GH-3888 release path. New DurabilitySettings.MaxAgentStartFailuresBeforeRelease (default 3); set it to 0 for the previous behaviour. (#​3994, closes #​3970)

The orphaned-message sweep no longer dominates database load

Reported against a 466-shard PostgreSQL deployment. The sweep's predicate could not use an index, so it full-scanned the whole inbox per database every five seconds to find nothing; the update was unbounded, so one node loss became a single ~910,000-row rewrite across the fleet; and it ran inside the shared recovery transaction, blocking inbox inserts. All three are fixed, with a new OrphanedMessageReleaseBatchSize and a dedicated OrphanedMessageSweepPollingTime. (#​3995, closes #​3971)

Upgrade note. This ships a partial index on owner_id, so PostgreSQL and Sql Server users will see one index applied on the next schema migration.

HTTP endpoints can take immutable request types

A Before / BeforeAsync method on an endpoint class that accepts the request type and returns it now replaces the request body for the rest of the chain, exactly as it has on the handler side since GH-516. Use it to stamp server-supplied values onto an immutable record request before the endpoint runs. (#​3984)


Full detail for every item is in CHANGELOG.md.

What's Changed

New Contributors

Full Changelog: JasperFx/wolverine@V6.29.1...V6.29.2

6.29.1

This bumps the Fisher dependency to 1.0. We needed this for the CritterWatch 1.0 release.

What's Changed

New Contributors

Full Changelog: JasperFx/wolverine@V6.29.0...V6.29.1

6.29.0

A feature release. Three of the five items fix silent failure modes — work that acted on a write which could still roll back, a convention mirror that installed a relay over a real handler, and two concurrent writers to one entity — so the notes below say what the old behaviour looked like, not just what changed.

AfterCommit — run work after the transactional commit

After reads like a post-handler hook that runs at the end. It does not run after the commit (#​3976, closes #​3975).

The commit is itself a postprocessor contributed by the persistence provider, and After methods are inserted at the front of that list. So an After method observing a write is observing one that is not durable yet and may still roll back — and there was no supported way to ask for the other side of it, even though Wolverine uses that position itself for the outbox flush.

public static class RaiseAlertHandler
{
    public static void Handle(RaiseAlert command, IDocumentSession session)
        => session.Events.Append(command.Id, new AlertRaised(command.Reason));

    // Only runs if the append above actually committed
    public static void AfterCommit(AlertLatch latch, RaiseAlert command)
        => latch.MarkRaised(command.Id);
}

Use the AfterCommit / AfterCommitAsync convention or [WolverineAfterCommit], on message handlers, sagas and HTTP endpoints. Parameters bind exactly as After already does.

The position is structural, not positional — frames go into a new IChain.PostCommitPostprocessors list concatenated after every postprocessor at frame-assembly time, rather than being appended from a policy sequenced after the persistence policy. Getting the position right by luck of policy ordering is precisely what breaks silently later.

Two behaviours worth knowing:

  • They do not run when the commit throws. Frames are concatenated without a try/finally, so the exception unwinds straight past them. That is the point — the reason to want "after the commit" is usually that the side effect must not happen for a write that did not land.
  • They run after the outbox flush as well, so a message cascaded from an after-commit method is not atomic with the write. Cascade from the handler if it has to be.

After's pre-commit position is unchanged and stays that way. Verified per provider: Marten, Polecat, Fisher, EF Core, RavenDb and CosmosDb each have a codegen test asserting the emitted call lands after that provider's own commit frame.

A store-agnostic EventsToAppend return type

Wolverine.Marten.Events, Wolverine.Polecat.Events and Wolverine.Fisher.Events are identical but store-named, so a handler that wanted to be store-agnostic could not name any of them (#​3969, closes #​3941).

The store-agnostic path did exist — a bare IEnumerable<object> return is picked up by a fallback — but that fallback is positional. IEnumerable<T> is covariant, so every reference-typed collection in a return tuple is a candidate and the first one wins. Nothing failed at codegen and nothing failed at runtime; the wrong collection simply became the appended events.

The type is EventsToAppend, not Events. Naming it Events would have been a source-breaking collision (CS0104) for any handler importing both the core event-sourcing namespace and a store integration — that is, on the very declaration the feature exists for.

Ask what will be handled, and how a batch is shaped

Discovery materializes after options time, so an extension installing fallback handlers could not ask "will this message type have a handler?" and had to hand-roll a mirror of Wolverine's own discovery convention (#​3977, closes #​3974).

Such a mirror drifts, and it drifts silently: one that scanned a single assembly stopped seeing handlers that moved to a second, and installed a bare relay over a real handler — the exact defect the guard existed to prevent, with every codegen test still passing.

opts.OnHandlersDiscovered(handlers =>
{
    if (!handlers.Handles<ServiceUpdates>())
    {
 ... (truncated)

## 6.28.2

A bug fix release, with one new opt-in on the RabbitMQ listener contributed by the community.

## Store agnostic document sessions

A handler can now take the `JasperFx.Events.Documents` contracts — `IDocumentSessionOperations`, `IDocumentWriteOperations`, `IDocumentReadOperations` — straight as parameters, and they bind *and commit* on Marten, Polecat and Fisher alike ([#​3962](https://github.com/JasperFx/wolverine/pull/3962), closes [#​3956](https://github.com/JasperFx/wolverine/issues/3956)):

```csharp
// Valid against all three stores -- nothing store specific is named
public static void Handle(RecordNote command, IDocumentSessionOperations session)
    => session.Store(new Note { Id = command.Id, Text = command.Text });

These are the document side counterparts to the IEventOperations contracts Wolverine already understood, and they are the only way store agnostic source can take a session without naming a concrete store type.

Before this, such a handler failed codegen outright on a stock host. Once bound, its writes were queued into the session's unit of work and silently discarded — no exception. Both halves are fixed.

⚠️ Importing the JasperFx.Events.Documents namespace makes ToListAsync() ambiguous with each store's own queryable extensions (CS0121). Alias the individual contracts rather than importing the namespace.

Durability agents no longer assigned to nodes that cannot run them

A node started with Durability.DurabilityAgentEnabled = false never registers the durability agent family, so it threw Unrecognized agent scheme 'wolverinedb' the moment the leader handed it one. The leader re-issued the identical assignment every five minutes indefinitely, no durability agent ran anywhere for that store, and owner_id = 0 outgoing envelopes were never recovered (#​3963, closes #​3954).

The failure was silent in both directions — every queue table read zero while the backlog grew. Nodes now publish a marker capability when the family is actually registered, the leader skips nodes that have not, and when no node in the cluster is capable a warning names the condition and the setting.

If you run a Balanced cluster with DurabilityAgentEnabled = false on any node, this release is worth taking.

Ancillary store transaction ownership

Ancillary store inference scanned chain.ServiceDependencies(), which walks constructor graphs recursively — so a dependency that merely held an ancillary store matched. A read only store injected two hops down counted the same as an injected DbContext, and a tenant Marten handler had its inbox and dead letters stolen by the wrong store (#​3957, closes #​3953).

That inference was only ever correct for EF Core. There is a new default null IPersistenceFrameProvider.TryDetermineTransactionOwnerType for it, implemented only by EF Core.

RabbitMQ

  • Wait for prefetched messages on drain (#​3796) — contributed by @​benjamin-alexander-simplisafe. Opt in with ListenToRabbitQueue("orders").DrainWaitForPrefetch() to let already prefetched messages finish rather than letting the broker requeue them.
  • The prefetch drain is safe for a non terminal stop (#​3960) — StopAsync is not always terminal, and a BatchingChannel silently discards a post after completion, so a delivery landing between the drain and the dispose latch vanished and was redelivered.
  • A rejected settle quiesces the channel the broker already closed (#​3964, addresses #​3950) — feeding a channel that is being torn down is what makes RabbitMQ.Client race itself and escalate a dead channel into a close of the entire connection (code=541). This narrows the window and speeds recovery; it does not prevent the close, whose root cause is upstream in rabbitmq-dotnet-client.
  • Listener recovery after a mid flight connection death is now asserted (#​3961).

Idle reaper no longer latches durable endpoints

A durable endpoint reached only via EndpointFor(uri) looked as disposable as an ephemeral reply queue and was reaped; the rebuilt agent then wrapped a disposed sender and latched forever (#​3958, closes #​3955). SendingAgentIdleTimeout had no test coverage at all before this.

6.28.1

Patch release over 6.28.0.

New package

WolverineFx.Http.Fisher (#​3949, closes #​3944) — there was a Wolverine.Http.Marten and a Wolverine.Http.Polecat and no Fisher equivalent, so a Fisher-backed application had nothing to reference for the aggregate/document HTTP attributes. The third flavour now exists alongside its siblings.

Fixes

A SQLite "schema name" is now the table name prefix it was documented to be

(#​3945, closes #​3943)

Setting FisherIntegration.MessageStorageSchemaName, or the schemaName argument to PersistMessagesWithSqlite(), reached the message store as a schema.table qualifier. SQLite has no user-defined schemas — the only names a plain connection knows are main, temp, and whatever has been ATTACHed — so any value other than main emitted SQL against a database that never existed, and the host died on the first envelope write with:

SQLite Error 1: 'no such table: <name>.wolverine_incoming_envelopes'

The two halves had disagreed all along. Weasel's SqliteObjectName drops the schema from its qualified name, so the DDL had been creating a bare wolverine_incoming_envelopes while the inherited DML asked for a qualified one; the main default is the only thing that hid it.

The name is now folded into the table names as a prefix — a meaning SQLite can honour, giving several logically separate Wolverine table sets inside one database file:

opts.PersistMessagesWithSqlite(connectionString, "reporting");
// => reporting_wolverine_incoming_envelopes, reporting_wolverine_outgoing_envelopes, ...

This covers the envelope, node, control queue, tenant, listener and saga tables, plus the dead-letter index names, since SQLite shares one identifier namespace between tables and indexes.

No migration. main is the default and prefixes nothing, so every database provisioned before this release keeps its existing wolverine_* names. Only hosts that explicitly set a non-main name see different table names — and those hosts could not start at all before this fix. Postgres, SQL Server, MySQL and Oracle render exactly as before.

FisherIntegration.TransportSchemaName is now documented as what it has always been on a Fisher host: inert. Tracked in #​3947.

Polecat unwraps Nullable<T> when determining an aggregate's id type

(#​3948, closes #​3942) Marten and Polecat disagreed for an aggregate whose id property is nullable: Polecat answered Nullable<T> verbatim, which is not a primitive id type, so the documented IdentifiedBy<T> escape hatch was skipped entirely. The two stores now agree.

Dependencies

Fisher 0.7.0 (#​3946) — the package floor moves from 0.6.0 to 0.7.0. Note that Fisher 0.7.0 bundles JasperFx.Events.SourceGenerator inside its own nupkg, as Polecat already does. A project that also references that generator explicitly will get two analyzer instances and a CS0433 duplicate-type error until one copy is removed.

Full changelog: JasperFx/wolverine@V6.28.0...V6.28.1

6.28.0

Storage agnostic conventions wave: write handlers and HTTP endpoints that read and append without naming a store.

Highlights

  • Storage.AppendEvents() / Storage.StartStream() (#​3934) — event stream counterparts to Storage.Store(), expressed entirely against JasperFx.Events.IEventOperations, so the same handler is valid on Marten, Polecat or Fisher with no IDocumentSession.
  • [FirstOrDefault] (#​3933) — the singleton document [Entity] cannot express, since it has no identity to look up by.
  • [All] and [Queryable] (#​3936) — every document of a type as an IReadOnlyList<T>, and a raw IQueryable<T> escape hatch.
  • Batched reads (#​3938) — on Marten, Polecat and Fisher, two or more batchable reads in the same handler now resolve in a single database round trip. Nothing to turn on.
  • OnMissing.EmptyContentWith204, [NoContentIfMissing] / [NotFoundIfMissing] (#​3931) — answer an empty 204 instead of a 404 when there is simply nothing to return.
  • DateTime / DateTimeOffset now in Wolverine.HTTP (#​3932) — matches the long standing message handler convention. Previously such a parameter silently bound from the query string and arrived as default.

Notable fix

An IEventStoreOperations / IEventOperations handler or endpoint parameter now resolves and commits (#​3936). CanApply recognized no event operations type, so AutoApplyTransactions skipped those chains and appended events were queued into the session's unit of work and never committed — with no exception thrown. This also affected each store's own event operations types, so it predates this release.

Also: [All] / [Queryable] / [FirstOrDefault] provider errors now name the declaring method (#​3937).

Full detail in CHANGELOG.md.

6.27.1

Wolverine 6.27.1

A same-day patch on 6.27.0, fixing a regression that release introduced and closing the asymmetry that surfaced it.

Regression fix: [WriteAggregate] lost its not-found guard in 6.27.0

WriteAggregateAttribute derives from WriteModelAttribute and overrides neither Modify nor Required, so it inherited 6.27.0's nullability inference (GH-3916) wholesale. [WriteAggregate] shipped a year before that inference, so in 6.27.0 an existing handler like:

public static Events Handle(RecordDeposit command, [WriteAggregate] Account? account)

silently lost its not-found guard and began running against a model that was never loaded — for a write model, that means appending events against a stream that was not fetched.

[WriteAggregate] and [ReadAggregate] now pin the unconditional Required = true they have always had, in Marten, Polecat and Fisher alike. Say Required = false explicitly, or move to [WriteModel] / [ReadModel], to opt out.

If you are on 6.27.0 and use [WriteAggregate] with a nullable parameter and no explicit Required, upgrade.

[ReadModel] takes Required from the parameter's nullable annotation (#​3929)

Matching what GH-3916 did for [WriteModel]: Order order is required and gets a not-found guard, Order? order is not and is handed to your method as null so your own null branch runs. An explicit Required at the call site still wins over the annotation.

This closes the write/read asymmetry — a handler moving between the two forms no longer needs a different attribute spelling for identical intent.

What deliberately did not change

  • [Entity] keeps its unconditional Required = true. It is the oldest and most widely used of these attributes and is heavily used in HTTP endpoints, where Required = true with OnMissing.Simple404 is the documented 404 behaviour. Loosening it would turn a clean 404 into a runtime NullReferenceException in an endpoint body.
  • [DeciderFunction] and [DcbModel] keep Required = false. Their model is folded out of an event stream or boundary and is always materialized, so absence is not the normal case; inferring here would tighten the default and could stop messages that process today.

Worth knowing

In an assembly compiled with <Nullable>disable</Nullable> a reference-type parameter reads as unknown rather than nullable, so [WriteModel] and [ReadModel] fall back to Required = true. The inference is a no-op for those projects rather than a silent behaviour change. Now documented in the persistence guide.

6.27.0

Wolverine 6.27.0

New: WolverineFx.Fisher

Fisher — the embedded SQLite document database and event store — is now a first-class Wolverine persistence integration, alongside Marten and Polecat.

builder.Services.AddFisher(opts => opts.Connection("Data Source=app.db"))
    .ApplyAllDatabaseChangesOnStartup()
    .IntegrateWithWolverine();

A Fisher-backed service is zero-infrastructure: no server, no container, no network. The transactional inbox/outbox, saga storage and the full aggregate handler workflow all work, and the store-agnostic [WriteModel] / [ReadModel] / [DeciderFunction] / [DcbModel] attributes run against it unchanged — the same handler code compiles and runs on any of the three stores.

Two SQLite realities shape it, both documented:

  • One writer per file. Wolverine's durability tables commit on Fisher's own connection inside Fisher's transaction. A second connection to the same file is a second writer and presents as a hang rather than an error.
  • DurabilityMode.Solo. Leader election and agent distribution need several nodes sharing one database; a Fisher store is a file.

Ancillary stores work too. AddFisherStore<T>().IntegrateWithWolverine() is supported, and [Storage(typeof(IMyStore))] routes a handler to it without naming Fisher in the consumer's source.

Not in this first release, each for a reason rather than for lack of time: multi-tenancy (Fisher's tenancy is a file per tenant), cluster durability modes, and transport schema stamping (SQLite has no schemas). See Fisher Integration.

Requires Fisher 0.6.0.

New: [DcbModel] — Dynamic Consistency Boundaries, store-agnostic

The DCB workflow joins the store-agnostic vocabulary in Wolverine core. Where [WriteModel] is about one stream, [DcbModel] spans every stream whose events match a tag query, with the store asserting at commit that no matching event landed in the meantime.

public static EventTagQuery Load(ReserveSeat command)
    => EventTagQuery.For(command.ScreeningId).Or(command.CustomerId);

public static SeatReserved Handle(ReserveSeat command, [DcbModel] SeatAvailability availability)
    => new(command.ScreeningId, command.CustomerId);

Wolverine.Marten.BoundaryModelAttribute and Wolverine.Polecat.BoundaryModelAttribute now inherit from it and behave identically — existing [BoundaryModel] code needs no change. Prefer [DcbModel] in new code.

[WriteModel] fixes

  • Required now defaults from the parameter's nullable annotation (#​3916). Order order is required and gets a not-found guard; Order? order is not, and is handed to your method as null so your own null branch runs. A nullable annotation with Required = true was a contradiction that silently resolved in favour of the attribute default, making the handler's null branch dead code. Setting Required explicitly still overrides the annotation either way.

    ⚠️ Behaviour change for a handler with a nullable model parameter that relied on the implicit guard. Set Required = true explicitly to keep it.

  • [Identity] is now honoured (#​3918). [DeciderFunction] always respected [Identity] on the command member; [WriteModel] did not, so the same command against the same model needed an explicit [WriteModel("...")] under one form and nothing under the other. Resolution order is now: explicit [WriteModel("orderId")], then [Identity], then {Model}Id, then id, then a strong typed id match.

Amazon SQS: oversized messages (#​3926)

A message too big for SQS is no longer retried forever. SQS caps a message at 256KB and rejects a larger one with InvalidParameterValue - Message must be shorter than 262144 bytes (SenderFault: true). SenderFault: true means the identical request will fail identically forever, but Wolverine treated it as a transient send failure and re-queued it — which is why this presented as a flood of identical errors rather than one. An oversized message is now logged once and discarded.
... (truncated)

6.26.0

Upgrade note

This release moves the Critter Stack dependencies forward together:

package from to
JasperFx, JasperFx.Events (+ both source generators) 2.46.0 2.47.0
Marten, Marten.AspNetCore, Marten.Newtonsoft 9.22.6 9.23.0
Polecat [5.7.0,6.0.0) (resolving to 5.7.0) [5.12.0,6.0.0)

Polecat users get the larger jump of the two: the old range pin resolved to its 5.7.0 floor, so this is 5.7.0 → 5.12.0 in practice.

If you reference JasperFx.Events.SourceGenerator explicitly and reference Polecat, you may hit CS0433 ("the type <X>Evolver exists in both <YourAssembly> and <YourAssembly>"). The generator ships both bundled inside the Polecat nupkg and as a standalone package; when the two copies are the same version they load as two analyzer instances and each emits every projection's Evolver dispatcher. Polecat 5.12.0 bundles 2.47.0, which is what this release pins, so the pair now matches. The fix is to drop one instance — see PolecatTests.csproj for the DropDuplicateBundledEventSourceGenerator target we use, which keeps the explicitly-pinned generator and removes the bundled duplicate.

Two new packages

WolverineFx.DataAnnotationsValidation and WolverineFx.FluentValidation.Grpc are published for the first time in this release. Both were documented as installable but had never actually shipped — every version returned BlobNotFound from nuget.org — because each declared a PackageId while being absent from the packaging list. If you followed the Data Annotations validation or gRPC error details docs and found the package missing, it exists now. A build-time check keeps the two lists from drifting apart again. (#​3905, #​3909)

Fixes

A [WriteAggregate]-only chain now reports IsTransactional correctly. The Marten and Polecat aggregate handler workflows appended a SaveChangesAsync postprocessor but never set IChain.IsTransactional, so a chain reported having no transactional middleware while its generated code committed. The disagreement was visible to IHttpPolicy authors, who had no reliable signal for whether a chain would commit — the workaround was an unused IDocumentSession parameter on every such endpoint, purely to flip the detection. Thanks to @​esond for the report and the fix. (#​3893, #​3901)

Outgoing batches no longer serialize eagerly. OutgoingMessageBatch built its contiguous byte[] in its constructor whether or not anything read it. (#​3906)

Aggregate handler workflow unification (#​3907, increment one)

Wolverine has carried two near-identical copies of the aggregate handler workflow — one in Wolverine.Marten, one in Wolverine.Polecat — and improvements had been landing on one copy at a time. This release starts implementing it once, in core.

Nothing is retired and no public API changes. [WriteAggregate], [ReadAggregate], [AggregateHandler], [ConsistentAggregate], Events, MartenOps/PolecatOps and the rest of each integration's surface stay exactly where they are.

What landed:

  • The drift between the two copies is reconciled, taking whichever side was correct rather than merging mechanically. The substantive one: AggregateHandling.DetermineVersionMember now returns MemberInfo? — Marten's copy used a null-forgiving operator that was masking a real null from IAggregateVersioning.VersionMember. Polecat regains AOT annotations it had dropped. Two reflectively-closed codegen frames widen a class constraint to notnull, matching IEventStream<T>'s own declaration; the narrower form threw for a struct aggregate instead of generating the same correct code.
  • IEventSourcingFrameProvider, the store seam — deliberately a sibling of IPersistenceFrameProvider rather than new members on it, so stores without event sourcing never grow no-op aggregate members.
  • The first shared mechanism moved into Wolverine.Persistence.EventSourcing: the event-capture frames and DetermineEventCaptureHandling, all written purely against JasperFx.Events' IEventStream<T>.
  • A reflection test enforcing that no core type shares a simple name with a public type in a Wolverine.<Store> integration, so a CS0104 ambiguity for users is caught by a failing test instead.

The bulk extraction continues in #​3911. Also in this release: #​3908, which pins what [Storage(typeof(...))] actually promises against a Marten ancillary store.

Upstream halves of this work: JasperFx/jasperfx#​648, JasperFx/marten#​5221, JasperFx/polecat#​453.

6.25.5

6.25.5 supersedes the never-published 6.25.4 (its tag and release were retired), so the first two fixes below make their first NuGet appearance here.

Fixes

PostgreSQL dead-letter and outgoing counts are exact for small tables (GH-3885)

The PostgreSQL message-store counts for the dead-letter and outgoing tables now report exact numbers for small tables instead of the estimate that could read as zero right after activity. First staged for 6.25.4; this is its first published release.

The durable inbox routes by endpoint for sticky handlers (GH-3886)

Durable inbox recovery now routes each envelope by its owning endpoint, so sticky-handler ([StickyHandler] / endpoint-scoped) messages recovered from the inbox execute on the endpoint they were received on rather than falling back to the default route. Also first staged for 6.25.4.

Never export empty metrics snapshots + idle-tenant eviction (#​3891)

Wolverine no longer exports metrics snapshots that contain no data, and per-tenant metric state for tenants that have gone idle is evicted after a configurable number of cycles via WolverineOptions.Metrics.TenantIdleEvictionCycles. This is the upstream half of CritterWatch#​963 — at very high tenant counts, idle tenants no longer pin memory or pad every export.

Agents that exhaust node-local auto-restarts are released to a capable peer (GH-3888, #​3896)

When a stalled agent uses up its node-local auto-restart budget, the node now releases the agent so a capable peer can pick it up, instead of retrying forever on the same node. A capability embargo prevents the agent from bouncing straight back to the node that just failed it.

Short-circuiting Before + Finally middleware no longer NREs (GH-3892, #​3895)

Middleware that combines a short-circuiting Before method with a Finally method no longer produces a NullReferenceException at codegen time — and Finally now runs on the short-circuit path, as the middleware contract promises.

Saga diagnostics tolerate an unprovisioned saga table (GH-3887, #​3894)

DatabaseSagaStoreDiagnostics.ReadSagaAsync / ListSagaInstancesAsync treat a missing saga table (Postgres 42P01 / SQL Server 208) as null / empty rather than surfacing a raw undefined-table error. A declared-but-never-persisted saga is a legitimate state, since AddSagaType is optional.

Polecat TransportSchemaName is honored (GH-3884, #​3897)

PolecatIntegration.TransportSchemaName is now actually applied — previously the setting was inert and the transport tables always landed in the default schema.

Improvements

The stalled-agent auto-restart path is testable (#​3890)

The auto-restart path now runs on TimeProvider, making it deterministic under test — with coverage added. Thanks @​erdtsieck!

Message types can be exempted from partitioned processing (GH-3899, #​3902)

MessagePartitioning.ExemptFromPartitionedProcessing<T>() exempts a message type from partitioned (GroupId-keyed) processing — exempt types ride the endpoint's normal parallelism while partitioned types keep strict per-group ordering.

Batched members' DeliverBy expiry is enforced again (GH-3898, #​3903)

Expired members are shed at batch assembly with the normal discard observability, and a whole-batch backstop expires batches whose every member has lapsed.

The sharded execution block deserializes in parallel with ordered emission (GH-3900, #​3904)

The sharded execution block's decompress/deserialize stage now runs N-wide while preserving per-group FIFO byte-for-byte.

... (truncated)

6.25.3

A silent data-plane bug for anyone combining Marten with a database-backed transport. Found from a user's minimal reproduction against CritterWatch.

What's Changed

IntegrateWithWolverine() registers MartenIntegration as an IWolverineExtension, so its Configure() runs at host build — after an inline UsePostgresqlPersistenceAndTransport(..., transportSchema: ...) in the same options lambda. It then stamped its own schema names onto the shared PostgreSQL transport unconditionally, so the integration's defaults silently overwrote whatever the caller asked for.

The failure lands on the data plane rather than at startup, which is what makes it expensive to diagnose. A host without Marten honours the configured schema and publishes to {configured}.wolverine_queue_x; a Marten-backed consumer listens on wolverine_queues.wolverine_queue_x. Auto-provision creates both tables happily, nothing is logged on either side, and no message is ever delivered — the publisher's rows just accumulate in a table nobody polls:

 myapp_queues     | wolverine_queue_orders   <- publishers write here
 wolverine_queues | wolverine_queue_orders   <- the Marten-backed host listens here

TransportSchemaName now records whether it was explicitly assigned and is stamped onto the transport only then; MessageStorageSchemaName is stamped only when non-empty. Both currently-working cases are unchanged — an explicitly-set Marten knob still wins, and a host that configures neither still lands on wolverine_queues.

If you have been running Marten alongside UsePostgresqlPersistenceAndTransport with a custom transportSchema, check for a duplicate wolverine_queue_* table under wolverine_queues — that is undelivered mail, and it becomes reachable once you upgrade.

Known related gap

PolecatIntegration.TransportSchemaName is declared and documented but never applied — the mirror-image problem (inert rather than over-eager), so an explicit value there is silently ignored. Its sibling MessageStorageSchemaName is wired correctly. Tracked as #​3884, not addressed in this release.

Full Changelog: JasperFx/wolverine@V6.25.2...V6.25.3

6.25.2

All related to CritterWatch

What's Changed

GlobalPartitionedMessageTopology.SetExternalTopology force-set EndpointMode.Durable on every external slot and companion local queue after the user's configure callback ran, with no way to opt out. For lossy, re-reported traffic — telemetry being the motivating case — that store-and-forwards every envelope through the application's own message store.

opts.MessagePartitioning.GlobalPartitioned(topology =>
{
    topology.UseShardedRabbitQueues("telemetry", 5);
    topology.Mode(EndpointMode.BufferedInMemory); // new — default stays Durable
});

The mode applies to the external slots and their companion local queues, and is order-independent (it may be set before or after the transport-specific UseSharded*Queues call). EndpointMode.Inline is rejected — partitioned slots depend on the external-listener-to-companion-queue bridge that inline endpoints bypass.

Full Changelog: JasperFx/wolverine@V6.25.1...V6.25.2

6.25.1

All related to CritterWatch

What's Changed

Full Changelog: JasperFx/wolverine@V6.25.0...V6.25.1

6.25.0

Couple bugs, one new API meant for CritterWatch

What's Changed

Description has been truncated

Bumps OpenIddict.EntityFrameworkCore from 7.6.0 to 7.6.1
Bumps WolverineFx.EntityFrameworkCore from 6.21.0 to 6.33.0

---
updated-dependencies:
- dependency-name: OpenIddict.EntityFrameworkCore
  dependency-version: 7.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ef-core
- dependency-name: WolverineFx.EntityFrameworkCore
  dependency-version: 6.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ef-core
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

0 participants