docs: treat live net-kingdom user-engine manifests as the wrap source

user-engine already runs from net-kingdom/sso-mfa/k8s/user-engine.
The rapp absorbs those manifests (manifest-managed), keeps or
cutovers the dedicated CNPG cluster, and becomes the only apply home.
This commit is contained in:
tegwick 2026-08-18 12:21:49 +02:00
parent 56cc9099a3
commit 77452492ec
2 changed files with 103 additions and 58 deletions

View file

@ -456,7 +456,8 @@ to `rapp-<app>` and keep `railiance/app.toml` inside the rapp.
### 9.1 Database ### 9.1 Database
Do not run a sidecar Postgres. Declare a consumer against `rapp-postgres`: Do not run a sidecar Postgres. The default for a new app is a consumer
against `rapp-postgres`:
- boundary unit: `database-per-consumer` - boundary unit: `database-per-consumer`
- credential lane: `railiance-platform-credential-broker` - credential lane: `railiance-platform-credential-broker`
@ -472,6 +473,11 @@ If the app consumes a single `DATABASE_URL`, URL-encode the password.
Prefer discrete `POSTGRES_*` env vars when the framework allows it Prefer discrete `POSTGRES_*` env vars when the framework allows it
(`railiance-apps/docs/django-on-railiance.md`). (`railiance-apps/docs/django-on-railiance.md`).
A **dedicated CNPG Cluster** is allowed only when placement policy
already says so (live `user-engine-pg` is this case). Then the Cluster
manifests belong in the rapp, not in `net-kingdom` or the app repo. Do
not be both a `rapp-postgres` consumer and a dedicated Cluster.
### 9.2 Secrets ### 9.2 Secrets
The rapp never stores values. Standing secrets go through a CCR and The rapp never stores values. Standing secrets go through a CCR and
@ -524,7 +530,7 @@ a grantable public surface.
### 10.1 Current facts ### 10.1 Current facts
`user-engine` is a headless user-domain / identity-domain service with a `user-engine` is a headless user-domain / identity-domain service with a
production WSGI portal. It already has: production WSGI portal. The **app repo** already has:
- `Containerfile` — Python 3.12 slim, non-root uid `10001`, port `8080` - `Containerfile` — Python 3.12 slim, non-root uid `10001`, port `8080`
- `python -m user_engine.runtime` - `python -m user_engine.runtime`
@ -532,14 +538,40 @@ production WSGI portal. It already has:
- Postgres store + in-tree migrations - Postgres store + in-tree migrations
- fail-closed production adapters: OIDC, flex-auth, outbox, provisioning, - fail-closed production adapters: OIDC, flex-auth, outbox, provisioning,
optional tenant-management and registration verification optional tenant-management and registration verification
- explicit non-ownership of IdP, authorization PDP, and secret custody - explicit non-ownership of IdP, authorization PDP, secret custody, **and
deployment** (`docs/release.md`)
`rapp-user-engine` is an empty git repository (2026-08-11). ADR-0007 `rapp-user-engine` is an empty Forgejo clone
already lists it as undeclared. (`forgejo-remote:coulomb/rapp-user-engine.git`). No README, no
declaration. ADR-0007 already lists it as undeclared.
Sister stubs `rapp-secrets-engine` and `rapp-tenant-engine` should follow **Live packaging is not in the rapp.** It lives in
the same guide after user-engine proves the path. Do not batch-scaffold `net-kingdom/sso-mfa/k8s/user-engine/`:
all three before the first declaration validates.
| File | Role |
| --- | --- |
| `runtime.yaml` | Namespace, dedicated CNPG `user-engine-pg`, Deployment, Service, SA, NetworkPolicies |
| `ingress.yaml` | Traefik Ingress `users.92-205-62-239.nip.io` (README still says no public Ingress until KeyCape PKCE is configured — treat exposure as unresolved) |
| `openbao-runtime.yaml` | ExternalSecrets from `platform/workloads/user-engine/runtime` |
| `backup.yaml`, `restore-drill.yaml` | Logical dump / restore |
| `verify-operability.sh` | Live checks |
Image already published:
`forgejo.coulomb.social/coulomb/user-engine@sha256:e3b5f65bafc1c0260dfdf2567a52766e67506ceb878a51759a2e9a307c4b5eb8`.
flex-auth for this consumer is pinned **inside** `flex-auth`
(`values/user-engine.yaml`, `examples/user-engine/`), not in a rapp.
Closest sibling analogue: `tenant-engine/deploy/` (digest-pinned
manifests, but that app *is* a `rapp-postgres` consumer).
The wrapper job is therefore **absorb and declare**, not invent a second
production target. Same rule as
`qonto-assistant/docs/knative-runtime-and-rapp-handoff.md`: once the
rapp exists, `net-kingdom` keeps IAM/provisioner contracts and stops
being the apply home.
Sister stubs `rapp-secrets-engine` and `rapp-tenant-engine` follow after
this path validates. `rapp-secrets-engine` is likely a grouping mistake
against `rapp-openbao` / S3, not a fourth identity member.
### 10.2 Recommended decisions ### 10.2 Recommended decisions
@ -548,12 +580,12 @@ all three before the first declaration validates.
| Create a rapp? | yes | stable identity, clear app/wrapper split, own rollout fate | | Create a rapp? | yes | stable identity, clear app/wrapper split, own rollout fate |
| `ownership_repo` | `user-engine` | application accountability, same pattern as `policy-nexus` / `qonto-assistant` | | `ownership_repo` | `user-engine` | application accountability, same pattern as `policy-nexus` / `qonto-assistant` |
| `workload_identity.name` | `user-engine` | schema rule | | `workload_identity.name` | `user-engine` | schema rule |
| `package_type` | `helm-managed-platform-service` | only Helm-shaped enum available today | | `package_type` | `manifest-managed-platform-service` | live assets are raw YAML/CNPG, not a Helm chart. Do not invent Helm just to match policy-nexus. |
| `primary_rail` | `rail-kubernetes` | long-running portal + durable store; no Knative need | | `primary_rail` | `rail-kubernetes` | long-running portal + durable store; no Knative need |
| `bound_reefs` | `[]` at `declared`; `[reef-railiance]` when placed | empty is legal and means "not yet placed" | | `bound_reefs` | `[]` at `declared`; `[reef-railiance]` when placed | empty is legal and means "not yet placed" |
| `data_classification` | `confidential` | identity and account data | | `data_classification` | `confidential` | identity and account data |
| `criticality` | `high` | shared identity-domain service; not the secrets/postgres critical path | | `criticality` | `high` | shared identity-domain service; not the secrets/postgres critical path |
| `exposure` | omit (private) | operator tunnel later if needed; public grant only with a hostname and residual-risk owner | | `exposure` | omit (private) until the KeyCape PKCE edge is decided | an Ingress object already exists; that is **not** a grant (ADR-0008). Decide hostname + residual-risk owner before `public`. |
| Granularity | single-repo rapp **for the pilot** | one deployable; do not group with tenant-engine or secrets-engine unless they share rollback fate. `rapp-first-wave-candidates.md` still flags a future grouped `rapp-netkingdom-identity` (user-engine + tenant-engine + key-cape + flex-auth). That grouping is only legal if those members cannot roll back independently — they almost certainly can. Treat the grouped name as a later composition question, not as a reason to leave `rapp-user-engine` undeclared. | | Granularity | single-repo rapp **for the pilot** | one deployable; do not group with tenant-engine or secrets-engine unless they share rollback fate. `rapp-first-wave-candidates.md` still flags a future grouped `rapp-netkingdom-identity` (user-engine + tenant-engine + key-cape + flex-auth). That grouping is only legal if those members cannot roll back independently — they almost certainly can. Treat the grouped name as a later composition question, not as a reason to leave `rapp-user-engine` undeclared. |
### 10.3 Draft declaration (index, not authority) ### 10.3 Draft declaration (index, not authority)
@ -572,9 +604,7 @@ contract_version: 1.0.0
readiness_state: draft readiness_state: draft
workload_identity: workload_identity:
name: user-engine name: user-engine
package_type: helm-managed-platform-service package_type: manifest-managed-platform-service
chart: rapp-user-engine/user-engine
chart_version: 0.1.0
app_version: 0.1.0 app_version: 0.1.0
data_classification: confidential data_classification: confidential
criticality: high criticality: high
@ -586,28 +616,28 @@ runtime_dependencies:
- kubernetes-api - kubernetes-api
- helm-apply-path - helm-apply-path
- forgejo-oci-registry - forgejo-oci-registry
- openbao-database-secrets-engine - pvc-storage
- keycape-oidc - keycape-oidc
- flex-auth - flex-auth
- openbao
- audit-core
- tenant-engine
secret_references:
- platform/workloads/user-engine/runtime
composition: composition:
purpose: Package and operate the user-engine identity-domain service on Railiance. purpose: Package and operate the user-engine identity-domain service on Railiance.
member_repos: member_repos:
- repo: rapp-user-engine - repo: rapp-user-engine
role: Helm package, smoke, and rollback for the user-engine runtime role: Kubernetes manifests, smoke, and rollback for the user-engine runtime
deployables: deployables:
- user-engine - user-engine
- repo: user-engine
role: application source, image, and schema migrations
deployables:
- user-engine-migrate
upstream_components: upstream_components:
- name: user-engine - name: user-engine
source: forgejo.coulomb.social/<org>/user-engine source: forgejo.coulomb.social/coulomb/user-engine
version: sha256:<pin-at-first-release> version: sha256:e3b5f65bafc1c0260dfdf2567a52766e67506ceb878a51759a2e9a307c4b5eb8
rollout_contract: rollout_contract:
default_mode: helm-upgrade-install default_mode: kubectl-server-side-apply
commands: commands:
- make render
- make server-dry-run - make server-dry-run
- make deploy - make deploy
production_gate: reef-production-approved production_gate: reef-production-approved
@ -617,15 +647,15 @@ smoke_contract:
- readyz-ok-when-store-ready - readyz-ok-when-store-ready
- metrics-requires-proxy-marker - metrics-requires-proxy-marker
- local-issuer-rejected-in-production - local-issuer-rejected-in-production
- postgres-consumer-database-present - live-image-digest-match
commands: commands:
- make verify-live - make verify-live
rollback_contract: rollback_contract:
order: order:
- previous-helm-revision - previous-immutable-image-digest
- apply-reviewed-git-revision - apply-reviewed-git-revision
commands: commands:
- make rollback REVISION=<previous-helm-revision> - make rollback DIGEST=<previous-image-digest>
source_documents: source_documents:
- repo: user-engine - repo: user-engine
path: INTENT.md path: INTENT.md
@ -633,22 +663,25 @@ source_documents:
path: docs/configuration.md path: docs/configuration.md
- repo: user-engine - repo: user-engine
path: docs/operability.md path: docs/operability.md
- repo: user-engine - repo: net-kingdom
path: docs/postgres-durable-store-consumer-requirements.md path: sso-mfa/k8s/user-engine/README.md
- repo: railiance-master - repo: railiance-master
path: schemas/rapp.schema.json path: schemas/rapp.schema.json
- repo: repo-manager - repo: repo-manager
path: docs/RailianceAppDeploymentGuide.md path: docs/RailianceAppDeploymentGuide.md
``` ```
Cardinality note: `user-engine` the *Deployment* belongs to this rapp. Cardinality note: the live deployable name is `user-engine` (the
`user-engine-migrate` is a Job owned by the same rapp. Do not also list Deployment). Migrations run in-process before serve, so do **not**
the Deployment under another rapp. invent a second `user-engine-migrate` deployable unless a separate Job
is added. `user-engine-pg` is a Cluster owned by this package only if
the dedicated-cluster decision stands; if it is inventoried as its own
deployable it still belongs to **this** rapp.
If the migrate Job is considered the same deployable as the app, collapse `ownership_repo: user-engine` already names the source repo. Do not also
to a single `user-engine` deployable and treat migration as a rollout claim the Deployment from `net-kingdom`. A single-member composition
step, not a second live unit. Prefer the single-deployable form unless matches every live rapp; add `user-engine` as a second member only if it
the Job must be inventoried separately for the coverage check. contributes a *different* deployable.
### 10.4 Runtime inputs the chart must accept ### 10.4 Runtime inputs the chart must accept
@ -681,26 +714,32 @@ Logical secret names the app already documents: `database.url`,
in `railiance/app.toml`; do not invent a second naming scheme in the in `railiance/app.toml`; do not invent a second naming scheme in the
chart. chart.
### 10.5 Postgres consumer (to be added in `rapp-postgres`) ### 10.5 Database decision (do not do both)
Sketch only — the owning package is `rapp-postgres`: Live user-engine uses a **dedicated** CNPG Cluster `user-engine-pg`
(postgresql 17.5, db `user_engine`), not a `rapp-postgres` consumer.
tenant-engine and audit-core *are* platform consumers.
```yaml Two legal next states:
apiVersion: rapp-postgres.railiance.io/v1alpha1
kind: PostgresConsumer 1. **Keep dedicated.** Move `user-engine-pg` manifests into the rapp.
metadata: Declare `pvc-storage` (and later `s3-backup-target` if backup is
name: user-engine attached). Do not add a postgres consumer.
spec: 2. **Become a consumer.** Add `rapp-postgres/consumers/user-engine.yaml`
database: user_engine (same shape as `audit-core.yaml`), delete the dedicated Cluster after
schema: user_engine a reviewed cutover. Then drop `pvc-storage` from this rapp.
costAttributionKey: platform:user-engine
clientNamespaces: [user-engine] Pick one in T04. The current live state is (1).
roles:
owner: user_engine_owner ### 10.6 Absorb the NetKingdom apply home
migration: user_engine_migrate
runtime: user_engine_app T03 should copy
tenantKeyingRequired: true `net-kingdom/sso-mfa/k8s/user-engine/` into
``` `rapp-user-engine/manifests/` (or `helm/` only if a real chart is
justified), add Makefile `server-dry-run` / `deploy` / `verify-live` /
`rollback DIGEST=…`, and leave a pointer README in NetKingdom so there
is one apply path. Do not Helm-rewrite the live YAML in the same
change.
### 10.6 What stays in `user-engine` ### 10.6 What stays in `user-engine`
@ -900,3 +939,6 @@ Worked example:
- `user-engine/docs/postgres-durable-store-consumer-requirements.md` - `user-engine/docs/postgres-durable-store-consumer-requirements.md`
- `user-engine/Containerfile` - `user-engine/Containerfile`
- `user-engine/src/user_engine/runtime.py` - `user-engine/src/user_engine/runtime.py`
- `net-kingdom/sso-mfa/k8s/user-engine/` (current live apply home)
- `tenant-engine/deploy/` (closest sibling manifest package)
- `qonto-assistant/docs/knative-runtime-and-rapp-handoff.md` (one apply home)

View file

@ -77,12 +77,15 @@ priority: high
state_hub_task_id: "3ba6828e-7514-4f4d-a1d3-54134e039f55" state_hub_task_id: "3ba6828e-7514-4f4d-a1d3-54134e039f55"
``` ```
Add the Helm chart, Makefile operator targets, `railiance/app.toml`, and Absorb `net-kingdom/sso-mfa/k8s/user-engine/` into
runtime-contract doc described in the guide, derived from `user-engine` `rapp-user-engine/manifests/` (or a Helm chart only if a rewrite is
port, health paths, Containerfile user, and documented env/secret names. justified separately). Add Makefile `server-dry-run` / `deploy` /
`verify-live` / `rollback DIGEST=…` and a runtime-contract doc. Leave a
pointer in NetKingdom so there is one apply path.
`make render` and `make lint` must pass with dummy digests. No cluster `make server-dry-run` must pass. No production apply in this task.
deploy in this task. Package type is `manifest-managed-platform-service` unless the rewrite
to Helm is an explicit later decision.
## Draft platform handoffs ## Draft platform handoffs