IAM Profile v0.3 and Tenancy Posture v0.1 are now live on policy.coulomb.social. Close the portal expansion workplan against those documents, keep the flex-auth live A2 probe waiting, and forward optional tenant_roles to flex-auth without authorizing from them locally.
317 lines
16 KiB
Markdown
317 lines
16 KiB
Markdown
---
|
|
id: USER-WP-0021
|
|
type: workplan
|
|
title: "Expand user-engine portal beyond the proven Binky MVP"
|
|
domain: communication
|
|
repo: user-engine
|
|
status: finished
|
|
owner: codex
|
|
topic_slug: netkingdom
|
|
created: "2026-07-30"
|
|
updated: "2026-08-19"
|
|
depends_on:
|
|
- USER-WP-0020
|
|
- TEN-WP-0005
|
|
state_hub_workstream_id: "ba217f48-5fa5-4178-9c79-73aa225f3f2e"
|
|
---
|
|
|
|
# USER-WP-0021 - Portal product expansion
|
|
|
|
Preserve deliberately deferred product breadth from `USER-WP-0020` without
|
|
holding the proven production MVP open. Activate according to tenant demand.
|
|
|
|
## T01 - Complete invitation and platform recovery APIs
|
|
|
|
```task
|
|
id: USER-WP-0021-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "342299b8-d9a3-408d-bf0d-914496714d5f"
|
|
```
|
|
|
|
Add invitation claim/resend/expiry, platform tenant management and recovery
|
|
routes, optimistic concurrency, complete OpenAPI schemas, and durable outbox
|
|
delivery/replay/dead-letter operations.
|
|
|
|
2026-08-08 increment: durable invitations now carry expiry and an optimistic
|
|
version. The API supports tenant create/list, version-gated resend/expire, and
|
|
authenticated claim with terminal-state and stale-version rejection. Outbox
|
|
state now covers attempts, bounded failures, delivery, replay, and dead-letter
|
|
through the store abstraction. OpenAPI 0.2 documents invitation and removal
|
|
operations. Platform recovery breadth and an outbox operator transport remain.
|
|
|
|
2026-08-08 follow-up: a provider-neutral `TenantManagementPort` now supports
|
|
platform-operator tenant creation with optional first-admin preparation. A
|
|
redacted recovery route recreates or reconciles provider identity state and
|
|
reactivates tenant lifecycle, while authenticated operator routes dispatch and
|
|
replay durable outbox events. Ordinary tenant users are denied these routes.
|
|
Broader tenant update/retirement operations remain.
|
|
|
|
## T02 - Expand self-service onboarding UX
|
|
|
|
```task
|
|
id: USER-WP-0021-T02
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "dc548cfb-db7c-4cbd-864f-2effeebc3dbd"
|
|
```
|
|
|
|
Add invitation acceptance, email-verification status, consent/profile,
|
|
tenant-selection, and fully resumable onboarding screens while keeping
|
|
password and MFA material on provider-owned surfaces.
|
|
|
|
2026-08-08 increment: invitation claim is now a first-class authenticated API
|
|
operation and preserves the provider-owned credential boundary. Dedicated
|
|
browser screens for the fully resumable journey remain.
|
|
|
|
2026-08-08 browser increment: authenticated users can review and accept an
|
|
invitation through a CSRF-protected flow, then land on a responsive onboarding
|
|
status screen showing provider verification, tenant memberships, and journey
|
|
state. Acceptance binds the verified OIDC email to the invitation address.
|
|
Consent/profile editing and tenant switching remain.
|
|
|
|
2026-08-08 completion increment: self-service profile completion and versioned
|
|
consent are now durable, audited, and emitted through the outbox; verified
|
|
email remains immutable. Membership-scoped tenant selection is available for
|
|
onboarding views, and a PKCE reauthentication handoff carries a tenant hint so
|
|
the identity provider—not browser state—changes authoritative tenant context.
|
|
Interactive completion of subsystem-owned journey steps remains.
|
|
|
|
2026-08-08 completion: users can complete active user-engine-owned onboarding
|
|
steps through API or CSRF-protected browser controls. Journey ownership is
|
|
checked before mutation. Password, MFA, and other subsystem-owned steps expose
|
|
handoff status only and cannot be completed from user-engine; their callbacks
|
|
resume the durable journey. This completes the self-service onboarding scope.
|
|
|
|
## T03 - Expand administration UX
|
|
|
|
```task
|
|
id: USER-WP-0021-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "3e0b41ee-6159-46a4-a9fe-c5b6d714cc2a"
|
|
```
|
|
|
|
Add platform tenant creation, first-admin bootstrap, invitation/recovery
|
|
management, account removal, and redacted lifecycle-gap diagnostics.
|
|
|
|
2026-08-08 increment: account removal now uses the provider-neutral
|
|
deprovisioning port and disables the tenant lifecycle record. Platform
|
|
creation/bootstrap and recovery UI remain.
|
|
|
|
2026-08-08 follow-up: platform APIs now create tenants through the tenant
|
|
authority, prepare the first administrator, and perform redacted identity
|
|
recovery. Browser administration screens for these operations remain.
|
|
|
|
2026-08-08 browser increment: tenant administrators can create, inspect,
|
|
resend, and expire invitations with versioned CSRF-protected forms, and remove
|
|
accounts through the provider-neutral deprovisioning boundary.
|
|
|
|
2026-08-08 platform-browser increment: platform operators have a responsive,
|
|
CSRF-protected tenant-creation screen with optional first-admin preparation
|
|
and a direct handoff to tenant administration. Redacted recovery and
|
|
lifecycle-gap browser diagnostics remain.
|
|
|
|
2026-08-08 recovery-browser increment: platform operators can inspect redacted
|
|
lifecycle-gap categories and invoke CSRF-protected identity recovery from the
|
|
tenant member view. Recovery recreates a missing provider link or reconciles
|
|
an existing identity, then restores the tenant lifecycle record without
|
|
showing credentials, factor evidence, or provider error bodies.
|
|
|
|
2026-08-08 completion: tenant and platform browser surfaces now cover every
|
|
listed administration operation while retaining tenant boundaries, CSRF,
|
|
provider-neutral lifecycle ports, and redacted diagnostics.
|
|
|
|
## T04 - Complete broad security and accessibility conformance
|
|
|
|
```task
|
|
id: USER-WP-0021-T04
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "fb59245e-6989-4bd9-b72a-68e53ea9f0af"
|
|
```
|
|
|
|
Automate duplicate/expired/replayed invitation, session expiry, provider
|
|
outage, partial failure, audit-redaction, keyboard/screen-reader, and
|
|
mobile/desktop conformance. Preserve existing cross-tenant and escalation
|
|
denial gates.
|
|
|
|
2026-08-08 increment: conformance covers invitation conflicts, resend/expiry,
|
|
outbox provider failure, replay, dead-letter, successful recovery, and
|
|
provider-neutral account removal. Browser accessibility breadth remains.
|
|
|
|
2026-08-08 follow-up: conformance now also proves ordinary-user denial of the
|
|
platform surface and the positive platform tenant/bootstrap, recovery, and
|
|
outbox delivery workflow.
|
|
|
|
2026-08-08 browser increment: conformance proves CSRF enforcement, invitation
|
|
email binding, successful browser claim and onboarding redirect, responsive
|
|
viewport metadata, focus-visible controls, semantic landmarks, and invitation
|
|
and removal administration controls.
|
|
|
|
2026-08-08 self-service/recovery increment: conformance additionally covers
|
|
durable consent/profile completion, immutable verified email, tenant-hinted
|
|
OIDC reauthentication, platform recovery controls, and redacted lifecycle-gap
|
|
presentation.
|
|
|
|
2026-08-08 completion: the automated matrix covers duplicate, explicitly
|
|
expired, revoked, and replayed invitations; stale versions; expired sessions;
|
|
provider outage and unchanged lifecycle state; bounded error/audit surfaces;
|
|
cross-tenant and platform denial; semantic headings/landmarks; visible keyboard
|
|
focus; responsive viewport/mobile tables; provider handoff denial; and
|
|
desktop/browser positive flows. The full suite passes 115 tests with three
|
|
provider integration tests skipped when their external service is absent.
|
|
|
|
## Upstream corrections consumed (2026-08-18)
|
|
|
|
tenant-engine reported that production had been silently rolled back to the
|
|
pre-lifecycle TEN-WP-0004 image between 2026-08-13 and 2026-08-16, so PATCH,
|
|
retire, and reactivate returned 404 while flex-auth simultaneously ran a
|
|
four-action policy that would have denied them anyway. Both are resolved and
|
|
verified live. Our conformance suite passed throughout because it was right
|
|
about the contract and production was not serving it — which is precisely the
|
|
gap the handoff note flagged. The live cross-service check from a user-engine
|
|
pod is now owed as part of the next rollout; contract-level evidence cannot
|
|
see a pin rollback.
|
|
|
|
tenant-engine also corrected the contract: `grouping` is no longer immutable.
|
|
It changes only through the authority's own reclassification route under a
|
|
separate `tenant.grouping.set` action, because grouping resolves a tenant's
|
|
guardrail spend ceiling and must not ride on a rename. The identifier's
|
|
grouping segment is now historical — a tenant created as `tenant:small:acme`
|
|
can report grouping `large`. Verified that user-engine performs no identifier
|
|
parsing; every `split` in the portal is URL routing. `TenantRecord` had been
|
|
dropping the field, so the read now carries it, the operator screen shows it
|
|
with the identifier caveat, and OpenAPI documents where to read it. The
|
|
reclassification control itself is deliberately not built: the route is not
|
|
deployed and wants its own permission.
|
|
|
|
## T05 - Trigger enterprise federation planning only on demand
|
|
|
|
```task
|
|
id: USER-WP-0021-T05
|
|
status: done
|
|
priority: low
|
|
state_hub_task_id: "05046780-9625-47c2-8caf-f57e9239c603"
|
|
```
|
|
|
|
When a tenant requires SAML/OIDC federation, SCIM, directory synchronization,
|
|
just-in-time provisioning, or customer-owned lifecycle, create a dedicated
|
|
cross-repository plan against the provider-neutral contracts.
|
|
|
|
2026-08-19 policy-nexus adaptation: no tenant has demanded those
|
|
integrations, so this workplan does not spawn one. The trigger now points
|
|
at published contracts rather than unpublished drafts:
|
|
|
|
- IAM Profile v0.3 at
|
|
`https://policy.coulomb.social/standards/iam-profile/v0.3/` — Keycloak
|
|
expanded mode remains the interchangeable enterprise-federation
|
|
implementation; grouping and `tenant_roles` are independent axes
|
|
(ADR-0013 / ADR-0014). Existing v0.2 claim handling stays conformant.
|
|
- Tenancy Posture v0.1 at
|
|
`https://policy.coulomb.social/standards/tenancy-posture/v0.1/`.
|
|
- NetKingdom architecture draft-2 at
|
|
`https://policy.coulomb.social/architecture/net-kingdom/v0.1/`.
|
|
|
|
user-engine still does not own federation, SCIM, or directory
|
|
synchronization. A new workplan is the right vehicle if a tenant later
|
|
requires them. This workplan's product-expansion scope is complete.
|
|
|
|
## Production rollout (2026-08-08)
|
|
|
|
Commit `8229c6d` passed 115 tests with three external-provider skips, was
|
|
published as
|
|
`forgejo.coulomb.social/coulomb/user-engine@sha256:cef2fdb19fd001a9d2a64742f58dacdcf2ff5868ce50685c91eaad7880645fd0`,
|
|
and rolled out successfully on Railiance. Public health returns schema
|
|
`0001_initial`, KeyCape login returns a PKCE redirect, and `/onboarding`,
|
|
`/admin/{tenant}`, and `/platform` are recognized protected routes (403
|
|
without a session, rather than 404). The rollout replaces in-memory OIDC
|
|
sessions, so existing browsers must authenticate again.
|
|
|
|
2026-08-09 production follow-up: tenant-engine and its dedicated flex-auth
|
|
policy service are deployed with persistent storage and least-privilege
|
|
networking. The portal adapter created a tenant successfully and the record
|
|
survived a tenant-engine restart. The separate user-engine flex-auth policy is
|
|
also deployed and has live self-service-allow/cross-tenant-deny evidence, but
|
|
the portal runtime switch remains gated on approved event and mail receivers.
|
|
T01 remains open only for broader tenant update/retirement operations and the
|
|
final outbox transport activation.
|
|
|
|
2026-08-10 rollout evidence: the current source image now fails closed when
|
|
the required flex-auth and delivery settings are absent. A guarded deployment
|
|
was restored to the last known-good revision without service loss. Final T01
|
|
activation remains blocked on approved OpenBao audit/mail receiver tokens and
|
|
transactional SMTP credentials; no placeholder or reused credential was added.
|
|
|
|
2026-08-10 authority check: the deployed tenant-engine contract currently
|
|
supports tenant creation plus role/plan operations, but exposes no tenant
|
|
metadata-update or retirement operation. Portal update/retirement routes must
|
|
remain out until that authority owns the corresponding lifecycle contract;
|
|
user-engine will not simulate authoritative tenant state locally.
|
|
|
|
TEN-WP-0005 is now registered and ready in tenant-engine for the authoritative
|
|
metadata update, retirement, and reactivation contract. This task is waiting
|
|
on that workplan and on the OpenBao-backed event/mail delivery credentials.
|
|
The non-secret flex-auth runtime URL is live and verified, removing that item
|
|
from the rollout gate.
|
|
|
|
2026-08-13 delivery hardening: audit-core and email-connect are now live as
|
|
separately scoped receivers. `HTTPOutboxDeliveryAdapter` therefore accepts
|
|
distinct event and mail bearer tokens; runtime configuration uses
|
|
`USER_ENGINE_EVENT_TOKEN` and `USER_ENGINE_MAIL_TOKEN`, and mail delivery
|
|
fails closed when its dedicated token is absent. Tests assert that the two
|
|
Authorization headers differ. The full suite passes 132 tests with three
|
|
external-provider skips. Production rollout still waits on governed delivery
|
|
of both scoped credentials.
|
|
|
|
2026-08-16 tenant lifecycle completion: TEN-WP-0005 is finished, so the last
|
|
deferred part of T01 is now implementable against a real authority.
|
|
`TenantManagementPort` gained `tenant`, `update_tenant`, `retire_tenant`, and
|
|
`reactivate_tenant`, and `HTTPTenantManagementAdapter` implements them against
|
|
the `tenant-lifecycle-api.md` contract: version ETag as `If-Match` (never `*`),
|
|
`Idempotency-Key`, actor/reason/correlation_id, and `Idempotent-Replay`
|
|
surfaced as a `replayed` flag. Authority failures map to redacted domain errors
|
|
carrying only the contract's stable `error_code`; the authority's `detail` text
|
|
never crosses the boundary.
|
|
|
|
Platform operators get `GET`/`PATCH /api/v1/platform/tenants/{tenant}` plus
|
|
`/retire` and `/reactivate`, and a CSRF-protected browser lifecycle screen that
|
|
reads the record first and echoes its version. A retired tenant renders no
|
|
metadata form, matching the authority's own transition rules. user-engine keeps
|
|
no tenant table and invents no retirement semantics: every read and write goes
|
|
to tenant-engine. Portal OpenAPI is now 0.3.0 with `TenantRecord`,
|
|
`UpdateTenant`, and `TenantLifecycleChange` schemas, and the immutable
|
|
identifier is documented as rejected rather than discovered from a 400.
|
|
|
|
Conformance proves ordinary-user denial, stale-version 409, replayed mutation
|
|
without a second version bump, update-while-retired and double-retirement 409,
|
|
reactivation, unknown-tenant 404, forged-CSRF rejection with unchanged
|
|
lifecycle, and adapter-level redaction of authority detail text. The full suite
|
|
passes 145 tests with three external-provider skips.
|
|
|
|
This closes the engineering scope of T01. Production activation of the outbox
|
|
transport remains gated on governed delivery of the OpenBao event/mail tokens
|
|
and transactional SMTP credentials — an operational gate outside this repo, not
|
|
outstanding user-engine code.
|
|
|
|
2026-08-14 live retry finding: the PostgreSQL adapter excluded every row with
|
|
`failed_at` set, while the in-memory contract correctly retains failures until
|
|
`dead_lettered_at`. A first receiver error therefore required manual replay
|
|
instead of receiving the documented bounded automatic retry. The query now
|
|
selects every undelivered, non-dead-letter event; regression coverage checks
|
|
that failed rows are not filtered out. The full suite passes 133 tests with
|
|
three external-provider skips.
|
|
|
|
## Residuals after close (2026-08-19)
|
|
|
|
- Live tenant-lifecycle probe from a user-engine pod (GET / PATCH / retire /
|
|
reactivate on a disposable tenant) remains owed with USER-WP-0023-T03.
|
|
Contract tests cannot see a digest pin rollback.
|
|
- Outbox transport activation still waits on governed OpenBao event/mail
|
|
tokens and transactional SMTP credentials.
|
|
- Optional `tenant_roles` is now forwarded to flex-auth. Privileged
|
|
live re-query against tenant-engine is required only if a future policy
|
|
treats those roles as `aal2`-class input; flex-auth currently has no
|
|
such policy.
|
|
- A new workplan is required if a tenant demands federation, SCIM, or
|
|
directory synchronization.
|