Skip to content

Master-table and sharded tenancy: the tenancy's own AutoCreate picks the apply mode, not only whether to apply - #5692

Merged
jeremydmiller merged 1 commit into
JasperFx:masterfrom
lahma:fix/tenancy-autocreate
Oct 19, 2026
Merged

jeremydmiller merged 1 commit into
JasperFx:masterfrom
lahma:fix/tenancy-autocreate

Conversation

@lahma

@lahma lahma commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Problem

The master-table and sharded tenancies each have an AutoCreate setting for their own lookup tables, and both are documented as an override of the store's setting:

  • MasterTableTenancyOptions.AutoCreate (Weasel): "If set, this will override the AutoCreate setting for just the master tenancy table". The sample_master_table_multi_tenancy snippet in docs/configuration/multitenancy.md says the same: "If set, this will override the database schema rules for only the master tenant table from the parent StoreOptions".
  • ShardedTenancyOptions.AutoCreate: "Override the AutoCreate setting for the master database tables. If null, uses the store's AutoCreateSchemaObjects setting."

Both maybeApplyChanges implementations use the setting only to decide whether to apply. The apply itself always runs under the store's mode:

if (!_hasAppliedChanges && (_configuration.AutoCreate ?? _options.AutoCreateSchemaObjects) != AutoCreate.None)
{
    await tenantDatabase.ApplyAllConfiguredChangesToDatabaseAsync(_options.AutoCreateSchemaObjects)...

The documented example uses store None with tenancy CreateOrUpdate. It works only because Weasel promotes an explicit None apply to CreateOrUpdate. Every other combination ignores the override:

store AutoCreateSchemaObjects tenancy AutoCreate master tables migrated under configured
None CreateOrUpdate CreateOrUpdate, by the None promotion CreateOrUpdate
CreateOnly CreateOrUpdate CreateOnly: any drift throws SchemaMigrationException CreateOrUpdate
CreateOrUpdate CreateOnly CreateOrUpdate: alters the table, and drops columns it doesn't declare CreateOnly
All CreateOrUpdate All CreateOrUpdate
CreateOrUpdate All CreateOrUpdate All

A tenancy set to None does skip the apply, as documented; only the mode was wrong.

The All row carries the most risk. Under All, Weasel drops and recreates a table it cannot alter. For the master table, that removes every tenant's row, which is exactly what a host that set the tenancy to CreateOrUpdate meant to rule out.

The CreateOnly row breaks host startup on upgrade. disabled was added to mt_tenant_databases in 8.37.3 and to mt_tenant_assignments in #4607. A host whose master table predates that column fails at startup if it runs store CreateOnly with tenancy CreateOrUpdate, which is the configuration the override exists for. The cause is that MartenActivator.StartAsync calls Tenancy.BuildDatabases() before any explicit apply. So ApplyAllDatabaseChangesOnStartup() doesn't help: BuildDatabases() throws first.

Fix

Compute _configuration.AutoCreate ?? _options.AutoCreateSchemaObjects once, and use it for both the gate and the apply. The change is the same in MasterTableTenancy and ShardedTenancy.

Tests

MultiTenancyTests/tenancy_auto_create_override.cs has two facts per tenancy, one per direction. Each starts from the lookup table as it was before the disabled column: the current shape, built by a first store, with disabled then dropped.

  • Tenancy CreateOrUpdate over store CreateOnly: BuildDatabases() migrates the table, the column is back, and the table works again (GetTenantAsync / AllDisabledAsync read the column).
  • Tenancy CreateOnly over store CreateOrUpdate: BuildDatabases() refuses with SchemaMigrationException, as the store's own CreateOnly does for a document table, and the table is left untouched.

Red, on unmodified master (2ece3a9) with only the test file added:

failed sharded_tenancy_auto_create_override.create_only_on_the_tenancy_is_not_overruled_by_a_create_or_update_store
  Shouldly.ShouldAssertException : Task `async () => { await tenancy.BuildDatabases(); }`
      should throw
  Weasel.Core.SchemaMigrationException
      but did not
failed sharded_tenancy_auto_create_override.create_or_update_on_the_tenancy_migrates_the_pool_tables_when_the_store_is_create_only
  Weasel.Core.SchemaMigrationException : Cannot derive schema migrations for TableDelta for sharded_auto_create.mt_tenant_assignments AutoCreate.CreateOnly
    at Weasel.Core.SchemaMigration.AssertPatchingIsValid(AutoCreate autoCreate)
    ...
    at Marten.Storage.ShardedTenancy.maybeApplyChanges() in src/Marten/Storage/ShardedTenancy.cs:1070
    at Marten.Storage.ShardedTenancy.BuildDatabases() in src/Marten/Storage/ShardedTenancy.cs:246
failed master_table_tenancy_auto_create_override.create_only_on_the_tenancy_is_not_overruled_by_a_create_or_update_store
  Shouldly.ShouldAssertException : Task `async () => { await tenancy.BuildDatabases(); }`
      should throw
  Weasel.Core.SchemaMigrationException
      but did not
failed master_table_tenancy_auto_create_override.create_or_update_on_the_tenancy_migrates_the_master_table_when_the_store_is_create_only
  Weasel.Core.SchemaMigrationException : Cannot derive schema migrations for TableDelta for master_auto_create.mt_tenant_databases AutoCreate.CreateOnly
    at Weasel.Core.SchemaMigration.AssertPatchingIsValid(AutoCreate autoCreate)
    ...
    at Marten.Storage.MasterTableTenancy.maybeApplyChanges(TenantLookupDatabase tenantDatabase) in src/Marten/Storage/MasterTableTenancy.cs:423
    at Marten.Storage.MasterTableTenancy.BuildDatabases() in src/Marten/Storage/MasterTableTenancy.cs:95

  total: 4, failed: 4, succeeded: 0

Green, with the fix:

Test run summary: Passed!
  total: 4, failed: 0, succeeded: 4

Whole MultiTenancyTests project, with the fix (net10.0, PostgreSQL 17): 178 of 183 passed. The 5 that failed are in SingleServerMultiTenancyTests and single_server_explorer_does_not_provision. Each timed out in its InitializeAsync while dropping its tenant databases, on DROP DATABASE under the harness's Command Timeout=5. Neither class reaches MasterTableTenancy or ShardedTenancy. Both pass 17/17 when run on their own, on the same build.

Schema and migration

No schema change. Two kinds of host see no difference: a host that leaves the tenancy's AutoCreate unset, and one that sets it while the store's mode is None, as in the documented example. A host that set CreateOnly or All on the tenancy now gets what it configured. For CreateOnly, that means a drifted master table makes BuildDatabases() throw, where before it was silently altered under the store's mode.

🤖 Generated with Claude Code

…the apply mode

MasterTableTenancyOptions.AutoCreate ("override the AutoCreate setting for just
the master tenancy table") and ShardedTenancyOptions.AutoCreate ("override the
AutoCreate setting for the master database tables") only ever decided WHETHER
the master tables were migrated. maybeApplyChanges gated on
`AutoCreate ?? StoreOptions.AutoCreateSchemaObjects` and then applied under
StoreOptions.AutoCreateSchemaObjects regardless, so the override was ignored in
every combination except a store set to None, where Weasel's promotion of an
explicit None apply to CreateOrUpdate happened to produce the documented result.

Use the same value for the gate and for the apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit 711b156 into JasperFx:master Oct 19, 2026
32 checks passed
This was referenced Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants