Repository navigation
Wolverine 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)
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)- 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 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) - 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: V6.30.3...V6.31.0