Skip to content

feat(tenancy): adoption prerequisites — default tenant id, tenant-role vocabulary, tenant_client (#380) [stack 1/11] - #385

Merged
antosubash merged 6 commits into
mainfrom
tenancy/01-prereqs
Oct 1, 2026
Merged

antosubash merged 6 commits into
mainfrom
tenancy/01-prereqs

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Closes #380. Stack 1/11 of the tenancy-adoption series (base: main).

What

  • DEFAULT_TENANT_ID = "default" (simple_module_db). On a non-strict install with no tenant bound, MultiTenantMixin inserts are stamped with it: session.add, ORM and Core insert, and multi-row .values(). A tenant id the caller sets explicitly is kept. Strict mode is unchanged and an unbound insert still raises. Reads on non-strict installs stay unfiltered, because every row belongs to the install. Without this, the first module to adopt the mixin would break writes on every single-tenant install.
  • Tenant-role vocabulary in core (simple_module_core.tenancy): TENANT_ROLE_PREFIX, TenantRole, tenant_role() and is_tenant_role(). Modules can now map_role(tenant_role(TenantRole.MEMBER), …) without importing tenants, and tenants re-exports these names.
  • tenant_client fixture in simple_module_test: a real user plus a tenant and membership, with that tenant set active in the session.
  • SM025: a boot warning when multi_tenant is on but no tenant_resolver is registered. It also shows on the in-app Doctor screen.
  • Fixes the ci-check-hardcoded-strings failure already on main in tenants/module.py (depends_on now uses constants).
  • Docs: multi-tenancy.md (default tenant, backfill migrations, tenant roles, testing), the diagnostic codes page and CLAUDE.md.

Tests

  • make test-py: 3319 passed, 9 skipped.
  • make lint: clean.
  • New: test_default_tenant.py, test_diagnostics_tenancy.py, test_tenant_resolver_diagnostic.py, test_tenant_client_fixture.py.

https://claude.ai/code/session_01F8RiTBUJQnZmSq56qReZeV

Groundwork so core modules can adopt MultiTenantMixin without breaking
single-tenant installs or leaving tenant members without permissions.

- DEFAULT_TENANT_ID = "default" (simple_module_db). With tenancy not strict
  and no tenant bound, MultiTenantMixin inserts (session.add, ORM/Core
  insert, multi-VALUES, and inside all_tenants()) are stamped with it
  instead of hitting the NOT NULL constraint. Strict mode is unchanged: an
  unbound insert still raises MissingTenantError. HostSettings.default_tenant
  is untouched.
  Reads stay unfiltered when unbound and not strict: on a single-tenant
  install every row belongs to the install whatever tenant_id it carries,
  and narrowing to "default" would hide rows written under default_tenant
  or while multi_tenant was on.
- Tenant-role vocabulary in core (simple_module_core.tenancy): TenantRole
  (owner/admin/member), TENANT_ROLE_PREFIX, tenant_role(), is_tenant_role().
  tenants re-exports them (MembershipRole is TenantRole).
- tenant_client fixture in simple_module_test: factory
  tenant_client(role="owner", *, tenant_id=None, email=None) yielding
  TenantClient(client, tenant_id, user_id); lazily imports users/tenants.
  tenants tests use it where it was a straight swap.
- SM025: multi_tenant on with no app.state.tenant_resolver, reported at boot
  after module registration (needs the built app, so not in the doctor CLI)
  in every environment, and folded into the Doctor screen's results.
- tenants: depends_on uses _MODULE_* constants (ci-check-hardcoded-strings).
- Docs: multi-tenancy (default tenant, roles, testing), diagnostic codes,
  CLAUDE.md.

Claude-Session: https://claude.ai/code/session_01F8RiTBUJQnZmSq56qReZeV
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7ad5870
Status: ✅  Deploy successful!
Preview URL: https://c5735544.simple-module-python.pages.dev
Branch Preview URL: https://tenancy-01-prereqs.simple-module-python.pages.dev

View logs

…n unscoped writes (review of #380)

Non-strict bypass and unbound writes always fell back to DEFAULT_TENANT_ID:
an insert with execution_options(all_tenants=True) inside a request dropped
the request's tenant, and on a default_tenant install every CLI/all_tenants()
write landed in "default", invisible to the install's own scoped requests.

Both guards now stamp current_tenant_id if bound, else the engine policy's
default_tenant_id, which the host publishes from Settings.default_tenant
(single-tenant installs only). Strict mode is unchanged.

Claude-Session: https://claude.ai/code/session_01F8RiTBUJQnZmSq56qReZeV
#380)

create_user looked the 'user' Role up and silently skipped the UserRole
when the row was absent, which it always is under create_all. Extract
users.bootstrap.ensure_user_role (shared with create_standard_user) and
use it so the fixture user really holds the platform role.

Claude-Session: https://claude.ai/code/session_01F8RiTBUJQnZmSq56qReZeV
@antosubash
antosubash marked this pull request as ready for review October 1, 2026 16:14
@antosubash
antosubash added this pull request to stack #397 October 1, 2026 16:14
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-01T16:17:45.613576Z 7ad5870 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@antosubash
antosubash merged commit e1f5b1b into main Oct 1, 2026
13 checks passed
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.

framework: tenancy adoption prerequisites (default tenant id, shared tenant-role vocabulary, tenant_client fixture)

1 participant