The cameleer-server team introduced `currentSchema` and `ApplicationName` JDBC parameters (commit `7a63135`) to scope admin diagnostic queries to a single tenant's connections. Previously, all tenant servers shared one PostgreSQL user and connected to the `cameleer` database without schema isolation — a tenant's server could theoretically see SQL text from other tenants via `pg_stat_activity`.
1. Validate slug against `^[a-z0-9-]+$` (reject unexpected characters).
2.`CREATE USER "tenant_<slug>" WITH PASSWORD '<password>'` (skip if user already exists — idempotent for re-provisioning).
3.`CREATE SCHEMA "tenant_<slug>" AUTHORIZATION "tenant_<slug>"` (skip if schema already exists).
4.`REVOKE ALL ON SCHEMA public FROM "tenant_<slug>"`.
All identifiers are double-quoted. The password is a 32-character random alphanumeric string generated by the same `SecureRandom` utility used for other credential generation.
### `dropTenantDatabase(slug)`
1.`DROP SCHEMA IF EXISTS "tenant_<slug>" CASCADE`
2.`DROP USER IF EXISTS "tenant_<slug>"`
Schema must be dropped first (with `CASCADE`) because PG won't drop a user that owns objects.
Server restart/upgrade re-creates containers via `provisionAsync()`, which re-reads `dbPassword` from the entity. Restarting an upgraded tenant picks up isolated credentials automatically.
5. dataCleanupService.cleanupClickHouse(slug) ← ClickHouse cleanup stays separate
6. entity.setStatus(DELETED) ← existing
```
`TenantDataCleanupService` loses its PostgreSQL cleanup responsibility (delegated to `TenantDatabaseService`). It keeps only the ClickHouse cleanup. Rename method to `cleanupClickHouse(slug)` for clarity.
## Backwards Compatibility
| Scenario | Behavior |
|----------|----------|
| **Standalone mode** | Unaffected. Server is in compose, not provisioned by SaaS. Defaults to `tenant_default`. |
| **Existing SaaS tenants** (dbPassword=null) | Shared credentials, no `currentSchema`. Same as before. |
| **Existing tenants after restart/upgrade** | Still use shared credentials until re-provisioned with new code. |
| **New tenants** | Isolated user+schema+JDBC URL. Full isolation. |
| **Delete of pre-existing tenant** | `DROP USER IF EXISTS` is a no-op (user doesn't exist). Schema drop unchanged. |
## InfrastructureService
No changes needed. Already queries `information_schema.schemata WHERE schema_name LIKE 'tenant_%'`. With per-tenant schemas now created, the PostgreSQL tenant table on the Infrastructure page will populate automatically.