coulomb-social/workplans/CSOC-WP-0002-netkingdom-user-management-reestablish.md
tegwick d36795faa6 Plan product path: app shell and Forgejo markdown spaces
Mark CSOC-WP-0002 identity done on app.coulomb.social. Defer bulk Bubble
migration until product foundation exists. Open CSOC-WP-0004 for post-login
entry and space content as markdown bound to Forgejo.
2026-08-10 09:40:25 +02:00

316 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: CSOC-WP-0002
type: workplan
title: "Reestablish coulomb.social with NetKingdom user management"
domain: communication
repo: coulomb-social
status: done
owner: bernd
topic_slug: coulomb-social
created: "2026-08-09"
updated: "2026-08-10"
depends_on:
- CSOC-WP-0001
related:
- NK-WP-0023
- NK-WP-0024
- KEY-WP-0004
- USER-WP-0020
origin: operator
origin_ref: session-2026-08-09-netkingdom-users
state_hub_workstream_id: "b9addc9a-cc08-4b08-bb67-b6544579beb8"
---
# Reestablish coulomb.social with NetKingdom user management
Stand up a **new** coulomb.social application whose members authenticate and are
administered through **NetKingdom infrastructure** (IAM Profile + issuer,
user-engine for user-domain facts, flex-auth for authorization decisions) —
not Bubble app-local accounts and not a password store inside this repo.
**Content and product parity with the live Bubble app are deferred.** This
workplan delivers identity, membership, and a minimal authenticated shell so
the platform can exist again on the Railiance delivery lane. Claiming /
migrating Bubble content is a later workplan (after or alongside
`CSOC-WP-0001` assessment), not a gate for user management.
## Why NetKingdom (not pure app-local)
Business-app service contract v0.1 baseline is app-local accounts (DR-3 A)
with a bounded auth module for a later OIDC switch. coulomb.social is a
**multi-member social product**: platform identity and user-domain services
are the intended long-term shape.
| Concern | Owner | This repo does |
|---------|--------|----------------|
| OIDC login, MFA, token lifecycle | NetKingdom issuer (KeyCape lightweight or Keycloak expanded) via **IAM Profile** | OIDC client + PKCE; no password store |
| User account, profile, membership facts | **user-engine** | Consumes projections / APIs; maps `(issuer, sub)` → local app context |
| Fine-grained authorization | **flex-auth** (PDP) | PEP only — register protected system + check decisions |
| Session cookie for browser | This app | Short-lived app session after successful OIDC; secrets via OpenBao at deploy |
| Product content (pages, artefacts, chunks) | **Later workplan** | Out of scope here |
Canonical references:
- `net-kingdom/canon/standards/iam-profile_v0.2.md` (or current v0.3)
- `net-kingdom/canon/standards/user-engine-boundary-contract_v0.1.md`
- `net-kingdom/docs/user-engine-interface-guidance.md`
- `the-custodian/canon/standards/business-app-service-contract_v0.1.md` §§12
- Delivery lane reference: `vergabe-teilnahme`
## Out of scope (explicit)
- Bubble content inventory, export, or migration (CSOC-WP-0001 / follow-on)
- Full UI parity with the live product
- Marketplace / co-creation domain features beyond auth shell
- Owning KeyCape, Keycloak, user-engine, or flex-auth implementation
- Platform SSO for *other* Binky apps (this work only onboards **this** app)
## Acceptance (workplan done when)
1. A deployed (or locally runnable) coulomb.social instance completes OIDC login
against a NetKingdom IAM Profile issuer (dev/bootstrap allowed).
2. Successful login creates or links a **user-engine** identity link and
application membership for coulomb.social; logout ends the app session.
3. Unauthenticated visitors cannot reach member routes; at least one protected
“signed-in home” shell renders with principal display name/subject.
4. Auth is isolated behind a single module/boundary (service contract §2.3).
5. Tenant key present from migration one (Binky / client #1).
6. Docs record the onboarding bindings (OIDC client, user-engine app id,
flex-auth system id if registered) without secrets.
7. No Bubble dependency for login or member identity.
---
## T01 — ADR: NetKingdom identity for coulomb.social
```task
id: CSOC-WP-0002-T01
status: done
priority: high
state_hub_task_id: "57a7b778-b1d6-44fe-95ba-f12373c0355c"
```
Write `docs/adr/ADR-0001-netkingdom-identity.md` deciding:
- IAM Profile as the only authentication contract (issuer-agnostic).
- user-engine as source of truth for user-domain facts and memberships.
- flex-auth as PDP for protected actions (fail-closed for sensitive writes).
- Explicit **non-goals**: app-local password DB; Bubble as IdP; content migration in this plan.
- Relationship to DR-3 A (contract baseline) vs this products NetKingdom path.
- Dev/bootstrap path (local-identity or KeyCape) vs production issuer.
**Done when:** ADR reviewed and linked from INTENT/SCOPE.
## T02 — Application scaffold and tenant model
```task
id: CSOC-WP-0002-T02
status: done
priority: high
state_hub_task_id: "7ffa6485-4929-4958-b3b1-af98241beb02"
```
Scaffold the standalone service (stack choice recorded; prefer alignment with
the business delivery lane unless ADR says otherwise):
- App entrypoints, config, health/ready probes.
- DB migrations: `tenant_id` on all client-owned tables from day one.
- Empty **member context** table(s) keyed by tenant + stable user-engine
`user_id` / external identity link — **no password columns**.
- Dev compose / run instructions in README or `docs/dev.md`.
**Done when:** `make test` (or equivalent) passes smoke tests for boot +
migrations; no product domain models beyond auth shell.
## T03 — NetKingdom OIDC client binding
```task
id: CSOC-WP-0002-T03
status: done
priority: high
state_hub_task_id: "1b1d1162-d44a-45db-b906-05ebd7110f21"
```
Register coulomb.social as an OIDC client against the active NetKingdom issuer
mode (document which: KeyCape vs Keycloak vs local-identity for dev):
- client_id, redirect URIs (no wildcards), scopes, PKCE.
- Secrets only in OpenBao / local 0600 files — never Git.
- Conformance: tokens satisfy IAM Profile claims needed by the app
(`sub`, issuer, tenant/principal claims as required).
Record non-secret binding facts under `docs/identity/oidc-client.md`.
**Done when:** browser authorization code + PKCE login completes against the
chosen issuer in a dev environment; evidence in `docs/identity/`.
2026-08-09: Registered public client `coulomb-social` on live KeyCape
(`kc.coulomb.social`) via `scripts/register-keycape-client.sh`. Authorize
probe: registered redirect → Authelia handoff; bad redirect →
`invalid_profile_usage`. Human MFA browser login remains the final UX check
when running the app with `OIDC_ENABLED=true`.
## T04 — user-engine application onboarding
```task
id: CSOC-WP-0002-T04
status: done
priority: high
state_hub_task_id: "bbf8183c-2dd6-448b-a257-8d837d2d5ebf"
```
Per application onboarding contract
(`user-engine-interface-guidance.md`):
- Register application `coulomb-social` in user-engine (display name, owner,
allowed profile scopes, projection types).
- On first successful OIDC login: create/link external identity
`(issuer, sub)``user_id`; ensure tenant membership for Binky / client #1.
- Define minimal application profile attributes needed for shell (display
name, avatar optional) — **not** Bubble content fields.
- Document bindings in `docs/identity/user-engine-binding.md`.
Coordinate with NetKingdom/user-engine operators if platform registration
steps are outside this repos authority (NK-WP-0023/0024, USER-WP-*).
**Done when:** login yields a resolvable user-engine projection usable by the
app; second login is idempotent (same user_id).
2026-08-09: `HttpUserEngineClient` calls live user-engine
`GET /api/v1/me` with `X-User-Engine-Proxy-Secret` + verified claims
envelope (iss/sub/tenant/principal_type/aud including user-engine-portal).
Offline stub remains when URL/secret unset. Live probe created
`usr_…` under `tenant:coulomb`.
## T05 — App auth module (OIDC session boundary)
```task
id: CSOC-WP-0002-T05
status: done
priority: high
state_hub_task_id: "ea546dd7-e1c8-4040-a738-270d5004fd73"
```
Implement a **single** auth boundary module (§2.3):
- Login start / callback / logout routes.
- Secure session after code exchange (httpOnly cookie or equivalent).
- Middleware: unauthenticated → login; authenticated → attach principal
(issuer, sub, user_id, tenant_id, display claims).
- No credentials stored in app DB; refresh strategy documented.
- Unit/integration tests for happy path + rejected/expired tokens.
**Done when:** tests green; module is the only place that talks OIDC.
## T06 — Authenticated shell and member routes
```task
id: CSOC-WP-0002-T06
status: done
priority: medium
state_hub_task_id: "b2d48db3-a216-440e-afbf-51fa9c33b4da"
```
Ship a minimal product surface that proves user management works:
- Public landing (unauthenticated) with “Sign in”.
- Protected home (`/app` or similar) showing signed-in identity summary.
- Sign out control.
- Optional: admin-only stub route gated via flex-auth or coarse IAM role
(if flex-auth not ready, document temporary claim check + follow-up).
**Do not** rebuild Bubble Research / Pages / Artefacts here.
**Done when:** manual smoke checklist in `docs/identity/smoke.md` passes.
## T07 — flex-auth protected system (minimal)
```task
id: CSOC-WP-0002-T07
status: done
priority: medium
state_hub_task_id: "04c13dad-1c6e-45b9-96ba-438458e2c388"
```
Register coulomb.social as a flex-auth protected system with a **tiny**
resource/action vocabulary (e.g. `shell:view`, `member:self:read`). Wire the
app as PEP (fail-closed when policy engine unavailable for protected actions).
If cluster flex-auth is not yet available for this app, deliver:
- local policy adapter for dev, and
- a blocked residual / intake for production flex-auth binding.
**Done when:** at least one protected action is decided via the authz port;
docs record system id and vocabulary.
2026-08-09: Local vocabulary + HTTP PEP (`POST /v1/check`) implemented; `shell:view` enforced on `/app/`. General flex-auth check Service still absent in-cluster — leave `FLEX_AUTH_BASE_URL` empty for local mode.
## T08 — Delivery-lane packaging stub
```task
id: CSOC-WP-0002-T08
status: done
priority: medium
state_hub_task_id: "4718f21c-d509-45b9-a8f5-31de2099fd3f"
```
Align with business-app delivery lane without full production cutover:
- Container image build from commit SHA (or documented path to it).
- Placeholder railiance-apps values notes / chart stub.
- Runtime secret references (OIDC client secret, DB) named for OpenBao —
values not in Git.
- Health endpoints suitable for deploy smoke.
**Done when:** image builds in CI or documented local script; deploy notes in
`docs/deploy.md`.
2026-08-09: `Dockerfile` added; `railiance-apps` chart + values + ingress stub + Makefile targets. Image `7067145` published and Helm release deployed; migrations applied; in-cluster /healthz+landing OK. Public DNS still Cloudflare/Bubble; TLS cert pending DNS cutover to 92.205.62.239.
2026-08-09 (smoke continuation): Offline checklist + `make test` (15) passed. Port-forward with `Host: coulomb.social`: healthz/landing OK; `/auth/login/` 302 to KeyCape with prod redirect + PKCE. KeyCape authorize accepts local and prod redirect URIs (→ Authelia). Full browser MFA login still human step (`docs/identity/smoke.md`); cutover steps in `docs/deploy.md`.
2026-08-10 (acceptance complete on parallel host):
- Public rebuild host **`https://app.coulomb.social`** (apex `coulomb.social` remains Bubble).
- KeyCape client `mfaRequired: false`; id_token claim path deployed (`:f5537d8`).
- Operator **tegwick** signed in end-to-end; authenticated shell shows principal
(display name, subject, user-engine id, tenant).
- Workplan acceptance criteria 17 satisfied for identity/membership shell.
- Content, spaces, and Bubble migration are **out of scope** here — see
`CSOC-WP-0004` (product shell + markdown/Forgejo content) and deferred
`CSOC-WP-0001` migration.
---
## Sequencing
```text
T01 ADR
└─► T02 scaffold + tenant model
├─► T03 OIDC client
└─► T04 user-engine binding
└─► T05 auth module
└─► T06 shell
├─► T07 flex-auth
└─► T08 packaging stub
```
T03 and T04 can proceed in parallel after T02 once redirect URIs and app id
are known.
## Handoffs / residuals (later plans)
| Residual | Future home |
|----------|-------------|
| Post-login product entry + spaces UI | **CSOC-WP-0004** (next) |
| Space content as markdown + Forgejo | **CSOC-WP-0004** |
| Public self-registration | CSOC-WP-0003 + NK-WP-0025 (platform) |
| Bubble content claim & full migration | CSOC-WP-0001 — **after** product parity on app.* |
| Production Keycloak expanded mode | NetKingdom (NK-WP-0011 triggers) if demand |
## Related
- `workplans/CSOC-WP-0001-bubble-io-exit-assessment.md` — assessment; content later
- `docs/design-extract/` — visual language for future UI (not blocking T01T08)
- `docs/design-extract-auth.md` — Bubble scrape auth only; not product IdP