Tenancy - Platform core¶
- Status: Implemented
- Prefix:
TNY - Related: Lifecycle and workflows, Tenant management, Provisioning monitor, ADR-0012
Purpose¶
The platform uses a single PostgreSQL database with shared schema multi-tenancy for the development environment (ADR-0012). This spec is the JIRA breakdown for that module: eleven stories, each small enough for one person and one pull request. Operator screens and the sign-up wizard are other epics.
Users and permissions¶
No operator UI in this spec. Other modules call the tenancy public API. Who may suspend or reactivate a tenant is defined in operator access.
Requirements¶
Each row is one JIRA story. Build them in order: TNY-01 blocks the rest; TNY-02 blocks TNY-03; TNY-03 blocks TNY-04 and TNY-08; TNY-04 and TNY-05 block TNY-06; TNY-06 blocks TNY-07; TNY-08 blocks TNY-09; TNY-02 and TNY-04 block TNY-10 and TNY-11.
| ID | Priority | JIRA summary | Requirement |
|---|---|---|---|
| TNY-01 | Must | Accept single-database multi-tenancy | Record the decision: single PostgreSQL database with shared schema multi-tenancy (ADR-0012). The stack lists that model as decided. |
| TNY-02 | Must | Tenant domain and state machine | A tenant has an immutable lowercase subdomain. Reserved names admin, api, app, www, status, and mail are rejected. State changes follow tenant management. Suspend, reactivate, maintenance, schedule deletion, and cancel deletion require a reason of at least 10 characters. |
| TNY-03 | Must | Registry columns and repository | The registry stores previous_state and schema_version. The connection string is not stored. It is derived from database_name and the admin database URL. |
| TNY-04 | Must | Tenant registry service | Registering the same subdomain again does not create a second tenant. Callers can load a tenant by id or subdomain, and can change display name, owner contact, and internal notes. Subdomain and tenant id cannot be changed. |
| TNY-05 | Must | Tenant request context and idle pools | Code handling a tenant request can read the current tenant. A call with no tenant context fails. An idle tenant connection pool closes after the configured idle time, and one tenant pool can be closed on demand. |
| TNY-06 | Must | Resolve tenant from host | A host {subdomain}.<root-domain> loads that tenant once per request. The bare root domain, localhost, and reserved hosts do not attach a tenant. An unknown subdomain is rejected and does not open a database. |
| TNY-07 | Must | Enforce tenant state | A tenant request continues only when the tenant is active. Every other state returns a distinct error code and does not open a database. A scheduled tenant job for a non-active tenant does not run and writes a log entry. Control-plane requests, which have no tenant, are unchanged. Migration and provisioning still run for a non-active tenant. |
| TNY-08 | Must | Create the tenant database | Creating the tenant database is safe to repeat. If the database already exists, the call succeeds and does not create a second one. The database name must be a safe identifier. |
| TNY-09 | Must | Migrate one tenant and every tenant | The shared tenant migrations apply to one tenant or to every tenant that is not deleted, and the registry records the schema version. Running the migration again does not fail. The existing command that migrates only the local development database still does only that. |
| TNY-10 | Must | Suspend, reactivate, and maintenance | An active tenant can be suspended and later reactivated. Entering maintenance stores the previous state and leaving maintenance restores it. Each of these actions publishes an in-process event. Tenant data is unchanged. |
| TNY-11 | Must | List tenants and run deletion | Callers can list every registry row, including deleted. Scheduling deletion requires a reason of at least 10 characters and the exact subdomain; a mismatch does not change state. Cancelling deletion restores the previous state. Completing deletion sets deleted, keeps the registry row, subdomain, and database name, and does not drop the database. Each of these actions publishes an in-process event. |
Screens¶
None. Tenant list, detail, and action dialogs are specified in tenant management.
Business rules¶
- One JIRA story per row above. Do not split a story across modules, and do not add operator HTTP or sign-up in these stories.
- TNY-01 is documentation only. Later stories do not merge before that decision is accepted.
- Scheduling, cancelling, and completing deletion are registry commands (TNY-11). The grace-period timer, the email to the tenant owner, dropping the database, and the subdomain hold stay on tenant management.
- Ending sessions and rejecting API keys after suspension is an
iamstory, not TNY-10.
Acceptance criteria¶
- TNY-01: The architecture index contains the accepted single-database multi-tenancy decision (ADR-0012), and the stack no longer marks that model as proposed.
- TNY-02:
admincannot be used as a subdomain. Suspending a tenant that is notactivefails. Cancelling a deletion that was scheduled fromsuspendedreturns the tenant tosuspended. - TNY-03: A registry row can store the state to restore and the applied schema version. No password is stored on the tenant row.
- TNY-04: Registering the same subdomain and database name twice returns one tenant. A second call with a different database name fails. The subdomain in the stored row does not change when the display name changes.
- TNY-05: Reading the current tenant outside a tenant request fails. A pool unused past the idle limit is closed.
- TNY-06:
acme.<root-domain>resolves tenantacme.admin.<root-domain>does not. A subdomain with no tenant is rejected and opens no database. - TNY-07: An
activetenant request proceeds. Asuspendedtenant request is rejected with a code that is different fromprovisioning,provisioning_failed,maintenance,pending_deletion, anddeleted, and does not open a database. A scheduled job for asuspendedtenant is skipped and logged. Migrating that tenant still runs. - TNY-08: The first create makes the database. The second create does not issue another create. A name such as
bizos;dropis rejected. - TNY-09: Migrating one tenant records the current migration tag. Migrating all tenants skips
deletedtenants. A deleted tenant cannot be migrated. - TNY-10: Suspend then reactivate leaves the subdomain and database name unchanged. Maintenance on a
suspendedtenant returns it tosuspended. A suspend publishesTenantSuspended. - TNY-11: The list includes a tenant after deletion is completed. Scheduling deletion with a different subdomain leaves the tenant
suspended. Cancelling a deletion that was scheduled fromsuspendedreturnssuspended. Completing deletion keeps the same subdomain and database name on the registry row and publishesTenantDeleted.
Out of scope¶
- Admin portal screens and operator HTTP (see tenant management).
- Sign-up and the provisioning run orchestrator (see provisioning monitor).
- Cutting tenant sessions and API keys within one minute of suspension.
- Changing a subdomain, merging tenants, billing plans, backups, and a background-job queue.
Open questions¶
None for these stories. Grace period and subdomain hold remain open on tenant management.