Skip to content

users: no tenant management, UserCreate, UserUpdate and create_admin have no tenant_id #360

Closed as not planned

Description

@antosubash

Summary

users_user.tenant_id exists and is nullable, but nothing in the users module lets an operator set it. UserCreate/UserUpdate (the API schemas) have no tenant_id field, create_admin/create_standard_user (the CLI/bootstrap path) take no tenant parameter, and there is no admin UI field either. On a host that turns on multi-tenancy, every existing user — including the bootstrap admin — is stuck at tenant_id=None with no supported way to fix it short of a raw UPDATE statement.

Observed

users/contracts/schemas.py:35 and :44:

class UserCreate(CreateUpdateDictModel, SQLModel):
    email: EmailStr
    password: str
    is_active: bool | None = True
    is_superuser: bool | None = False
    is_verified: bool | None = False
    full_name: str | None = None
    # no tenant_id

class UserUpdate(CreateUpdateDictModel, SQLModel):
    password: str | None = None
    email: EmailStr | None = None
    is_active: bool | None = None
    is_superuser: bool | None = None
    is_verified: bool | None = None
    full_name: str | None = None
    # no tenant_id

users/bootstrap.py:51:

async def create_admin(
    db: AsyncSession,
    *,
    email: str,
    password: str,
    full_name: str | None = None,
    force: bool = False,
) -> CreateAdminResult:

No tenant_id parameter. (UserRead, the response schema at schemas.py:20-32, does already expose tenant_id: str | None read-only — it's only the write paths that are missing it.)

Minimal reproduction (no DB needed):

import inspect
from users.contracts.schemas import UserCreate, UserUpdate
from users.bootstrap import create_admin

assert "tenant_id" not in UserCreate.model_fields
assert "tenant_id" not in UserUpdate.model_fields
assert "tenant_id" not in inspect.signature(create_admin).parameters
print("confirmed: no write path sets tenant_id")

Confirmed on the real stack in the spike (FACT 3, test_spike_app_tenancy.py::test_3_multi_tenant_host_resolution): users.bootstrap.create_admin -> users_user.tenant_id=None (no tenant parameter). The only way the spike found to assign a tenant was a raw UPDATE users_user SET tenant_id=... WHERE id=....

Expected

An operator running a multi-tenant host should be able to create a user (or the bootstrap admin) with a tenant, and update an existing user's tenant, through the same UserCreate/UserUpdate schemas and admin UI used for everything else about the user.

Why a module cannot work around it

records doesn't own the users table or its schemas; it can only refuse tenant-less users at its own admin routes (fail-closed), not give an operator a way to fix the underlying user.

Proposed API

tenant_id: str | None = None on UserCreate/UserUpdate (and the admin create/edit forms), plus create_admin(..., tenant_id: str | None = None) and create_standard_user(..., tenant_id: str | None = None).

Context

Found while making the records module (antosubash/smpy_modules#37) multi-tenant; design doc docs/plans/2026-09-23-records-multitenancy.md there.

Activity

  1. antosubash commented on Oct 1, 2026

    @antosubash
    OwnerAuthor

    Closing: filed before #370 (fail-closed tenancy + tenants module) landed, so the premise is stale. Module tenancy adoption is being re-tracked with fresh per-module issues in the post-#370 format (see #371–#377); any remaining gap here gets a new issue and lands in the tenancy adoption PR stack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions