Tracking issue for the conjoined EF Core multi-tenancy epic: ITenanted on an EF entity makes it conjoined-multi-tenant through Wolverine — mapped tenant_id, tenant-bound global query filter, stamp-on-insert, cross-tenant write rejection, opt-in Weasel-managed physical partitioning, and CritterWatch tenant management out of the box. Behaviorally compliant with what Marten (and Polecat) do for conjoined tenancy today.
Motivating context: https://barretblake.dev/posts/development/2026/07/multi-tenant-part-1/ — hand-rolled shared-database tenancy in EF Core 10 (tenant column, named query filters everyone must remember, raw partition DDL smuggled into EF migrations, "one forgotten IgnoreQueryFilters() and Party A is reading Party B's mail"). Every one of those pain points is something the critter stack already automates for Marten; this epic brings it to EF Core.
Decisions
- The
ITenanted marker is shared critter-stack-wide from JasperFx.MultiTenancy (Marten/Polecat re-point their identical local markers).
- SQL Server gets physical partitioning in v1 via
Weasel.SqlServer.ManagedTenantPartitions; a follow-up epic brings Polecat to full Marten per-tenant-partitioning parity.
- Tenant lifecycle (enable/disable, authoritative tenant list) lives in a Wolverine-owned
wolverine_tenants registry table, not in Weasel's partition control tables.
- Conjoined sagas are in scope.
Work items
Upstream prerequisites
Wolverine phases
Companions / follow-ups
Sequencing
jasperfx#531 (ITenanted) ─┐
weasel#362 (SqlSvr gaps) ─┼─> Marten/Polecat pin bumps
│
#3462 Phase 1 ─────────────┤ needs jasperfx#531 only
#3463 Phase 2 ─────────────┤ PG ready now; SqlServer needs weasel#362
#3464 Phase 3 ─────────────┤ needs Phase 1; CritterWatch#720 in parallel
Phase 4 compliance/docs ───┤
│
polecat#335 parity epic ───┘ needs weasel#362; parallel to Phases 2–4
Verify-early risks
- EF
FindAsync vs global query filters (saga load correctness) — spike first.
- EF filter caching vs per-context tenant state; interplay with EF 10 named filters.
- Phase 2 composite-PK rewrite changes keyed-load call shapes for user code — needs loud docs/diagnostics.
- SQL Server ordinal stamping needs a synchronous-safe, pre-hydrated lookup (Polecat
TenantEventSequenceRegistry pattern).
- Marten conjoined + EF conjoined in one database must coexist (
mt_tenant_partitions vs wolverine_tenant_partitions; CritterWatch fans out to both sources by design).
Open naming/API questions
- Registration API:
AddDbContextWithWolverineManagedConjoinedTenancy<T> vs something shorter (AddDbContextWithConjoinedTenancy<T>?).
- Table names
wolverine_tenants / wolverine_tenant_partitions in the durability schema.
- Partitioned sagas: globally-unique saga ids vs per-tenant id reuse (composite identity).
- Cross-tenant write exception naming.
- Plain conjoined (no partitioning) stays EF-migrations-friendly, or also requires Wolverine-managed migrations?
Full analysis and phase detail: CONJOINED-TENANCY-EPIC-PLAN.md (repo root, working doc).
Tracking issue for the conjoined EF Core multi-tenancy epic:
ITenantedon an EF entity makes it conjoined-multi-tenant through Wolverine — mappedtenant_id, tenant-bound global query filter, stamp-on-insert, cross-tenant write rejection, opt-in Weasel-managed physical partitioning, and CritterWatch tenant management out of the box. Behaviorally compliant with what Marten (and Polecat) do for conjoined tenancy today.Motivating context: https://barretblake.dev/posts/development/2026/07/multi-tenant-part-1/ — hand-rolled shared-database tenancy in EF Core 10 (tenant column, named query filters everyone must remember, raw partition DDL smuggled into EF migrations, "one forgotten
IgnoreQueryFilters()and Party A is reading Party B's mail"). Every one of those pain points is something the critter stack already automates for Marten; this epic brings it to EF Core.Decisions
ITenantedmarker is shared critter-stack-wide fromJasperFx.MultiTenancy(Marten/Polecat re-point their identical local markers).Weasel.SqlServer.ManagedTenantPartitions; a follow-up epic brings Polecat to full Marten per-tenant-partitioning parity.wolverine_tenantsregistry table, not in Weasel's partition control tables.Work items
Upstream prerequisites
ITenantedintoJasperFx.MultiTenancy(leads the lockstep release)ManagedTenantPartitionsparity: data-removing drop, ordinal bucketing, new-table back-fill, batch statusWolverine phases
WolverineModelCustomizerconvention, stamping/guarding interceptor, conjoinedIDbContextBuilder<T>, conjoined sagas). Needs Ability to configure the outgoing message batch size for all endpoint types that batch send #531 only; independently shippable.wolverine_tenantsregistry +ConjoinedTenantSource : IDynamicTenantSource<string>+ descriptor plumbing for CritterWatch.TenantIdStyle/stamping/hydration/tenant-scoped deletes/cross-tenant rejection),ConjoinedMultiTenantedEfCoresample app, docs page, announcement post.Companions / follow-ups
DbContextUsagedescriptors, conjoined-aware add/hard-delete handlingSequencing
Verify-early risks
FindAsyncvs global query filters (saga load correctness) — spike first.TenantEventSequenceRegistrypattern).mt_tenant_partitionsvswolverine_tenant_partitions; CritterWatch fans out to both sources by design).Open naming/API questions
AddDbContextWithWolverineManagedConjoinedTenancy<T>vs something shorter (AddDbContextWithConjoinedTenancy<T>?).wolverine_tenants/wolverine_tenant_partitionsin the durability schema.Full analysis and phase detail:
CONJOINED-TENANCY-EPIC-PLAN.md(repo root, working doc).