Implement tenant update and reversible retirement API (TEN-WP-0005 T01-T04)
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>
This commit is contained in:
parent
7dcccafc03
commit
d6fd73bd42
10 changed files with 1625 additions and 28 deletions
|
|
@ -4,7 +4,7 @@ type: workplan
|
|||
title: "Tenant metadata update and reversible retirement API"
|
||||
domain: infotech
|
||||
repo: tenant-engine
|
||||
status: ready
|
||||
status: active
|
||||
owner: codex
|
||||
topic_slug: tenant-lifecycle
|
||||
created: "2026-08-10"
|
||||
|
|
@ -27,7 +27,7 @@ and audit correlation; it is not a hard-delete endpoint.
|
|||
|
||||
```task
|
||||
id: TEN-WP-0005-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5b7d9022-4c0f-4bc3-9f63-b268836c8efc"
|
||||
```
|
||||
|
|
@ -49,11 +49,18 @@ Document stable response/error schemas for user-engine and other consumers.
|
|||
Done when the OpenAPI contract makes concurrency, idempotency, authorization,
|
||||
and lifecycle semantics unambiguous without defining a hard-delete operation.
|
||||
|
||||
Done 2026-08-10: lifecycle (`active`/`retired`), allow-listed mutable metadata
|
||||
(`display_name`, `contact_email`), record version, and lifecycle timestamps are
|
||||
in the domain contract. All four routes exist; the allow-list is enforced by
|
||||
the request schema (`extra: forbid`) so it is visible in the OpenAPI document.
|
||||
Consumer contract written up in `docs/tenant-lifecycle-api.md`. No hard-delete
|
||||
operation was defined.
|
||||
|
||||
## T02 - Implement durable lifecycle state and migration
|
||||
|
||||
```task
|
||||
id: TEN-WP-0005-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "3596d8f2-06cf-4bf9-bdab-00cf48a25956"
|
||||
```
|
||||
|
|
@ -68,11 +75,23 @@ result across process restarts.
|
|||
Done when in-memory and PostgreSQL conformance prove atomic compare-and-swap,
|
||||
restart-safe idempotency, and lossless migration of existing tenants.
|
||||
|
||||
Done 2026-08-10, against SQLite rather than PostgreSQL: this workplan was
|
||||
drafted assuming Postgres, but TEN-WP-0004 shipped SQLite on a PVC as the
|
||||
production store, so the migration and conformance target the store that
|
||||
actually runs. Adding an unused Postgres path would have been dead code.
|
||||
The `TenantStore` Protocol keeps the seam if the backend changes later.
|
||||
|
||||
`mutate_tenant()` carries idempotency replay, version CAS, mutation, and audit
|
||||
event in one commit -- splitting them would leave a window where a crash
|
||||
yields a bumped version with no receipt (a retry then double-applies). The
|
||||
migration is forward-only and idempotent: existing rows default to `active` at
|
||||
version 1 with identifiers, grants, and plans untouched.
|
||||
|
||||
## T03 - Implement authorized update and lifecycle endpoints
|
||||
|
||||
```task
|
||||
id: TEN-WP-0005-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "083bcd28-47f4-4337-9bbe-e2f9f67cc2d1"
|
||||
```
|
||||
|
|
@ -91,11 +110,19 @@ grants or invent plan state.
|
|||
Done when all lifecycle mutations are authorized, versioned, idempotent,
|
||||
correlated, and provider-neutral.
|
||||
|
||||
Done 2026-08-10: `tenant.update`, `tenant.retire`, and `tenant.reactivate` are
|
||||
distinct flex-auth actions, so policy can permit a metadata edit without
|
||||
permitting a retirement. Authorization runs before the store is touched, so an
|
||||
unauthorized caller cannot probe which tenants exist. 503s are redacted.
|
||||
|
||||
One deviation, deliberate: role *revocation* stays available while retired --
|
||||
it only reduces privilege, and blocking it would be fail-open.
|
||||
|
||||
## T04 - Add lifecycle security and compatibility conformance
|
||||
|
||||
```task
|
||||
id: TEN-WP-0005-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b716d7bf-ef6b-4b1d-bac3-e3f716d1a0b8"
|
||||
```
|
||||
|
|
@ -111,6 +138,18 @@ compatible.
|
|||
Done when unit, API, store-conformance, PostgreSQL, and flex-auth tests pass and
|
||||
the existing API behavior has no unplanned breaking change.
|
||||
|
||||
Done 2026-08-10: 124 tests pass (was 66). The store-conformance suite is
|
||||
parametrised over both backends so the durable store cannot silently diverge
|
||||
from the reference semantics. All 66 pre-existing tests still pass unchanged;
|
||||
`POST /tenants` returns a superset of its previous body.
|
||||
|
||||
The concurrent-writer test caught a real pre-existing defect: reads on the
|
||||
shared SQLite connection ran unguarded and could observe a row mid-transaction
|
||||
from another thread, producing a spurious `tenant_not_found`. Reads now take
|
||||
the same lock as writes.
|
||||
|
||||
Not covered: no PostgreSQL tests exist, per the T02 note above.
|
||||
|
||||
## T05 - Integrate and verify the production authority
|
||||
|
||||
```task
|
||||
|
|
@ -131,3 +170,10 @@ Done when production evidence confirms durable lifecycle behavior and the
|
|||
consumer handoff names the immutable image, API version, and authorization
|
||||
policy revision.
|
||||
|
||||
Status 2026-08-10: still open, and it is the only thing between user-engine and
|
||||
USER-WP-0021. Blocked on work outside this repo: the three flex-auth actions
|
||||
must be added to the policy package (until then every lifecycle check
|
||||
correctly resolves to deny), and image build plus rollout need cluster access.
|
||||
The consumer-facing contract is finalized and ready to hand over:
|
||||
`docs/tenant-lifecycle-api.md`.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue