Add an explicit tenant lifecycle (active/retired), allow-listed mutable
metadata, record versioning, and lifecycle timestamps to the tenant authority.
- domain: TenantLifecycle, with_metadata/retire/reactivate, immutability and
transition invariants. Identifier stays immutable -- it is the IAM Profile
`tenant` claim key-cape mints into tokens.
- store: mutate_tenant() commits idempotency replay, version CAS, mutation,
and audit event together; durable receipts survive restart. Retired tenants
refuse new grants and plan changes but keep their history.
- sqlite: forward-only idempotent migration; existing rows default to active
at version 1. Reads now take the write lock -- the concurrent-writer test
caught unguarded reads on the shared connection observing mid-transaction
state as a spurious tenant_not_found.
- api: GET/PATCH /tenants/{id}, POST retire|reactivate. Idempotency-Key and
If-Match required, distinct flex-auth actions per operation, stable error
schema, redacted 503s.
- docs/tenant-lifecycle-api.md: consumer contract for user-engine.
Implemented against SQLite, not PostgreSQL as the workplan assumed --
TEN-WP-0004 shipped SQLite on a PVC as the production store.
124 tests pass (was 66); no breaking change to existing endpoints.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Python 3.12 + FastAPI, pyproject.toml + Makefile mirroring
qonto-assistant's exactly. src/tenant_engine/{domain,store,app,main}.py:
- domain.py: Tenant, CapabilityRole (PLTF/IAM/VEN/CUS), RoleGrant,
PlanAssignment, create_role_grant() enforcing ADR-0014's invariants.
Refinement made while implementing: platform_default grants are valid
for trial-grouped tenants OR the reserved tenant:platform/tenant:coulomb
tenants (their baseline roles were never purchased either) -- the task
spec only named the trial case.
- store.py: TenantStore Protocol + InMemoryTenantStore, every mutation
emits a DomainEvent per the boundary contract's Audit Correlation
Contract.
- app.py/main.py: FastAPI factory + /health endpoint, verified live on
127.0.0.1:8090.
29 tests passing, including non-exclusive role coexistence (CUS+VEN
simultaneously) and append-only revoke semantics.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>