Bump the wolverine group with 7 updates - #234
Open
dependabot[bot] wants to merge 1 commit into
Open
Conversation
Bumps WolverineFx from 6.21.0 to 6.31.0 Bumps WolverineFx.EntityFrameworkCore from 6.21.0 to 6.31.0 Bumps WolverineFx.FluentValidation from 6.21.0 to 6.31.0 Bumps WolverineFx.Postgresql from 6.21.0 to 6.31.0 Bumps WolverineFx.RabbitMQ from 6.21.0 to 6.31.0 Bumps WolverineFx.RuntimeCompilation from 6.21.0 to 6.31.0 Bumps WolverineFx.SqlServer from 6.21.0 to 6.31.0 --- updated-dependencies: - dependency-name: WolverineFx dependency-version: 6.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wolverine - dependency-name: WolverineFx.EntityFrameworkCore dependency-version: 6.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wolverine - dependency-name: WolverineFx.FluentValidation dependency-version: 6.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wolverine - dependency-name: WolverineFx.Postgresql dependency-version: 6.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wolverine - dependency-name: WolverineFx.RabbitMQ dependency-version: 6.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wolverine - dependency-name: WolverineFx.RuntimeCompilation dependency-version: 6.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wolverine - dependency-name: WolverineFx.SqlServer dependency-version: 6.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wolverine ... Signed-off-by: dependabot[bot] <support@github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updated WolverineFx from 6.21.0 to 6.31.0.
Release notes
Sourced from WolverineFx's releases.
6.31.0
Logical message deduplication
Envelope.Ididentifies 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 differentEnvelope.Idand every one gets through.6.31.0 promotes
Envelope.DeduplicationIdinto a first-class logical id, with storage, enforcement, and a retention policy behind it.It is opt-in throughout — leaving it off means no schema change at all on upgrade. Storage is a separate
wolverine_deduplicationtable rather than a column on the inbox, because underEnableInboxPartitioningthe inbox isPARTITION 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 anINSERTthat either succeeds or trips the primary key, never aSELECT-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 returnsAlreadyExists/InvalidArgumentper 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.DeduplicationIdat 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]:These are
IEnvelopeRuleat the message type level, resolved once when the route is built rather than per message. An explicitDeliveryOptions.DeduplicationIdalways wins, then configured rules, then the attribute.Fixes
ListeningAgentsees past its receiver wrappers.ReceiverWithRules— installed by a bare endpoint-levelMessageTypeorTenantId— is unconditionally anILocalQueue, 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)TriggerLabel, which was beating overlay declarations and minting aSourceDisagreementhotspot 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)Dependencies
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.IsolatedAndScopedis now honored by Wolverine.HTTP (#4171). An endpoint or middleware asking for anIServiceProvideralways receivedhttpContext.RequestServices, whatever you configured. Note the consequence: asking for anIServiceProviderin an endpoint is service location and now registers as such, so underServiceLocationPolicy.NotAllowedthose 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-locatedIMessageContext,IMessageBus, or MartenIDocumentSessionwas 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 asTryAddScoped(typeof(Lazy<>), typeof(LazyResolver<>))was ignored whenever the closed type was itself concrete, andnew Lazy<IFoo>()was emitted instead. That compiles and can never work — the first.ValuethrowsMissingMemberExceptionfor 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
ResequencerSagaadvancesLastSequencewhen 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
shouldHandleAlreadySequencedhook logs a warning by default — behavior is unchanged — and can be overridden to discard, raise a metric, or throw.Startup
AutoCreate.Noneno 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.NativeAckThe 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.
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:
BufferedInMemory()(#3712, #4022).Multi-tenancy
IntegrateWithWolverine(). Marten hands Wolverine anNpgsqlDataSourcerather than a connection string, andNpgsqlDataSource.ConnectionStringdeliberately omits the password — so there is a newDbDataSourceoverload ofAddDbContextWithWolverineManagedConjoinedTenancythat carries credentials through intact. A second defect on the same path is fixed too:IntegrateWithWolverine()never registered the tenant partitioning provider, soPartitionPerTenant()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).[WriteAggregate]endpoints toProblemDetailsinstead of an unhandled 500. Note thatStreamLockedExceptionderives fromMartenException, notConcurrencyException, so catching only the latter silently leavesFetchForExclusiveWritingreturning 500s (#3764).HttpChainDescriptorandGrpcRpcDescriptornow 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
MaxTotalAckExtensionsilently 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 perSubscriberClientrather than per inner client (#4067).PubsubTopicOptions.OrderBygained a configuration surface (#4087).DeleteStreamEntryOnAcksilently never acked on Redis < 8.2, whereXACKDELis unsupported (#4058).Upgrading
Additive.
EndpointMode.NativeAckis opt-in per endpoint and default-closed per transport, andMaximumBrokerRedeliveriesdefaults 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
ClearAllAsyncdeleted node records by tracked entity from a session that had never loaded them, so a Solo-mode start after a Balanced-mode run threwInvalidOperationException: WolverineNode is not associated with the sessionon every stale node and the application could not start at all. The workaround of clearingWolverineNodesby 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 thatNodePersistenceCompliancenever exercisedClearAllAsyncat 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.BuildAgentAsyncthrew, 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. NewDurabilitySettings.MaxAgentStartFailuresBeforeRelease(default3); set it to0for 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
OrphanedMessageReleaseBatchSizeand a dedicatedOrphanedMessageSweepPollingTime. (#3995, closes #3971)HTTP endpoints can take immutable request types
A
Before/BeforeAsyncmethod 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 immutablerecordrequest 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 commitAfterreads 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
Aftermethods are inserted at the front of that list. So anAftermethod 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.Use the
AfterCommit/AfterCommitAsyncconvention or[WolverineAfterCommit], on message handlers, sagas and HTTP endpoints. Parameters bind exactly asAfteralready does.The position is structural, not positional — frames go into a new
IChain.PostCommitPostprocessorslist 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:
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.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
EventsToAppendreturn typeWolverine.Marten.Events,Wolverine.Polecat.EventsandWolverine.Fisher.Eventsare 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.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.
These are the document side counterparts to the
IEventOperationscontracts 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.
Durability agents no longer assigned to nodes that cannot run them
A node started with
Durability.DurabilityAgentEnabled = falsenever registers the durability agent family, so it threwUnrecognized 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, andowner_id = 0outgoing 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 = falseon 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 injectedDbContext, 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.TryDetermineTransactionOwnerTypefor it, implemented only by EF Core.RabbitMQ
ListenToRabbitQueue("orders").DrainWaitForPrefetch()to let already prefetched messages finish rather than letting the broker requeue them.StopAsyncis not always terminal, and aBatchingChannelsilently discards a post after completion, so a delivery landing between the drain and the dispose latch vanished and was redelivered.code=541). This narrows the window and speeds recovery; it does not prevent the close, whose root cause is upstream in rabbitmq-dotnet-client.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).SendingAgentIdleTimeouthad 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 aWolverine.Http.Martenand aWolverine.Http.Polecatand 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 theschemaNameargument toPersistMessagesWithSqlite(), reached the message store as aschema.tablequalifier. SQLite has no user-defined schemas — the only names a plain connection knows aremain,temp, and whatever has beenATTACHed — so any value other thanmainemitted SQL against a database that never existed, and the host died on the first envelope write with:The two halves had disagreed all along. Weasel's
SqliteObjectNamedrops the schema from its qualified name, so the DDL had been creating a barewolverine_incoming_envelopeswhile the inherited DML asked for a qualified one; themaindefault 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:
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.
mainis the default and prefixes nothing, so every database provisioned before this release keeps its existingwolverine_*names. Only hosts that explicitly set a non-mainname 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.TransportSchemaNameis 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 documentedIdentifiedBy<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.SourceGeneratorinside its own nupkg, as Polecat already does. A project that also references that generator explicitly will get two analyzer instances and aCS0433duplicate-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 toStorage.Store(), expressed entirely againstJasperFx.Events.IEventOperations, so the same handler is valid on Marten, Polecat or Fisher with noIDocumentSession.[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 anIReadOnlyList<T>, and a rawIQueryable<T>escape hatch.OnMissing.EmptyContentWith204,[NoContentIfMissing]/[NotFoundIfMissing](#3931) — answer an empty 204 instead of a 404 when there is simply nothing to return.DateTime/DateTimeOffset nowin Wolverine.HTTP (#3932) — matches the long standing message handler convention. Previously such a parameter silently bound from the query string and arrived asdefault.Notable fix
An
IEventStoreOperations/IEventOperationshandler or endpoint parameter now resolves and commits (#3936).CanApplyrecognized no event operations type, soAutoApplyTransactionsskipped 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.0WriteAggregateAttributederives fromWriteModelAttributeand overrides neitherModifynorRequired, 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: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 unconditionalRequired = truethey have always had, in Marten, Polecat and Fisher alike. SayRequired = falseexplicitly, or move to[WriteModel]/[ReadModel], to opt out.If you are on 6.27.0 and use
[WriteAggregate]with a nullable parameter and no explicitRequired, upgrade.[ReadModel]takesRequiredfrom the parameter's nullable annotation (#3929)Matching what GH-3916 did for
[WriteModel]:Order orderis required and gets a not-found guard,Order? orderis not and is handed to your method asnullso your own null branch runs. An explicitRequiredat 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 unconditionalRequired = true. It is the oldest and most widely used of these attributes and is heavily used in HTTP endpoints, whereRequired = truewithOnMissing.Simple404is the documented 404 behaviour. Loosening it would turn a clean 404 into a runtimeNullReferenceExceptionin an endpoint body.[DeciderFunction]and[DcbModel]keepRequired = 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 toRequired = 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.FisherFisher — the embedded SQLite document database and event store — is now a first-class Wolverine persistence integration, alongside Marten and Polecat.
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:
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-agnosticThe 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.Wolverine.Marten.BoundaryModelAttributeandWolverine.Polecat.BoundaryModelAttributenow inherit from it and behave identically — existing[BoundaryModel]code needs no change. Prefer[DcbModel]in new code.[WriteModel]fixesRequirednow defaults from the parameter's nullable annotation (#3916).Order orderis required and gets a not-found guard;Order? orderis not, and is handed to your method asnullso your own null branch runs. A nullable annotation withRequired = truewas a contradiction that silently resolved in favour of the attribute default, making the handler's null branch dead code. SettingRequiredexplicitly still overrides the annotation either way.Required = trueexplicitly 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, thenid, 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: truemeans 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:
[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.SourceGeneratorexplicitly and reference Polecat, you may hitCS0433("the type<X>Evolverexists 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 — seePolecatTests.csprojfor theDropDuplicateBundledEventSourceGeneratortarget we use, which keeps the explicitly-pinned generator and removes the bundled duplicate.Two new packages
WolverineFx.DataAnnotationsValidationandWolverineFx.FluentValidation.Grpcare published for the first time in this release. Both were documented as installable but had never actually shipped — every version returnedBlobNotFoundfrom nuget.org — because each declared aPackageIdwhile 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 reportsIsTransactionalcorrectly. The Marten and Polecat aggregate handler workflows appended aSaveChangesAsyncpostprocessor but never setIChain.IsTransactional, so a chain reported having no transactional middleware while its generated code committed. The disagreement was visible toIHttpPolicyauthors, who had no reliable signal for whether a chain would commit — the workaround was an unusedIDocumentSessionparameter 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.
OutgoingMessageBatchbuilt its contiguousbyte[]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 inWolverine.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/PolecatOpsand the rest of each integration's surface stay exactly where they are.What landed:
AggregateHandling.DetermineVersionMembernow returnsMemberInfo?— Marten's copy used a null-forgiving operator that was masking a real null fromIAggregateVersioning.VersionMember. Polecat regains AOT annotations it had dropped. Two reflectively-closed codegen frames widen aclassconstraint tonotnull, matchingIEventStream<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 ofIPersistenceFrameProviderrather than new members on it, so stores without event sourcing never grow no-op aggregate members.Wolverine.Persistence.EventSourcing: the event-capture frames andDetermineEventCaptureHandling, all written purely againstJasperFx.Events'IEventStream<T>.Wolverine.<Store>integration, so aCS0104ambiguity 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
Beforemethod with aFinallymethod no longer produces aNullReferenceExceptionat codegen time — andFinallynow runs on the short-circuit path, as the middleware contract promises.Saga diagnostics tolerate an unprovisioned saga table (GH-3887, #3894)
DatabaseSagaStoreDiagnostics.ReadSagaAsync/ListSagaInstancesAsynctreat 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, sinceAddSagaTypeis optional.Polecat
TransportSchemaNameis honored (GH-3884, #3897)PolecatIntegration.TransportSchemaNameis 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'
DeliverByexpiry 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()registersMartenIntegrationas anIWolverineExtension, so itsConfigure()runs at host build — after an inlineUsePostgresqlPersistenceAndTransport(..., 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 onwolverine_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:TransportSchemaNamenow records whether it was explicitly assigned and is stamped onto the transport only then;MessageStorageSchemaNameis 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 onwolverine_queues.If you have been running Marten alongside
UsePostgresqlPersistenceAndTransportwith a customtransportSchema, check for a duplicatewolverine_queue_*table underwolverine_queues— that is undelivered mail, and it becomes reachable once you upgrade.Known related gap
PolecatIntegration.TransportSchemaNameis declared and documented but never applied — the mirror-image problem (inert rather than over-eager), so an explicit value there is silently ignored. Its siblingMessageStorageSchemaNameis 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.SetExternalTopologyforce-setEndpointMode.Durableon 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.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*Queuescall).EndpointMode.Inlineis 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
Full Changelog: JasperFx/wolverine@V6.24.10...V6.25.0
6.24.10
Small bug fix release: queue endpoints addressed only by Uri on the database-backed transports (SQL Server, PostgreSQL, SQLite, MySQL) now sanitize the queue name the same way the fluent API does, so a name like
sqlserver://my-service-controlno longer produces invalidwolverine_queue_*table DDL from the dash. This was uncovered by CritterWatch'ssystemControlUriusage in the field.What's Changed
Full Changelog: JasperFx/wolverine@V6.24.9...V6.24.10
6.24.9
This is mostly about CritterWatch uncovered issues with very high volumes of messaging via SQS and making the back pressure detection a bit more sophisticated
What's Changed
Full Changelog: JasperFx/wolverine@V6.24.8...V6.24.9
6.24.8
Bug fix release. Four durability and multi-tenancy fixes, all with regression coverage.
Fixes
#3856 — Dormant inbox rows for a durable local queue were never recovered (#3857)
PublishToPartitionedLocalMessaging()marks every slotListenerScope.Exclusive, and the GH-3590 carve-out then handed inbox recovery to a loop that is never constructed for a local queue — a local queue never gets aListeningAgentat all. Envelopes sat atstatus='Incoming',owner_id=0indefinitely, surviving rolling deploys. Both guards implementing that hand-off now ask a singleEndpoint.IsSingleNodeListenerpredicate, whichLocalQueueanswersfalse. Reported by @erdtsieck.#3815 —
forEveryDatabasevisited the main database twice (#3858)MultiTenantedMessageStore.ActiveDatabases()yieldsMainfirst, so on any multi-tenanted configuration the Oracle, PostgreSQL and MySQL queues counted the main database twice —GetAttributesAsync()reported a queue depth of 2 for a single row. Schema checks and purges also ran twice. SqlServer and Sqlite were already correct.#3859 — MySQL multi-tenanted queues shared one physical table (#3861)
A MySQL schema is a database, so the single
TransportSchemaNameresolved every tenant to the same queue table: no isolation, and counts that multiplied by the tenant count instead of summing. Queue tables now resolve inside each tenant's own database. Single-database hosts are unaffected.#3860 — MySQL database-per-tenant storage had no isolation (#3862)
The same root cause in the message stores: every tenant store received the one configured schema name, so inbox, outbox, dead letter, node and saga tables were shared across all tenants. Each tenant's database is now its own schema.
Upgrading
MySQL database-per-tenant users only. Before this release your tenant envelope rows all lived in the single configured schema. After upgrading, each tenant reads from its own database instead — drain or copy across any in-flight envelopes still sitting in the old shared tables before you upgrade. No other provider or configuration is affected.
Full changelog: JasperFx/wolverine@V6.24.7...V6.24.8
6.24.7
This is a fix release. Its centre of gravity is agent assignment: a leader that re-decided the same placements every cycle, and — hidden underneath that churn — a serial stop path that made every rebalance far slower than it needed to be.
Agent assignment converges much faster
#3852 — the leader re-decided placements it had already made. The GH-3698 pending-assignment ledger armed on a
ReassignAgentbut could never apply one: an agent being moved is still listed in its source node's persistedActiveAgents, so the guard that skips agents with a known original node skipped every reassignment. GH-3698 closed this hole for first-time placement and left it open for moves.On a 512-database / 5-node / ~8,700-agent cluster that reproduced as 3,468 decisions every cycle against a frozen snapshot, indefinitely — matching the ~45,000 decisions over six minutes reported from production. It converged in spite of itself, because the batched command carries set-based value equality and the dispatcher collapses an identical re-emitted batch while its lane is busy, so it read as benign. The telemetry was not deduplicated at all:
AssignmentsChangedfires before batching, so every one of those decisions wrote anAssignmentChangednode record.The churn was concealing a second defect.
StartAgentsgot bounded parallelism back in GH-3604 — a 50-agent chunk started one at a time was seconds of dead wall-clock that blew the reply window. The stop side is the same shape and never got it: a plainforeach, so atAgentStartBatchSize = 50an entire chunk's stop cost ran in series before a single start could cascade. It survived only because the per-cycle churn was trickling agents onto the destination alongside the batch. Fixing the churn exposed it.Measured against the 512-database reproduction:
Net 5.7x faster to converge than 6.24.6, not merely quieter.
#3850 — the cached node-number release is now bounded by a high-water mark, so a newcomer's messages cannot be released by a stale cache. Follow-up to GH-3846.
Node-number lookups happen once per node instead of once per database (#3847, thanks @erdtsieck) — a real saving on multi-database deployments, where the old shape scaled with the shard count.
Durability/projection affinity now reports whether it engaged
#3785 shipped in 6.24.5: a shard database's durability agent follows that database's event-subscription agents, so the database attracts one node's connection pool instead of two.
That join is deliberately fail-silent — a miss falls back to the even spread, because a miss is never wrong, only not-better. The problem is diagnostic: a join that never fires because the two descriptor pipelines spell the same database differently looks exactly like the feature working, minus the benefit. Verifying it meant joining
pg_stat_activityagainst the assignment table on a live cluster.It now says so directly, once, when the numbers change:
and escalates to a warning in the one unambiguous case — projection agents present, database-bearing durability agents present, zero matched. On a multi-database store that is a spelling divergence, not a coincidence. An application with no projections has nothing to follow and stays quiet.
Transport and listener fixes
#3832 — a deliberately paused listener now reports the distinct
ListeningStatus.Pausedinstead of being indistinguishable from back-pressureTooBusy. The contract now matches what the code actually does.#3842 —
RabbitMqListener.CreateAsyncno longer dereferences a nullChannelwhen the agent is disposed mid-startup.Testing and build
Description has been truncated