Replace the entity slugs on organizations, workspaces, resource pools, and plan ladders with nullable `key` columns and add keys to products, prices, and entitlement sets. Rename `providers.slug` to `provider` and add partial unique indexes for system and org role names. Assign invoice numbers per billing account from a gapless transactional counter; Stripe's number moves to the invoice mapping as an external reference. Seeds, fixtures, and the operator lookup address rows by key, and the returning-login resync no longer blanks a display name when the IdP sends no `name` claim.
7.0 KiB
system-tenant Specification
Purpose
TBD - created by archiving change system-tenant. Update Purpose after archive.
Requirements
Requirement: A singleton System tenant owns ownerless resources
The system SHALL maintain exactly one System tenant: an organization.organizations row with org_type = 'system' and key = 'system', a workspace under it, and a reserved synthetic owner (identity.users + identity.persons). system is one of the two key constants the application writes by design (the other is each organization's default pool); it is a fixed literal, never derived from a person's data (ui-vocabulary). The System tenant SHALL be the owning workspace for provider resources that belong to no member (e.g. ownerless FedWiki farm sites). The org_type = 'system' row SHALL have default_plan_ladder_id = NULL, and the System organization SHALL have no pool assignment — so auto-provisioning SHALL NOT create a default grant for it and force_reduce SHALL skip it. The synthetic system user SHALL be non-loginable (a reserved oidc_subject with no IdP account). Singleton-ness SHALL rest on unique constraints (org_types.org_type; the partial unique index on organizations.org_type where org_type = 'system'; workspaces(org_id, lower(name)) among non-deleted workspaces; users.oidc_subject; persons.user_id); uq_organizations_key additionally makes system unclaimable by any other organization. The console SHALL resolve the System tenant by the type predicate org_type = 'system' and SHALL NEVER resolve it by its key: the key is written once at creation as a declarative address for seeds, configuration files, and scripts, so an operator who changes it leaves the console working and makes those declarative consumers miss.
Scenario: System tenant present after boot
- WHEN the application has completed boot
- THEN exactly one organization with
org_type = 'system'SHALL exist - AND that organization SHALL carry
key = 'system' - AND it SHALL have a workspace and a reserved synthetic owner person and user
Scenario: System tenant receives no default entitlements
- WHEN the System organization exists
- THEN its
org_typeSHALL havedefault_plan_ladder_id = NULL - AND auto-provisioning SHALL NOT create a default grant for it
- AND it SHALL have no pool assignment, so
force_reduceSHALL skip it
Scenario: The console resolves the tenant by its type, not its key
- WHEN any console code path resolves the System tenant or its workspace
- THEN the query SHALL select on
org_type = 'system' - AND no console code path SHALL select an organization on
key = 'system'
Scenario: System user cannot log in
- WHEN the synthetic system user exists
- THEN its
oidc_subjectSHALL be a reserved value with no corresponding IdP account - AND no interactive login SHALL resolve to it
Requirement: The System tenant is ensured idempotently at boot
The application SHALL ensure the System tenant exists on every boot, after database migrations and before the FedWiki sync schedule is wired, via an idempotent operation that find-or-creates the user → person → org_type='system' → org → workspace chain in a single transaction. The organization is found or created by its org_type = 'system' natural key and carries key = 'system'; the ensure operation SHALL NOT supply an org_id or any other identifier the database generates. Re-running when the tenant already exists SHALL make no changes. The operation SHALL be safe under concurrent boots (multiple replicas). If the System tenant cannot be ensured, application boot SHALL fail with an error rather than continue.
Scenario: Fresh database creates the chain
- WHEN the application boots against a database with no System tenant
- THEN the system SHALL create the system user, person,
org_type='system', organization, and workspace - AND every one of those rows SHALL take the identifier the database generates for it
- AND the workspace SHALL be available for the FedWiki sync before its schedule is wired
Scenario: Existing tenant is a no-op
- WHEN the application boots and the System tenant already exists
- THEN no duplicate user, person, organization, or workspace rows SHALL be created
- AND the existing organization SHALL keep the
org_idit was first generated with
Scenario: Existing system-org workspace is adopted
- WHEN the System organization already has a workspace (e.g. from a prior ad-hoc seed that used a different workspace name)
- THEN the ensure operation SHALL adopt that existing workspace
- AND SHALL NOT create a second workspace under the System organization
Scenario: Ensure failure fails boot
- WHEN the ensure operation cannot complete (e.g. a database error)
- THEN application boot SHALL fail with an error
- AND the FedWiki sync SHALL NOT be scheduled
Requirement: The System tenant workspace is resolved by natural key, not configuration
The owning workspace for ownerless provider resources SHALL be resolved by natural key — the singleton organization where org_type = 'system', taking its workspace — and SHALL NOT be supplied by configuration. No system-tenant workspace identifier SHALL be read from configuration or persisted as a literal reference; the resolver SHALL query for it fresh. The fedwiki-sync-default-workspace-id configuration key and its CLI flag SHALL NOT exist. This prohibition on persisting the workspace identifier as a literal reference SHALL also apply to durable workflow or schedule arguments: a Temporal schedule's stored args are a persisted literal reference in the same failure class as a configuration key, since they survive independently of the application database and can go stale (e.g. an app-database wipe rotates the System tenant's workspace ID while the schedule's stored args do not). Consumers that need the System tenant's workspace at execution time SHALL resolve it fresh at that time rather than reading it from workflow input or schedule args.
Scenario: Resolution by org type
- WHEN the system resolves the ownerless-resource workspace
- THEN it SHALL select the workspace of the organization whose
org_type = 'system'
Scenario: No configuration key for the default workspace
- WHEN application configuration is loaded
- THEN there SHALL be no
fedwiki-sync-default-workspace-idkey or flag - AND the FedWiki sync's landing workspace SHALL be the resolved System tenant workspace
Scenario: App database re-initialized under a persistent scheduler resolves the current workspace
- WHEN the application database is re-initialized (the System tenant is recreated with a new workspace ID) while a Temporal schedule and its history persist from before the re-initialization
- THEN a consumer resolving the System tenant's workspace at execution time SHALL query fresh by natural key and obtain the current workspace
- AND no stale workspace identifier carried in the schedule's persisted args SHALL be used