Wolverine 6.24.1
Patch release. Four reported issues, all with reproductions from production clusters.
Fixes
#3701 — wolverine_node_records grows without bound (#3734)
A reporting cluster reached 36,135,221 rows / 16 GB in five days on a diagnostic table nothing on the hot path reads. Three distinct defects:
INodeAgentPersistence.DeleteOldNodeRecordsAsyncwas implemented for every relational store and never invoked outside tests.- The pruning that did run bounds the table by age only (
NodeEventRecordExpirationTime, 5 days), which is no ceiling at all at high write rates — every one of those 36M rows was inside the window. - That age sweep's hourly throttle was dead. Its backing field was never assigned (the
CS0649suppression on it said so), so a full-table delete went out on every recovery cycle — every 5 seconds by default.
New Durability.NodeRecordRetention (default 10,000 rows) and Durability.NodeRecordPruningPeriod (default hourly). MultiTenantedMessageStore now delegates the trim to the main store instead of inheriting a no-op default, and Sqlite, MySQL and Oracle gained implementations they had also been missing.
#3697 — no supported force-catch-up under Wolverine-managed event subscription distribution (#3735)
Wolverine already implemented the coordinator-driven catch-up path, but only exposed it as a TrackActivity() stage. Adds the standalone entry point on IHost and IServiceProvider, plus <T> ancillary-store variants:
await host.PauseThenCatchUpOnMartenDaemonActivityAsync();
await host.PauseThenCatchUpOnMartenDaemonActivityAsync(CatchUpMode.AndDoNothing);
await host.PauseThenCatchUpOnMartenDaemonActivityAsync<IMyStore>();It never calls IProjectionDaemon.CatchUpAsync — doing so under a live coordinator is what produces the ProgressionProgressOutOfOrderException and pk_mt_event_progression duplicate-key errors suites have been retrying around. Resuming the agents that already own the shards means there is only ever one writer.
#3733 — a comma in an agent Uri voided a whole batch confirmation (#3736)
AgentsStarted, StartAgents, AgentsStopped and StopAgents joined their Uri[] on a comma, which RFC 3986 permits unescaped in a path segment. Agent URIs embed tenant ids and projection names, so one comma shattered an agent into fragments — and because the read side built the array in a single projection, the resulting throw took out the confirmation for the entire batch. Newline is the delimiter now, and entries are parsed individually so a bad one names itself.
The comma remains the default on the wire for payloads that do not contain one, so rolling upgrades keep working in both directions.
#3706 — RabbitMQ acks were cumulative (#3737)
Every ack went out as BasicAckAsync(tag, multiple: true), acknowledging every lower delivery tag on the channel. That is only correct when completions happen in delivery order, and they do not with ConsumerDispatchConcurrency > 1 — acking one message silently acknowledged deliveries whose handlers were still running, and a crash at that moment lost them.
Acks are now per message. Two dead-letter paths that relied on the cumulative sweep settle themselves, most importantly the un-mappable-message branch in WorkerQueueMessageConsumer, which dead-lettered and returned without touching the delivery at all. This unblocks the planned native-ack parallel endpoint mode.