--- id: TREV-WP-0011 type: workplan title: "Deploy Trust Service and Control Plane to the Railiance reef" domain: infotech repo: target-revenue status: active owner: claude topic_slug: infotech created: "2026-07-30" updated: "2026-07-30" state_hub_workstream_id: "f40fa906-9fbc-4445-9ebc-62ec46ac17b5" --- # Deploy Trust Service and Control Plane to the Railiance reef Deploy the hosted Trust Service (`service/app.py`, WP-0006) and the Control Plane interactive UI (`service/control_plane_app.py`, WP-0009-T04) to Railiance infrastructure-as-code, on a rail hosted in `reef-railiance`'s grouped home substrate, fronted by the subdomain `revenue.coulomb.social`. This is an infrastructure delivery workplan, not a governance one: it does not authorize a real Phase to go live for any repo. That gate remains `workplans/TREV-WP-0008-governance-and-pilot-rollout.md` T05, independent of whether the software is reachable at a public URL. The Control Plane UI itself already carries this disclaimer in its footer (`service/control_plane_templates/base.html`) and will continue to once deployed. **Reference deployment**: `railiance-vergabe-teilnahme` / `railiance-apps/charts/vergabe-teilnahme` is the closest existing precedent — a Django app, its own Postgres, a Helm chart living in `railiance-apps`, an `Ingress` with `cert-manager.io/cluster-issuer: letsencrypt-prod`, and runtime secrets via an `ExternalSecret` reading an OpenBao `ClusterSecretStore`. `issue-core` is a second precedent using a different pattern — its own repo carries `k8s/railiance` and an ArgoCD `Application` points `repoURL` directly at that repo rather than at `railiance-apps`. T01 below has to pick between these two patterns (or a third) rather than assume one. ```task id: TREV-WP-0011-T01 status: todo priority: high human_accept_required: true state_hub_task_id: "8dffe5e1-6f2b-4020-ac07-a108a8fc2edc" ``` **Decide the deployment pattern (human gate).** Concretely: - **Manifest home**: does `target-revenue` carry its own `k8s/railiance` path (like `issue-core`) with an ArgoCD `Application` pointing straight at this repo, or does the Helm chart/Ingress/manifests live in `railiance-apps/charts/target-revenue` alongside `vergabe-teilnahme` (this repo's TRSL licensing and its being a monetization-framework repo, not an application repo, may argue for keeping deploy manifests out-of-tree)? - **Process topology**: `service/app.py` (Trust Service) and `service/control_plane_app.py` (Control Plane UI) are two separate FastAPI `app` objects today, sharing the same Postgres and the same Ed25519 signing key. Deploy as two Deployments/Services behind one Ingress with path-based routing, or mount one inside the other (`app.mount("/control-plane", control_plane_app)`) into a single process/image? The latter needs a small code change (WP-0009-T04's Optional-Next-Step note flagged this as undecided); the former needs no code change but two sets of secrets/probes. - **Database**: a dedicated CNPG (`CloudNativePG`) `Cluster` for this workload's Postgres (matching the `cnpg-option-a-*` manifests already in `railiance-apps`), sized appropriately for a pilot-scale Trust Service instance — not the shared state-hub Postgres, per this project's own standing rule about never reusing that instance for hosted-service data. - **Secrets**: which values go through OpenBao + `ExternalSecret` (`TRF_SIGNING_KEY_HEX`, `TRF_CONTROL_PLANE_SECRET_KEY`, `TRF_DATABASE_URL`/the `trf_app` role password) versus which, if any, are fine as plain Helm `values.yaml` — following the `reuse-surface-runtime-externalsecret.yaml` pattern, one `ClusterSecretStore` per workload namespace. - **Subdomain and TLS**: confirm `revenue.coulomb.social` DNS is (or will be) pointed at the Railiance ingress, and that `letsencrypt-prod` issuing for a `coulomb.social` subdomain has no unresolved prerequisite (SAN limits, existing wildcard, etc.) — check `railiance-infra`/ `railiance-platform` docs rather than assuming parity with `whywhynot.de`. - **Founding credential bootstrap**: how the first `binky` admin credential is seeded into the deployed database — an idempotent migration-adjacent script, or a documented one-time manual `kubectl exec`/`psql` step, consistent with how `railiance-apps`/`railiance-platform` already handle comparable one-time bootstrap actions. Record the decision here (Result) before any manifest is written — this gate exists because every later task in this workplan assumes a specific answer to each bullet above. ```task id: TREV-WP-0011-T02 status: todo priority: high state_hub_task_id: "d4b3dae4-eb2a-4bed-bc10-5ce90d972cec" ``` **Containerize.** Add a `Dockerfile` (and, if T01 decided on two processes, two images or one image with two entrypoints/commands) building the `service` extras (`fastapi`, `uvicorn`, `jinja2`, `itsdangerous`, `python-multipart`, `psycopg[binary]`, `psycopg-pool`) plus `static/whynot-design` and `control_plane_templates/` as part of the image — nothing in T04's implementation currently assumes local filesystem paths outside the package, but this should be verified for a container context (non-root user, read-only root filesystem where the app's own static/ template files don't need write access). Push to `forgejo.coulomb.social/coulomb/target-revenue` per the existing image registry convention (`vergabe-teilnahme`'s `values.yaml` `image.repository` pattern). ```task id: TREV-WP-0011-T03 status: todo priority: high state_hub_task_id: "06567eb5-d924-4100-9cd5-19478121c3c7" ``` **Provision the database and secrets** per T01's decision: a CNPG `Cluster`, the `0001`–`0006` migrations applied against it (as a Job, an init step, or a documented manual run — decide which, consistent with however `railiance-apps`'s other CNPG-backed workloads handle first-boot migration), the `trf_app` role and its grants (already defined in the migration files themselves), an `OpenBao` `ClusterSecretStore` + `ExternalSecret` surfacing `TRF_SIGNING_KEY_HEX`, `TRF_CONTROL_PLANE_SECRET_KEY`, and the `trf_app` password/DSN into the workload's namespace. ```task id: TREV-WP-0011-T04 status: todo priority: high state_hub_task_id: "e6ecf3e5-3a9d-4f00-92b1-a05817b19013" ``` **Helm chart, Ingress, and ArgoCD wiring.** Following T01's chosen home (own-repo `k8s/railiance` vs. `railiance-apps/charts/target-revenue`): Deployment(s)/Service(s)/Ingress for `revenue.coulomb.social` with `cert-manager.io/cluster-issuer: letsencrypt-prod` and TLS, readiness/ liveness probes against a real health path (neither `service/app.py` nor `control_plane_app.py` currently exposes one — add a trivial `/healthz` to each rather than probing an authenticated or Postgres-dependent route), and an ArgoCD `Application` (`railiance-tenants` project, matching `issue-core.application.yaml`'s shape) with automated sync. ```task id: TREV-WP-0011-T05 status: todo priority: medium state_hub_task_id: "37847415-19c4-43b5-8b0d-de7a4211bfdd" ``` **Bootstrap and smoke-test the live deployment.** Seed the founding `binky` admin credential per T01's decision, confirm `https://revenue.coulomb.social/login` is reachable and TLS-valid, sign in, register a throwaway test Phase (id clearly marked as a smoke test, not a real candidate), append a Development Credit entry, confirm the audit log and metrics reflect it, then decide whether to leave that smoke-test Phase in place (clearly labeled) or remove it — Phase Manifests are append-only by design (no delete path), so if a smoke test is unacceptable to leave behind, use a disposable local/staging instance for this step, not the production deployment, and only do a read-only login check against the real one. Update `README.md`'s WP-0011 summary row and this workplan's Result sections; add a short deployment note (URL, how to sign in, that no Phase go-live is authorized by deployment alone) to `docs/` or `SCOPE.md` as appropriate.