Releases: TheCraftyMaker/wolverine-mongodb
Release list
v1.0.0
Added
- Generic entity persistence (
[Entity],Insert<T>/Update<T>/Store<T>/Delete<T>,
IStorageAction<T>). MongoDB now implements Wolverine's generic persistence surface for
any plain document type, not justSagasubclasses — closing the one functional gap vs
Cosmos and RavenDb.[Entity]handler parameters load a document by id before the handler
runs; returningInsert/Update/Store/Delete<T>or anIStorageAction<T>persists it
afterward, atomically with the outbox on the same MongoDB transaction session.CanPersistis now unconditionaltrue(previously scoped toSagasubclasses) —
the saga-vs-entity distinction moved into the frame factories, which branch on
variable.VariableType.CanBeCastTo<Saga>(). Saga behavior, OCC, and collection naming
are unchanged.- Collection naming: one un-prefixed collection per entity type,
<lowercased-type-name>(e.g.OrderNote→ordernote) — distinct from sagas'
wolverine_saga_prefix, since entity collections are application data. - Write semantics:
Insert/Update/Storeall upsert (ReplaceOneAsync(IsUpsert=true),
matching Cosmos); no optimistic concurrency for plain entities — use the repository
pattern for app-controlled OCC. The entity's_idis extracted via the MongoDB driver's
class map (BsonClassMap...IdMemberMap), not a.ToString()coercion. - Coverage: Wolverine's upstream
StorageActionCompliancesuite passes (all facts) via
storage_action_compliance.cs. Custom tests cover entity write + outbox atomicity, and
saga/entity coexistence in the same handler (frame-branching regression guard). Full
single-node suite green on net9.0 + net10.0; cross-node entity persistence verified under
DurabilityMode.Balanced. - Demo:
OrderNoteHandlerdemonstratesInsert/[Entity]+Update/[Entity]+Delete
against a realOrderNotedocument, wired toPOST/DELETE /orders/{id}/notesendpoints.
- Saga store diagnostics (
ISagaStoreDiagnostics). MongoDB now implements Wolverine's
read-only saga-explorer surface — matching RavenDb, and above Cosmos, which does not
implement it — so CritterWatch and other monitoring tools can list the Mongo-owned saga
types, read a single saga instance by id, and peek at recent instances. Registered
automatically byUseMongoDbPersistence. Reads run against thewolverine_saga_<type>
collections with native_idmatching (no string coercion);countis clamped to
[0, 1000]; descriptors are tagged"MongoDb". Registration does not affect startup or
the existing inbox/outbox/saga behavior (full single-node suite green on net9.0 + net10.0). MongoDbUnitOfWorkdemo example.RecordOrderAuditHandlershows the no-repository
write path — a handler that acceptsMongoDbUnitOfWorkdirectly and writes through
Collection<T>(name), with the session threaded automatically — alongside the existing
repository +IClientSessionHandleexample, wired toPOST /orders/{id}/audit.- Saga-cascade read-model consumer in the demo.
FulfillmentStatusProjectorconsumes
OrderFulfillmentSaga'sFulfillmentShippedEvent/FulfillmentCompletedEventcascades via
a durable local queue and maintains afulfillment_delivery_statusesread model, exercising
the full saga → outbox → consumer path end to end.
Changed
- Multinode leadership compliance is no longer compile-gated. The upstream
LeadershipElectionCompliancesuite (previously behind#if RUN_MULTINODEbecause earlier
WolverineFx releases required a leadership-race ordering guarantee this provider's
w:majoritylock could not make) now runs unconditionally as part of CI's multinode step,
after WolverineFx 6.9.0 reworked the underlying facts around the "any healthy node leads"
model this provider already implements. Verified 5× consecutive green on net9.0 and net10.0
before un-gating.
v0.1.0-beta.7
Added
- MongoDB saga persistence. Stateful Wolverine sagas (
Sagasubclasses) are now
persisted in MongoDB via the provider's code-generation contracts (IPersistenceFrameProvider).
Each saga type gets its own collection namedwolverine_saga_<lowercased-type-name>
(e.g.wolverine_saga_orderfulfillmentsaga). Collections are created automatically on
startup.- Supported id types:
Guid,string,int, andlong— stored natively as the
corresponding BSON type (not coerced to string as in Cosmos/RavenDb). - Optimistic concurrency via
Saga.Version: insert stampsVersion = 1; update
uses a guardedReplaceOneAsyncon(_id, oldVersion)and throws
SagaConcurrencyExceptionwhenModifiedCount == 0. Delete on completion is
unguarded, matching Wolverine's lightweight SQL provider. - Atomic with the outbox: saga state writes and outbox entries commit in the same
MongoDB multi-document transaction as the handler's domain writes. - Coverage: Wolverine's upstream compliance suites (
StringIdentifiedSagaComplianceSpecs,
GuidIdentifiedSagaComplianceSpecs,IntIdentifiedSagaComplianceSpecs,
LongIdentifiedSagaComplianceSpecs) pass — 27 compliance facts across 4 id types on
net9.0 + net10.0. Custom tests cover atomicity (rollback saga + outbox on failure),
completion delete, OCC conflict, and inbox idempotency. Cross-node saga correctness
verified with five consecutive green runs ofsaga_multinode.cson both TFMs.
- Supported id types:
OrderFulfillmentSagain the demo. The demo now includes a saga that tracks an
order through placement, shipping, and delivery confirmation. Seven integration tests
cover: start, continue, complete (doc deleted), missing-state (UnknownSagaException),
duplicate-message idempotency, across-restart state survival, and saga/projector
coexistence viaMultipleHandlerBehavior.Separated.
Changed
- Upgraded the WolverineFx baseline from 6.2.2 to 6.9.0. Bumped
WolverineFx
andWolverineFx.ComplianceTestsinDirectory.Packages.propsand moved the
pinnedexternal/wolverinecompliance submodule toV6.9.0, so the library is
now built, tested, and packaged against the same WolverineFx version consumers
run. This fixes saga persistence under WolverineFx newer than 6.2.2: a
library compiled against 6.2.2 was not selected as the saga persistence provider
at runtime under 6.9.0 — the saga handler ran but its state was never persisted
(the inbox/outbox path was unaffected, which is why the regression went
unnoticed until a saga ran against a newer runtime). Verified against 6.9.0 on
net9.0 and net10.0: the full single-node compliance suite (150 tests) and the
multinode end-to-end message-guarantee tests (multinode_end_to_end) pass. The
multinode leadership-election compliance facts remain compile-gated behind
RUN_MULTINODEby deliberate decision (seeFOLLOWUPS.md) and are not part of
the automated run.
v0.1.0-beta.6
Added
DurabilityMode.Balanced(multinode) support. Multiple nodes can now run
against the same MongoDB store. Requiresopts.UseTcpForControlEndpoint()(or
any control endpoint) and synchronized node clocks. Startup emits anInformation
log message confirming the mode instead of throwing.MongoDbPersistenceOptions— MongoDB-specific persistence tuning. Pass a
configure callback toUseMongoDbPersistenceto setLockLeaseDuration
(default 1 minute). Example:
opts.UseMongoDbPersistence("db", mongo => mongo.LockLeaseDuration = TimeSpan.FromSeconds(30)).DeleteOldNodeRecordsAsyncimplementation. The leader now trims old
node-event records by retain count (DeleteOldNodeRecordsAsync(int)). The
TTL index onwolverine_node_recordsremains a 14-day backstop.- Dead-node ownership release in
DurabilityMode.Balanced. Each recovery
tick releases incoming and outgoing envelope ownership held by node numbers
that have no live node document (crashed nodes), then re-runs orphan recovery
so rescued envelopes are re-claimed in the same tick. - Cross-node message-guarantee tests (
multinode_end_to_end.cs). Two in-proc
Balanced-mode hosts verify: (1) a scheduled message executes exactly once
across competing nodes; (2) a survivor releases and recovers envelopes owned by
a dead node. Both facts verified with five consecutive green runs on net9.0 and
net10.0. - CI runs the multinode test category as a separate step. The
libraryjob
now runsCategory!=multinodeandCategory=multinodeas distinct steps so a
cross-node flake is immediately distinguishable from a core regression. - Demo config-driven durability mode with multinode runbook. The demo API
readsWolverine:DurabilityModefrom configuration (defaultSolo); set it to
Balancedto run multiple instances against the same MongoDB and RabbitMQ.
Seedemo/README.mdfor the two-instance runbook.
Changed
Behavior change: Leader lock lease default changed from 5 minutes to 1 minute.
The previous 5-minute default made leader failover unacceptably slow and was
the root cause of leadership compliance suite flakiness. The new default of
1 minute provides reasonable failover speed for most deployments. Tune via
MongoDbPersistenceOptions.LockLeaseDuration if needed.
Behavior change:
DurabilityMode.Balancedno longer throws at startup.
Previously, Initialize, StartScheduledJobs, and BuildAgent threw
InvalidOperationException if DurabilityMode.Balanced was detected. These
now log an Information message and continue — the host starts normally.
DurabilityMode.Solo still works as before; no changes needed for existing
single-node deployments.
Fixed
- CAS-guarded outgoing recovery prevents cross-node double-claims. When two
nodes race to recover the same orphaned outgoing envelopes, the second node's
claimUpdateManynow carries anOwnerId == AnyNodefilter guard. After the
update, only envelopes this node actually won (confirmed by a re-read) are
enqueued — preventing duplicate sends. LoadOutgoingAsyncnow returns only globally-owned envelopes, batch-limited.
Previously the query filtered by destination only, which caused orphan recovery
to re-claim in-flight envelopes (duplicate sends) and load unbounded result sets.
The query now filtersOwnerId == 0and appliesLimit(RecoveryBatchSize),
mirroring all RDBMS providers.- Handled inbox markers carry
KeepUntilfor TTL expiry.
IncomingMessagepreviously droppedenvelope.KeepUntil, leaving handled markers
with no expiry — the TTL index never fired and the inbox grew without bound.
Both the lazy (StoreIncomingAsync) and eager (PersistIncomingAsync) paths
now preserveKeepUntil. - Dead-letter replay is now idempotent and per-document fault-tolerant.
A crash between the re-insert and the DLQ delete previously left the next replay
tick throwingDuplicateIncomingEnvelopeException, aborting the whole batch
permanently. The loop now catches the duplicate and falls through to delete the
DLQ document, converging the state. Body-less poison dead letters are unflagged
(not retried every tick) and remain queryable. - Write concerns pinned on the message store.
The store constructor now wraps its database handle with
WriteConcern.WMajority.With(journal: true)andReadConcern.Majority,
independent of the consumer'sMongoClientconfiguration. Aw:1client no
longer weakens inbox/outbox durability. - Transaction frame applied to
IMongoCollection<T>,IMongoClient, and
IClientSessionHandlehandlers. Previously only handlers whose dependency
tree containedIMongoDatabasereceived the transactional frame. Handlers
injectingIMongoCollection<T>silently ran without a transaction; handlers
declaringIClientSessionHandledirectly failed code generation.
Added
- CI runs the full compliance test suite on every PR. The
libraryjob checks
out the Wolverine source at tagV6.2.2and runs
dotnet test src/Wolverine.MongoDB.TestswithUseWolverineSource=true. The
demojob downloads the freshly packed nupkg (0.0.0-ci) and runs the
end-to-end integration tests against it, so no stale NuGet version is exercised. MongoDbUnitOfWork— session-bound write helper. Handlers can accept
MongoDbUnitOfWorkas a parameter instead of (or alongside)IClientSessionHandle.
Writes throughuow.Collection<T>("name")automatically participate in the
handler's transaction — the session cannot be forgotten.SessionBoundCollection<T>
exposesInsertOneAsync,InsertManyAsync,ReplaceOneAsync,UpdateOneAsync,
UpdateManyAsync,DeleteOneAsync,DeleteManyAsync,FindOneAndUpdateAsync,
andFind.- Compound and TTL indexes on all envelope collections. New indexes:
- Incoming:
(Status, ExecutionTime)for scheduled-message poll;EnvelopeId
for reassignment/reschedule;(OwnerId, ReceivedAt)for orphan recovery;
KeepUntilTTL. - Outgoing:
(OwnerId, Destination)serving the fixedLoadOutgoingAsync;
existingDestinationandDeliverByindexes retained. - Dead letters:
ExpirationTimeTTL (no-op when field absent, i.e. expiration
disabled);SentAt,MessageType,ExceptionType,Replayableindexes. - Node records:
TimestampTTL (14-day retention).
- Incoming:
- Server-side aggregation for
SummarizeAllAsync/SummarizeAsync. Dead-letter
and scheduled-message summary methods now use$grouppipelines instead of
loading all documents into the application process. - Release automation:
releaseagent + GitHub Releases. A
.claude/agents/release.mdagent proposes the next version, prepares a CHANGELOG +
version-bump PR (gated on human approval and merge), then tags, monitors the publish
workflow, and verifies the NuGet push and the GitHub Release.publish.ymlnow
creates a GitHub Release from the released version'sCHANGELOG.mdsection, extracted
by.github/scripts/extract-changelog.sh.
Changed
Behavior change: Dead letters no longer expire by default.
Previously, MoveToDeadLetterStorageAsync unconditionally stamped ExpirationTime,
causing TTL deletion after 10 days under the library's default settings — silent
data loss. Now, ExpirationTime is only written when
opts.Durability.DeadLetterQueueExpirationEnabled = true (Wolverine's default is
false). Existing deployments that relied on automatic expiry must opt in explicitly.
Behavior change: Startup now throws on
DurabilityMode.Balanced.
Wolverine.MongoDB only supports single-node (DurabilityMode.Solo) deployments.
Previously, a consumer who forgot to set Solo got a subtly broken cluster.
Initialize, StartScheduledJobs, and BuildAgent now throw
InvalidOperationException if DurabilityMode.Balanced is detected. Set
opts.Durability.Mode = DurabilityMode.Solo in your host configuration.
- Per-property BSON
DateTimerepresentation instead of a process-global serializer.
AllDateTimeOffset/DateTimeOffset?fields on document types are now annotated
with[BsonRepresentation(BsonType.DateTime)]. TheMongoSerializerRegistration
class and its[ModuleInitializer]call have been removed. The library no longer
mutates the host application's BSON registry. - Release flow bumps version + CHANGELOG before tagging.
Directory.Build.props
and theCHANGELOG.mdversion section are now set in the release PR onmain
before the tag is pushed, so the tagged commit is self-consistent. The previous
post-publish auto-bump PR has been removed.