4.7 KiB
GovOPlaN Tenancy Module Boundary
govoplan-tenancy owns tenant registry, tenant settings, tenant lifecycle, and
tenant-context route contributions.
Owned Routes
/api/v1/admin/tenants*: tenant registry, delta feed, owner candidates, settings, deletion plans, retirement, and guarded empty-tenant destruction./api/v1/tenancy/switch-tenant: tenant-owned route surface for interactive tenant context switching.
/api/v1/auth/switch-tenant remains in govoplan-access for API
compatibility, but both routes delegate to the same
auth.tenantContextSwitcher capability.
Lifecycle Rules
Tenant retirement is non-destructive. It marks the tenant inactive and stores lifecycle metadata in tenant settings.
The compatibility DELETE /api/v1/admin/tenants/{tenant_id} route is
non-destructive retirement only. Requests with mode=destroy fail with a link
to the governed erasure-operation API; even an apparently empty scope must not
bypass recent authentication, typed confirmation, approval, and durable
evidence.
Populated-tenant erasure uses /erasure-operations instead. A durable
operation stores a non-secret, digest-bound provider preview, policy snapshot,
distinct approvals, and per-step checkpoints. The safe default production
policy requires two distinct approvals, a preview no older than fifteen
minutes, recent interactive authentication, the dedicated
system:tenants:erase permission, and exact tenant-slug confirmation. The
policy is configurable through tenant_erasure_policy in system settings;
production profiles cannot reduce approval below two.
Authorized system administrators read and update it through
GET/PATCH /api/v1/admin/tenant-erasure-policy; policy changes require both
system:tenants:erase and system:settings:write plus recent interactive
authentication.
Execution verifies the preview again, suspends tenant access, and commits a checkpoint before each module effect. A timeout or unknown/pending outcome stops for reconciliation using the same provider idempotency key. Cancellation is available only before destructive work starts. The Core scope is deleted only after providers finish, a fresh inventory is clear, and delete vetoes and tenant counts are zero. The durable operation then removes its free-text reason and never stores typed confirmation, secrets, or erased tenant content.
Tenant lifecycle planning uses registered tenant summary providers and delete veto providers. Modules that own tenant-scoped data must contribute summaries so destructive deletion cannot silently miss their rows.
Delete veto providers are registered through module manifests and receive
(session, tenant_id, resource_id). Providers should return a structured
DeleteVetoIssue, a list of issues, or None; legacy providers that raise an
exception are treated as blocking module vetoes. Tenancy exposes those issues in
the deletion plan with module attribution and resource details, so operators can
see which module blocks or qualifies the lifecycle action.
Core's tenancy.erasure_provider.<module_id> contract supplies module-owned
resource dispositions, irreversible warnings, ordered steps, idempotent
execution, and outcome reconciliation. An installed module with nonzero tenant
summary counts but no erasure provider explicitly blocks the operation. A
module that declares neither a tenant summary nor an erasure provider is
reported as outside tenant-persistence scope rather than silently executed.
Access provides the first concrete contribution and retains shared global
accounts and identities while erasing target-tenant authorization records.
Lifecycle Events
govoplan-tenancy.backend.lifecycle is the module-local contract for tenant
lifecycle event names and payload shape. Modules that need to react to tenant
lifecycle changes should depend on the event type strings or the emitted audit
events, not on tenancy API route internals.
The stable lifecycle event names are:
tenant.created: tenant registry entry was created and owner membership provisioning was requested.tenant.suspended: tenant was marked inactive through the admin lifecycle route.tenant.resumed: tenant was reactivated through the admin lifecycle route.tenant.deletion_requested: retirement or destructive erasure was requested after lifecycle planning passed.tenant.erasure_completed: destructive tenant deletion completed.
Legacy audit actions such as tenant.updated, tenant.retired, and
tenant.destroyed can still be emitted for compatibility. New module behavior
should key off the explicit lifecycle events above.
Lifecycle event details use concrete tenant identifiers plus optional actor, reason, count, and mode information. Destructive erasure is only emitted after the tenant row is successfully scheduled for deletion in the same transaction.