Appearance
Entity: tenant
Entity Type: Database table (platform.tenants)
Description: A single provisioned organisation-space in EM3 — one tenant maps to one dedicated database schema. tenant is referenced by tenantId on the great majority of tenant-scoped tables across the whole system (this catalog included — see e.g. group, groupMembership, tenantUser, currently under analysis on a separate Users & Access branch not yet merged to main), but until now had no entity page of its own in this catalog. This page documents tenant as it exists today in EM3, sourced directly from the platform-backend migrations rather than from any prior written spec.
Relationship to
client: the pre-existing, Confluence-migrated client page describesclientas corresponding 1:1 totenant("one client = one tenant schema"). The current DB schema instead models this the other way round and with an explicit uniqueness constraint:tenant.clientIdis a required, unique foreign key toclient— i.e. a 1:1 relationship enforced from thetenantside, withclientas the referenced (not referencing) table. This is a known, pre-existing inconsistency between the legacyclientwrite-up and the real schema; per prior direction on this analysis, reconciling it is out of scope here and is left unaddressed.
Data Attributes Table
| Attribute Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Primary key. | UUID | Generated in code (app layer) | Yes | Yes | UUID v7 | - | Primary Key | 018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| tenantDatabaseId | The logical database this tenant's schema lives in. Platform infrastructure concern (Tiger Cloud service connection → logical database), not modeled further by this analysis. | UUID | - | Yes | No | UUID v7 | FK → tenant_database (platform infrastructure table, not in this catalog); ON DELETE RESTRICT; must exist. | - | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| schemaName | Name of the tenant's dedicated database schema. Always tenant_<id without dashes> — see Tenant Provisioning. | String | - | Yes | Yes | - | Non-empty | - | tenant_018ed0b3c2987c7a96d58b36f5a7f8d2 |
| displayName | Human-readable name of the tenant, shown in the UI. | String | - | Yes | No | - | Non-empty | - | Město Brno |
| status | Lifecycle state of the tenant. | Enum | provisioning | Yes | No | See Enum - TenantStatus | Must be a valid TenantStatus value | name: idx_tenants_status, type: btree | active |
| clientId | The client this tenant belongs to. Required and unique — a tenant has exactly one client, and a client has at most one tenant (see relationship note above). | UUID | - | Yes | Yes | UUID v7 | FK → client; ON DELETE RESTRICT; must exist. Unique constraint uq_tenants_client_id. | - | 018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| provisioningStartedAt | The lease timestamp for the current or most recent provisioning attempt — fixed once when the attempt is claimed, timestamptz(3) (millisecond precision, matched to what a JS Date round-trips, so every terminal write can compare it for equality). Null once the tenant is active, suspended, deleted, or has never been claimed. See Tenant Provisioning. | Timestamp with time zone (ms precision) | - | No | No | ISO 8601 | - | - | 2026-09-16T12:00:00.000Z |
| provisioningAttempts | Number of provisioning attempts started (claimed) so far for this tenant, capped by the sweep's maxAttempts policy. | Integer | 0 | Yes | No | - | - | - | 2 |
| lastProvisioningError | The error message from the most recent failed provisioning step, or a converge failure recorded after the tenant went active. Cleared (null) the moment status successfully flips to active. | Text | - | No | No | - | - | - | connect ECONNREFUSED 127.0.0.1:5432 |
| createdAt | Timestamp of when the record was created. Immutable after insert. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 | Cannot be null; cannot be modified after creation. | - | 2025-05-25T13:11:00Z |
| updatedAt | Timestamp of the last update. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 | Cannot be null. | - | 2025-05-25T13:11:00Z |
| deletedAt | Timestamp of soft deletion. Null means the tenant is active. Once set, immutable. | Timestamp with time zone | - | No | No | ISO 8601 | Immutable once set. Active records: WHERE deletedAt IS NULL | name: idx_tenants_deleted_at, type: btree (partial: WHERE deletedAt IS NULL) | - |
| createdBy | Identifier of the actor who created the record. | String | - | Yes | No | type:actor | Non-empty. | - | user:018e... |
| updatedBy | Identifier of the actor who last updated the record. | String | - | Yes | No | type:actor | Non-empty. | - | system:migration |
Notes on status vs. deletedAt: the table carries both a status value of deleted and a standard soft-delete deletedAt column. Whether these are kept in lock-step by application logic, or can diverge (e.g. status = suspended with deletedAt still null), is not resolved by the migrations alone — see Enum - TenantStatus.
Provisioning: documented in full at Tenant Provisioning — a periodic sweep claims each not-yet-active tenant under a lease and runs it through schema creation, migration, seeding, and (once active) a platform-admin access converge; failed is the terminal state if the retry budget runs out first.
Audited fields
Recorded on created (in full) and updated (changed only) — no deleted, since a tenant is never hard- or soft-deleted by this flow (see status/deletedAt note above): tenantDatabaseId, schemaName, displayName, status, clientId, provisioningStartedAt, provisioningAttempts, lastProvisioningError.
Excluded: none.
Not part of the entityName registry — a tenant row is named through a separate mechanism: every audit entry already carries its own tenantName, filled directly from the resolved tenant scope rather than looked up per row (architecture 61-audit-log.md §7.1–§7.2 in the code repo).