net-kingdom/workplans/NK-WP-0036-restore-user-portal-client-registration.md
tegwick c8e07615c3
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Identity provider journey acceptance / provider (push) Successful in 6s
Build and Publish identity-provisioner / build-and-push (push) Successful in 10s
Surface redacted directory bind failures before native onboarding
Map uncaught HTTPError from LLDAP login to a structured
dependency_unavailable response, add /readyz as the provisioner-to-directory
preflight, keep /healthz as process liveness, and run the contract in CI.
Auth rejection is not retried during cooldown.

NK-WP-0036-T05 remains in progress until the immutable image is published,
pinned with /readyz, and one native login/create/password-setup journey is
verified.

Assistant: grok
Assistant-Session: 01a09dc6-3f0e-78f1-a884-c8c703c24ddf
2026-09-14 04:46:29 +02:00

199 lines
9.7 KiB
Markdown

---
id: NK-WP-0036
type: workplan
title: "Restore native portal login and tenant-onboarding integration"
domain: infotech
repo: net-kingdom
status: active
owner: the-custodian
topic_slug: netkingdom
created: "2026-09-11"
updated: "2026-09-14"
related: [KEY-WP-0007, RAPPS-WP-0014, VERGABE-WP-0019]
state_hub_workstream_id: "6e1358d6-87e4-52e7-b3dd-09abdc48cefc"
---
The demo-company operator login returns `invalid_profile_usage`, feature
`client_id`, description `unknown client_id`. The approved public client from
KEY-WP-0007 is absent from live `sso/keycape-config`. The bootstrap generator
also omits it; this is a demonstrated regeneration hazard, not proof of which
historical change removed the live registration. User requests retry/repair
under continued demo-tenant onboarding authority.
## Preserve the existing registration in source and prepare the bounded repair
```task
id: NK-WP-0036-T01
status: done
priority: high
state_hub_task_id: "7f30efcb-beac-5843-bca7-a5cbc1c715cc"
```
Keep `user-engine-portal` public, authorization-code/S256 PKCE, exact callback
`https://users.92-205-62-239.nip.io/oidc/callback`, and only
`openid profile email groups`. Add the existing declaration to the bootstrap
generator and test equality with `register-user-engine-portal.py`.
`portal-client-rollout.py` adds only a missing declaration, preserving other
configuration bytes and Secret data. It refuses changed/duplicate registrations,
controller ownership, wrong cluster and stale UID/resourceVersion. Secret values
stay in memory/child stdin and never enter output, arguments or persisted files.
Four synthetic regression tests pass. No credentials or roles are granted.
## Restore and verify the live login entry point
```task
id: NK-WP-0036-T02
status: done
priority: high
state_hub_task_id: "8a3c3e91-d604-59ee-9d15-f79223667780"
```
Inspect and server-dry-run the exact observed Secret revision, apply the bounded
patch, read back unchanged unrelated data, and restart the existing KeyCape
deployment to load configuration. Keep the current image and 25m resources.
Record readiness and valid-client authorize redirect, invalid-callback and
missing-PKCE refusal. Reopen a fresh portal login only after positive preflight.
Actual human login and demo tenant creation continue in RAPPS-WP-0014-T02;
this repair alone is not evidence of tenant existence or operator entitlement.
If native verification fails, retain the exact non-secret failure and reconcile
the current resource revision; do not replay a stale complete Secret. Hand any
remaining actionable defects to live records before finishing this plan.
Live repair succeeded on 2026-09-11. The exact missing client was added without
changing unrelated Secret data. KeyCape restarted at its existing image and
resources. Valid authorization redirects to Authelia; wrong callback and missing
PKCE are refused. The operator subsequently authenticated as platform-root and
reached the platform form. See docs/evidence/2026-09-11-native-portal-repair.md.
## Repair and verify the portal-to-tenant policy contract
```task
id: NK-WP-0036-T03
status: done
priority: high
state_hub_task_id: "378b03b3-aedc-50c3-8de7-94064794582b"
```
The real form submission returned provisioning_unavailable, correlation
corr_d41f417c61713afc7ddea08f. Tenant Engine logs show POST /tenants returning
403 before its create transaction. Its PDP recognizes only tenant-engine and
flex-auth; the shipped portal calls as user-engine. Tenant read is also missing
from that policy despite the shipped endpoint requiring it.
Register the existing user-engine service for only non-platform tenant create
and read in flex-auth/examples/tenant-engine. Keep ServiceAccount caller auth
enforced. Preserve denied grants, lifecycle, plan and guardrail mutations.
In user-engine, preserve display name and correlation id in the create request,
and map authority denial to the existing redacted 403 path, proving that no
administrator is created after a tenant-authority denial.
Run owner regression suites, publish through existing Forgejo image CI, pin and
roll only the Tenant Engine PDP and rapp-user-engine runtime, and verify the
actual allowed/denied contract with authenticated consumer calls. Then reconcile
native demo tenant existence before retrying the operator form. RAPPS-WP-0014
retains actual tenant onboarding and product placement; this workplan does not
claim tenant existence or application readiness from component tests alone.
Completed: both owner suites and CI images passed, the Tenant Engine PDP and
User Engine runtime were independently promoted by immutable digest, nine live
policy/caller checks passed, and native demo-company readback returns not-found.
Remaining actual operator submission, administrator onboarding and application
placement remain live in RAPPS-WP-0014-T02 and VERGABE-WP-0019-T03/T04. This
integration repair is complete; the invited product pilot is not yet admitted.
Follow-through: the operator submitted the native form successfully at 19:03:18
UTC. Independent authority readback confirms demo-company active/version 1;
User Engine shows the first administrator as invited. Remaining ordinary members,
identity activation and product accounts are retained in RAPPS-WP-0014-T02.
## Reconcile the identity provisioner's rejected directory credential
```task
id: NK-WP-0036-T04
status: done
priority: high
needs_human: false
state_hub_task_id: "3497d77a-1dc2-5fe6-8784-33949321f556"
```
A subsequent native Create login fails before directory identity creation, at
LLDAPProvisioner._login, HTTP 401. The existing identity-provisioner image
5b460f5c was restarted to reload its lldap-secrets reference; readiness passed
but contained provider authentication still returns 401. No provider credential
was changed and no rejected create was replayed. Native user records remain.
The non-printing, field-only attended repair and seven synthetic tests are
prepared at sso-mfa/k8s/lldap/identity-provisioner-reconcile.py. See
docs/identity-provisioner-bind-repair.md for exact scope, operator input,
metadata guard, candidate authentication, apply, reload and verification.
The operator completed the hidden-input apply on 2026-09-11. Its sanitized
receipt confirms provider_login and consumer_login true, provider_password_changed
false, and the existing Secret resourceVersion advanced from 51345775 to
60026132. Independent consumer login plus directory-read verification returns
reloaded-check-passed; the deployment is ready 1/1 at its unchanged image.
The existing native user can now retry Create login; successful identity linkage
and password setup remain the pilot onboarding task. The unresolved warden route is a pointer, not authority
to export another live Secret. Retain NK-WP-0033's separate incident residuals.
## Make dependency failure visible before another human onboarding attempt
```task
id: NK-WP-0036-T05
status: progress
priority: high
state_hub_task_id: "46d33ab6-d76b-537f-8d60-32c4451675b5"
```
Cover the real provisioner-to-directory contract in a contained preflight,
return a redacted structured dependency failure instead of an uncaught HTTPError,
and make CI execute those contract tests. Keep process liveness distinct from
functional readiness. Publish and promote through a reviewed immutable image
lane, then verify provider login, user creation/linkage and password setup as a
single native onboarding journey. Preserve credential secrecy and avoid
unbounded password-check retries. Actual demo users and application admission
remain RAPPS-WP-0014 and VERGABE-WP-0019.
2026-09-14 agent implementation (not yet done): source now maps directory
HTTPError/URLError to redacted `{"error":"dependency_unavailable",
"dependency":"directory","reason":"auth_rejected|unreachable|protocol_error"}`.
`GET /healthz` remains process liveness; `GET /readyz` runs one login plus one
directory read. Auth rejection is cached for 30s so probes do not retry a
rejected password unbounded. 28 provider tests pass locally, including the new
directory-contract suite. identity-journeys CI discovers those tests;
identity-provisioner-image.yaml is the immutable publish lane.
Remaining operator steps before T05 can be marked done:
1. Push this commit to `main` so identity-journeys and identity-provisioner-image
run. Confirm journeys green, then record the image digest from
`coulomb/identity-provisioner`.
2. Pin that digest in `sso-mfa/k8s/identity-provisioner/deployment.yaml`, switch
readiness to `/readyz` with `timeoutSeconds: 5` or higher, keep liveness on
`/healthz`, apply, and confirm Ready 1/1 with `/readyz` returning
`{"status":"ready","dependency":"directory"}`.
3. Verify one native onboarding journey: provider login, user create/linkage,
password setup. Do not retry a rejected password in a loop. Demo users and
application admission stay RAPPS-WP-0014 and VERGABE-WP-0019.
## Admit the canonical users hostname and preserve callback validation
```task
id: NK-WP-0036-T06
status: done
priority: high
state_hub_task_id: "d736f4d1-1c97-5cf3-894e-600ea113c785"
```
Operator supplied DNS for users.coulomb.social on 2026-09-12. The contained
portal-client-rollout helper now upgrades only the exact legacy public client
by appending https://users.coulomb.social/oidc/callback. It preserves unrelated
configuration bytes and Secret fields; six synthetic tests pass, including
idempotence and refusal of altered scopes/type/callbacks. The bootstrap and
registration declarations agree. Server dry-run passed; guarded Secret revision
59999030 became 60090393, UID unchanged. KeyCape restarted at its unchanged
image/resources. Canonical authorize redirects to Authelia; wrong callback and
missing PKCE return 400. The runtime package owns portal routing and callback
cutover; final human login confirmation is recorded there. No password rotated.