Marten 9.5.2; register MasterTenantSource in DI (#3016); empty master-table seed fix (#3019); foundational per-tenant-partitioned-events tests (#3018)#3020
Merged
Conversation
…-table seed fix (#3019); foundational per-tenant-partitioned-events tests (#3018); bump 6.4.2 - Bump Marten family 9.4.0 -> 9.5.2 (JasperFx 2.8.0 + Weasel 9.0.2 already satisfy its floors). - #3016: register the MasterTenantSource as IDynamicTenantSource<string> in DI when UseMasterTableTenancy() is configured, so store-agnostic admin consumers can drive the dynamic-tenancy lifecycle through the abstraction. Registered in the fluent UseMasterTableTenancy() method (config time, before the container is built) rather than in BuildMessageStore (runs lazily off the IMessageStore factory, post-build) or Configure() (Include() runs it eagerly, before the flag is set). The factory defers to IMessageStore resolution, which constructs the MasterTenantSource and assigns ConnectionStringTenancy. - #3019: SeedDatabasesAsync threw "CommandText property has not been initialized" when the master tenant table starts empty (no seeded tenants) — an empty assignment set appended no SQL, so the compiled command had no CommandText. Skip execution when there is nothing to seed. - #3018 (foundational slice): exercise the Wolverine aggregate-handler workflow against a Marten store with Events.UseTenantPartitionedEvents (Conjoined + Quick), on both string and guid stream identity: tenant-routed append lands in the correct partition; the same stream-id value in two tenants stays isolated; a no-tenant command falls to the default partition, isolated from named tenants. Finding: Marten 9.5.2 uses MANAGED partitions (AddMartenManagedTenantsAsync, MT002 if unregistered) — NOT the lazy 42P01 provisioning the issue assumed; the projected aggregate must be MultiTenanted; the default tenant needs an explicit (table-safe-suffixed) partition. Deferred matrix tracked on #3018. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
Jun 4, 2026
…hapes, WriteAggregate (#3021) (#3022) Extends the foundational single-store partitioned-events slice (#3020) with the remaining single-store aggregate-handler scenarios, each scoped by tenant against a Conjoined + Quick + UseTenantPartitionedEvents store (string identity), reusing TenantTally / PartitionedTenancyHost: - [ReadAggregate] reads the routed tenant's partition; Required=false returns null for a tenant with no such stream. - Every append return shape lands only in the routed tenant's partition: single event, IEnumerable<object>, Events, IAsyncEnumerable<object>. - [WriteAggregate] (optimistic) appends to the routed tenant and stays isolated from other tenants. Findings (folded into the #3021 checklist): - MartenOps.StartStream has no tenant overload (only Store/Insert/Update/Delete do) — StartStream uses the ambient session tenant. The issue's StartStream(id, tenantId, events) does not exist. - [WriteAggregate] Required=true -> 404 is an HTTP-endpoint concept; a message handler does not throw for a missing required aggregate. The Required/404 wrong-tenant-isolation case therefore belongs in the deferred HTTP cluster. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 8, 2026
Merged
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.
Closes #3016. Closes #3019. Starts #3018 (foundational slice; deferred matrix tracked on the issue). Bumps to 6.4.2.
Marten 9.5.2
Bumps the Marten family 9.4.0 → 9.5.2. Its floors (JasperFx 2.8.0, Weasel 9.0.2) are already on main, so this is a Marten-only bump. Validated by a full
wolverine.slnxRelease build + the Marten test runs below.#3016 — register
MasterTenantSourceasIDynamicTenantSource<string>When
UseMasterTableTenancy()is configured, theMasterTenantSourceis now resolvable from DI asIDynamicTenantSource<string>, so store-agnostic admin consumers (e.g. CritterWatch's tenant-management handlers) can drive Add/Disable/Enable/Remove through the abstraction without sniffing the concrete type.Registration point matters. The issue suggested registering inside
BuildMessageStore, but that runs lazily off theAddSingleton<IMessageStore>(s => ...)factory — after the container is built — so it would never reachhost.Services. AndConfigure()runs eagerly viaInclude(), before the fluent.UseMasterTableTenancy()sets the flag. So the registration goes in the fluentUseMasterTableTenancy()method itself (config time, pre-build); the factory defers toIMessageStoreresolution, which constructs theMasterTenantSourceand assignsConnectionStringTenancy.Tests (positive resolves
MasterTenantSource; negative — noUseMasterTableTenancy→ empty). Both verified fail-without/pass-with.#3019 — empty master-table seed crash
SeedDatabasesAsyncthrew"CommandText property has not been initialized"when the master tenant table starts empty (no seeded tenants): the empty assignment set appended no SQL, so the compiled command had noCommandText. Now it skips execution when there is nothing to seed (an empty master table is valid — tenants can be registered at runtime). Regression test boots a host withUseMasterTableTenancy(_ => {})andStartAsync— verified fail-without/pass-with.#3018 — foundational per-tenant-partitioned-events test slice
First Wolverine tests exercising the aggregate-handler workflow against
Events.UseTenantPartitionedEvents(Conjoined + Quick), on string and guid stream identity:Findings (these correct the issue's premises — see the issue checklist comment)
store.Advanced.AddMartenManagedTenantsAsync(...)before a tenant's first append, elseMT002— there is no lazy 42P01 provisioning. The "fresh tenant 42P01" item is therefore reframed.MultiTenanted()to match conjoined events ("Tenancy storage style mismatch" otherwise).*DEFAULT*is not a legal suffix).MT002— left out of this slice pending investigation.The full 2a/2b matrix (multi-node distribution, sharded DBs, HTTP endpoints, exclusive concurrency, blue/green, subscriptions/
RelayWithEventTenant) remains deferred — checklist on #3018.Validation
wolverine.slnxRelease build clean (net9.0 + net10.0), 0 warnings / 0 errors.PostgresqlTests(Register MasterTenantSource as IDynamicTenantSource<string> in DI when UseMasterTableTenancy is configured #3016 + MasterTableTenancy: SeedDatabasesAsync throws "CommandText property has not been initialized" when the master tenant table starts empty #3019): green (1 unrelated RabbitMQ replay flake in the broader run).MartenTests/AggregateHandlerWorkflow(71 incl. the new 5): green — confirms Marten 9.5.2 runtime + no discovery collisions from the new aggregate/handler types.🤖 Generated with Claude Code