Status: in-flight
Third slice of the dedup audit deliverable under pillar #214.
Prior slices: #218 (database-manipulation), #219 (events / projections).
Scope: multi-tenancy strategies cluster — anything in Marten or Polecat that fits Rule 1 (non-database) targeting JasperFx.MultiTenancy, or Rule 2 (database) targeting Weasel.Core.MultiTenancy.
Headline
This slice is mostly already done, much like the events/projections slice. The heavy consolidation of the multi-tenancy substrate — IHasTenantId, TenantId, TenantIdStyle, UnknownTenantIdException, dynamic / static tenant sources — has already happened in JasperFx.MultiTenancy. The strategy implementations (MasterTableTenancy, SeparateDatabaseTenancy, etc.) are database-coupled and rightfully stay product-side. The shared options shell (MasterTableTenancyOptions<T>) and the assignment strategies (HashTenantAssignment, SmallestTenantAssignment, ExplicitTenantAssignment, ITenantDatabasePool) already live in Weasel.Core.MultiTenancy.
The audit found exactly two Polecat-side rows that warrant migration, plus a low-priority ergonomic addition to JasperFx.
Active migrations (sub-issues filed)
Optional JasperFx-side ergonomic addition (low priority)
Already in JasperFx.MultiTenancy (no action needed)
The agent confirmed both products correctly consume these:
IHasTenantId — marker interface with string? TenantId { get; set; }
TenantId — value type
TenantIdStyle enum (CaseSensitive, ForceUpperCase, ForceLowerCase) + MaybeCorrectTenantId extension
UnknownTenantIdException (Marten consumes; Polecat doesn't yet — Row 2)
IDynamicTenantSource — dynamic tenant lookup
ITenantedSource — abstract tenanted source
Already in Weasel.Core.MultiTenancy (no action needed)
MasterTableTenancyOptions<T> (Marten's MasterTableTenancyOptions is a Postgres NpgsqlDataSource shim over it)
ExplicitTenantAssignment, HashTenantAssignment, SmallestTenantAssignment
IDatabaseSizingStrategy, ITenantAssignmentStrategy, ITenantDatabasePool
MasterTableMultiTenancyExtensions
PooledDatabase
Polecat doesn't currently use the master-table tenancy pattern, so there's no Polecat-side gap here.
Correctly product-specific (no migration)
Audited and confirmed these stay where they are — they encode product-specific storage primitives or use-cases without cross-product parallels:
Marten.Storage.ITenancy vs Polecat.Storage.ITenancy — different shapes. Marten's extends IDatabaseSource, IDisposable, IDatabaseUser and returns Marten.Storage.Tenant + IMartenDatabase; Polecat's exposes DatabaseCardinality, ConnectionFactory, PolecatDatabase. Both correctly DB-specific.
- Marten tenancy strategies —
DefaultTenancy, MasterTableTenancy, ShardedTenancy, SingleServerMultiTenancy, StaticMultiTenancy. All Postgres-specific.
- Polecat tenancy strategies —
DefaultTenancy, SeparateDatabaseTenancy. SQL-Server-specific.
Marten.Storage.TenancyStyle — Single, Conjoined. Marten-only; Polecat doesn't model conjoined tenancy as an enum.
Marten.Schema.Indexing.Unique.TenancyScope — Global, PerTenant. Index-tenancy scope; no Polecat equivalent.
Polecat.Internal.DatabaseCardinality — different concept from Marten's TenancyStyle; describes connection routing, not data-scoping.
Polecat.Linq.SqlGeneration.TenantInFilter — LINQ-tenancy filter. SQL-Server-specific.
Marten.Metadata.TenancyPolicy — Marten-only document-mapping policy hook.
Slice exit criteria
Method
Same as the prior slices: focused structural compare across marten/src/Marten/Storage/, marten/src/Marten/Metadata/, polecat/src/Polecat/Storage/, polecat/src/Polecat/Metadata/, polecat/src/Polecat/Exceptions/, jasperfx/src/JasperFx/MultiTenancy/, and weasel/src/Weasel.Core/MultiTenancy/. Per-row decisions follow the propose-and-ratify pattern: propose target home + rule classification; ratify in conversation; reflect above.
Next slice
The pillar #214 inventory has four remaining clusters that could be candidate next slices:
- Document store metadata / descriptors — most of this already moved during the CritterWatch descriptor work, but a sweep would confirm nothing slipped.
- Schema migration & DDL diffing — Weasel charter territory; lower-priority because divergence is unlikely (both products use Weasel for DDL).
- Async daemon abstractions — partially confirmed in the events slice; a focused re-audit could close any leftover gaps.
- Helper utilities /
JasperFx.Core overflow — any utility code that's grown product-side and is general-purpose.
Recommendation: document store metadata / descriptors as the next slice — the CritterWatch work was extensive and a verification pass closes the loop.
Headline
This slice is mostly already done, much like the events/projections slice. The heavy consolidation of the multi-tenancy substrate —
IHasTenantId,TenantId,TenantIdStyle,UnknownTenantIdException, dynamic / static tenant sources — has already happened inJasperFx.MultiTenancy. The strategy implementations (MasterTableTenancy,SeparateDatabaseTenancy, etc.) are database-coupled and rightfully stay product-side. The shared options shell (MasterTableTenancyOptions<T>) and the assignment strategies (HashTenantAssignment,SmallestTenantAssignment,ExplicitTenantAssignment,ITenantDatabasePool) already live inWeasel.Core.MultiTenancy.The audit found exactly two Polecat-side rows that warrant migration, plus a low-priority ergonomic addition to JasperFx.
Active migrations (sub-issues filed)
Row 1 — Polecat
ITenantedshould extendIHasTenantIdMarten's
Marten.Metadata.ITenanted : IHasTenantIdalready extends the canonical JasperFx marker (a one-line implementation). Polecat'sPolecat.Metadata.ITenantedre-declares its ownstring TenantId { get; set; }property without extendingIHasTenantId, so Polecat-side framework code can't accept arbitraryIHasTenantIdvalues. Migration: Polecat'sITenantedextendsIHasTenantId(drop the localTenantIdproperty since the base contributes it). Rule 1.→ polecat#TBD (filed below)
Row 2 — Polecat
UnknownTenantException→JasperFx.MultiTenancy.UnknownTenantIdExceptionPolecat has
Polecat.Exceptions.UnknownTenantExceptionwith aTenantIdproperty and a Polecat-specific message. JasperFx hasJasperFx.MultiTenancy.UnknownTenantIdExceptionwith a generic message and noTenantIdproperty. Marten already consumes the JasperFx one directly (seeMarten.Storage.ITenancy.GetTenantXML doc). Migration: Polecat drops the local exception; consumes the JasperFx version. Rule 4 (exception duplicated across products) + Rule 1.→ polecat#TBD (filed below)
Optional JasperFx-side ergonomic addition (low priority)
TenantIdproperty toJasperFx.MultiTenancy.UnknownTenantIdExceptionPolecat's local exception carries a
TenantIdgetter; the canonical JasperFx one does not. The id is in the message string but isn't programmatically accessible. Adding it is additive and unblocks Polecat consumers that want to catch + inspect. Worth doing alongside the Polecat migration so the Polecat side doesn't regress on diagnostics.→ can be folded into Row 2's Polecat PR via a small JasperFx-side companion, or filed as a standalone JasperFx issue if preferred.
Already in JasperFx.MultiTenancy (no action needed)
The agent confirmed both products correctly consume these:
IHasTenantId— marker interface withstring? TenantId { get; set; }TenantId— value typeTenantIdStyleenum (CaseSensitive,ForceUpperCase,ForceLowerCase) +MaybeCorrectTenantIdextensionUnknownTenantIdException(Marten consumes; Polecat doesn't yet — Row 2)IDynamicTenantSource— dynamic tenant lookupITenantedSource— abstract tenanted sourceAlready in Weasel.Core.MultiTenancy (no action needed)
MasterTableTenancyOptions<T>(Marten'sMasterTableTenancyOptionsis a PostgresNpgsqlDataSourceshim over it)ExplicitTenantAssignment,HashTenantAssignment,SmallestTenantAssignmentIDatabaseSizingStrategy,ITenantAssignmentStrategy,ITenantDatabasePoolMasterTableMultiTenancyExtensionsPooledDatabasePolecat doesn't currently use the master-table tenancy pattern, so there's no Polecat-side gap here.
Correctly product-specific (no migration)
Audited and confirmed these stay where they are — they encode product-specific storage primitives or use-cases without cross-product parallels:
Marten.Storage.ITenancyvsPolecat.Storage.ITenancy— different shapes. Marten's extendsIDatabaseSource, IDisposable, IDatabaseUserand returnsMarten.Storage.Tenant+IMartenDatabase; Polecat's exposesDatabaseCardinality,ConnectionFactory,PolecatDatabase. Both correctly DB-specific.DefaultTenancy,MasterTableTenancy,ShardedTenancy,SingleServerMultiTenancy,StaticMultiTenancy. All Postgres-specific.DefaultTenancy,SeparateDatabaseTenancy. SQL-Server-specific.Marten.Storage.TenancyStyle—Single,Conjoined. Marten-only; Polecat doesn't model conjoined tenancy as an enum.Marten.Schema.Indexing.Unique.TenancyScope—Global,PerTenant. Index-tenancy scope; no Polecat equivalent.Polecat.Internal.DatabaseCardinality— different concept from Marten'sTenancyStyle; describes connection routing, not data-scoping.Polecat.Linq.SqlGeneration.TenantInFilter— LINQ-tenancy filter. SQL-Server-specific.Marten.Metadata.TenancyPolicy— Marten-only document-mapping policy hook.Slice exit criteria
Method
Same as the prior slices: focused structural compare across
marten/src/Marten/Storage/,marten/src/Marten/Metadata/,polecat/src/Polecat/Storage/,polecat/src/Polecat/Metadata/,polecat/src/Polecat/Exceptions/,jasperfx/src/JasperFx/MultiTenancy/, andweasel/src/Weasel.Core/MultiTenancy/. Per-row decisions follow the propose-and-ratify pattern: propose target home + rule classification; ratify in conversation; reflect above.Next slice
The pillar #214 inventory has four remaining clusters that could be candidate next slices:
JasperFx.Coreoverflow — any utility code that's grown product-side and is general-purpose.Recommendation: document store metadata / descriptors as the next slice — the CritterWatch work was extensive and a verification pass closes the loop.