WP-0011 T05: live deploy revenue.coulomb.social on railiance01

Image 0.1.1 (schema path fix), OpenBao ESO store, CNPG cluster, TLS,
pilot Phase re-registered on public API. Founding token local-only.
This commit is contained in:
tegwick 2026-08-05 17:56:38 +02:00
parent 592a75a171
commit ca76fa23a3
13 changed files with 168 additions and 54 deletions

View file

@ -4,7 +4,10 @@ FROM python:3.12-slim AS runtime
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
HOME=/home/app
HOME=/home/app \
TRF_SCHEMAS_DIR=/src/schemas \
TRF_SPECS_DIR=/src/specs \
TRF_MIGRATIONS_DIR=/src/migrations
RUN useradd --create-home --home-dir /home/app --uid 10001 app

View file

@ -87,7 +87,7 @@ The concept's §13 now defines a **Global Contingency Share Determination Rule**
| [TREV-WP-0008](workplans/TREV-WP-0008-governance-and-pilot-rollout.md) | Governance formalization + pilot rollout — **finished**, all 5 tasks done. **T05 accepted 2026-08-05**: first real Phase is `trsl:phase:info-tech-canon-service-surface` (`history/260805-T05-GoLive-info-tech-canon.md`). Org-wide TRSL LICENSE adoption (2026-07-30) was a prior, separate act |
| [TREV-WP-0009](workplans/TREV-WP-0009-target-revenue-control-plane.md) | Target Revenue Control Plane — interactive UI for the `binky` tenant, incl. interactive Development Credit entry creation (`specs/TargetRevenueControlPlaneConcept.md`) — **finished**, all 4 tasks done. **T04 (interactive UI, `src/target_revenue/service/control_plane_app.py`) built on vendored `whynot-design` web components** rather than from scratch, per an explicit feasibility check |
| [TREV-WP-0010](workplans/TREV-WP-0010-development-effort-calculator.md) | Development Effort Calculator — **finished**, all 3 tasks done. Applied to the three real pilot candidates (`history/260730-EffortCalculator-CandidateApplication.md`) — every calculator-derived Initial Target came out materially lower than the earlier hand-picked placeholders, two of three carrying explicit warnings recommending manual review |
| [TREV-WP-0011](workplans/TREV-WP-0011-railiance-reef-deployment.md) | Deploy Trust Service + Control Plane to Railiance (`revenue.coulomb.social`) — **active**; T01T04 done (Dockerfile, `k8s/railiance/`, ArgoCD Application). **T05** remains: operator DNS/OpenBao/image-push/sync + pilot data migration. Guide: [`docs/deployment.md`](docs/deployment.md) |
| [TREV-WP-0011](workplans/TREV-WP-0011-railiance-reef-deployment.md) | Deploy Trust Service + Control Plane **finished**. Live at [`https://revenue.coulomb.social`](https://revenue.coulomb.social) (UI `/ui/`). Pilot Phase registered on public API. Guide: [`docs/deployment.md`](docs/deployment.md) |
| [TREV-WP-0012](workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md) | Phase provenance, ledger reference, and degeneration-policy modeling — **finished**, all 5 tasks done. Decisions (T02T04) synthesized into [`specs/PhaseProvenanceSpecAddendum.md`](specs/PhaseProvenanceSpecAddendum.md) (T05) — **not yet accepted for implementation**; that's the document to discuss before any schema/UI work is filed as its own workplan |
| [TREV-WP-0013](workplans/TREV-WP-0013-remission-credit-automation.md) | Remission Credit automation (degeneration policy execution) — **finished**, all 3 tasks done. Pure `plan_remission` + hosted apply with `system:policy-engine` actor; monthly UTC schedule; Control Plane forecast + apply button |
| [TREV-WP-0014](workplans/TREV-WP-0014-control-plane-extensions-breach-attestation-ui.md) | Control Plane UI: Extension Registry, Breach Records, Conversion Attestation — **finished**, all 4 tasks done. Operator+ register extension / Admin canonicalize; breach form with anonymized-by-default; attestation on phase detail |

View file

@ -86,10 +86,10 @@
| task | TREV-WP-0010-T01 | done | — | workplans/TREV-WP-0010-development-effort-calculator.md |
| task | TREV-WP-0010-T02 | done | — | workplans/TREV-WP-0010-development-effort-calculator.md |
| task | TREV-WP-0010-T03 | done | — | workplans/TREV-WP-0010-development-effort-calculator.md |
| task | TREV-WP-0011-T01 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T02 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T03 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T04 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T01 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T02 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T03 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T04 | done | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0011-T05 | todo | — | workplans/TREV-WP-0011-railiance-reef-deployment.md |
| task | TREV-WP-0012-T01 | done | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |
| task | TREV-WP-0012-T02 | done | — | workplans/TREV-WP-0012-phase-provenance-and-policy-modeling.md |

View file

@ -1,9 +1,11 @@
# Deploying Target Revenue Trust Service + Control Plane
**Workplan:** `workplans/TREV-WP-0011-railiance-reef-deployment.md`
**Public host (target):** `https://revenue.coulomb.social`
**Public host (live 2026-08-05):** `https://revenue.coulomb.social`
**Control Plane UI:** `https://revenue.coulomb.social/ui/`
**Trust Service API:** `https://revenue.coulomb.social/` (e.g. `/phases/...`, `/public-key`)
**Trust Service API:** `https://revenue.coulomb.social/` (e.g. `/phases/...`, `/public-key`)
**Cluster:** railiance01 (`KUBECONFIG=~/.kube/config-hosteurope`)
**Image:** `forgejo.coulomb.social/coulomb/target-revenue:0.1.1`
This deployment does not by itself authorize new Phases beyond what
WP-0008-T05 already recorded. The first pilot Phase is
@ -25,25 +27,25 @@ WP-0008-T05 already recorded. The first pilot Phase is
---
## Operator checklist (first bring-up)
## Operator checklist (first bring-up) — completed 2026-08-05
1. **DNS:** point `revenue.coulomb.social` at the Railiance Traefik/ingress (same path as other `*.coulomb.social` hosts on railiance01).
2. **OpenBao:** create KV path `workloads/target-revenue/runtime-secrets` with:
- `TRF_SIGNING_KEY_HEX` (64 hex chars)
- `TRF_CONTROL_PLANE_SECRET_KEY`
- `TRF_DATABASE_URL` (trf_app DSN to `target-revenue-pg-rw`)
- `TRF_MIGRATE_DATABASE_URL` (trf_owner DSN for migrate/bootstrap Jobs)
3. **CNPG bootstrap secrets** in namespace `target-revenue` (before Cluster reconciles):
- `target-revenue-pg-credentials` — keys as required by CNPG for owner `trf_owner`
- `target-revenue-trf-app-credentials` — password for managed role `trf_app`
4. **Image:** build and push
`forgejo.coulomb.social/coulomb/target-revenue:0.1.0`
(see Dockerfile; bump tag in Deployment + Jobs when releasing).
5. **ArgoCD:** apply
`railiance-platform/argocd/applications/target-revenue.application.yaml`
(once `main` contains `k8s/railiance/`).
6. **After migrate + bootstrap Jobs succeed:** pull founding token from bootstrap Job logs (`TOKEN=...`), store offline; sign in at `/ui/login`.
7. **Migrate pilot Phase data** from the local go-live instance (`trf-pilot-pg` / `:8765`) if that instance still holds the authoritative ledger — export Manifest + ledger JSON and re-register/append on the public instance (or promote the pilot DB dump deliberately). Do not leave two competing ledgers.
Deployed with **kubectl on railiance01** (forgejo/reuse interim model).
Coulombcore ArgoCD Application exists for future multi-cluster sync but was
**not** the live path for this bring-up.
1. **DNS:** `revenue.coulomb.social` A-record → railiance01 — done.
2. **OpenBao policy + ESO token:**
`OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token ./scripts/openbao-eso-token-apply.sh`
+ apply `openbao-target-revenue.clustersecretstore.yaml`.
3. **CNPG secrets + Cluster**, then write
`platform/workloads/target-revenue/runtime-secrets` (signing key, session secret, DSNs).
4. **Image:** `docker build/push …:0.1.1` (0.1.0 had schema-path bug; fixed).
5. **`kubectl apply -k k8s/railiance/`** on railiance01.
6. **Founding token:** bootstrap Job log →
`~/.config/target-revenue/revenue-coulomb-binky.token` (chmod 600; never commit).
7. **Phase POR:** re-registered
`trsl:phase:info-tech-canon-service-surface` on the public API; local
`trf-pilot-pg` is superseded (ledger was empty, so no entry migration).
---

View file

@ -82,18 +82,18 @@ from day one, not merely a git file:
| Item | Value |
| --- | --- |
| Trust Service API | `http://127.0.0.1:8765` (local pilot; not public) |
| Postgres | Docker container `trf-pilot-pg`, port `127.0.0.1:55432`, volume `trf-pilot-pgdata` |
| Migrations | 00010007 applied |
| Licensor credential | `binky` / label `pilot-founder` (token in `~/.config/target-revenue/pilot-binky.token`, never committed) |
| Registration time (t0) | 2026-08-05T13:57:18Z (host `registered_at`) |
| Trust Service API (initial) | `http://127.0.0.1:8765` (local pilot; used only until public deploy) |
| Trust Service API (**POR, 2026-08-05 later same day**) | `https://revenue.coulomb.social` (railiance01; WP-0011 T05) |
| Control Plane | `https://revenue.coulomb.social/ui/` |
| Postgres (POR) | CNPG `target-revenue-pg` in namespace `target-revenue` |
| Licensor credential | `binky` / label `founding-admin` (token in `~/.config/target-revenue/revenue-coulomb-binky.token`, never committed) |
| Registration time (t0) on POR | 2026-08-05T15:55:52Z (public re-registration; local pilot ledger was empty) |
| Extension registered | `trsl:extension:development-license@1.0` |
| Ledger at registration | empty; Outstanding Target = 141,800 EUR |
When WP-0011 lands a public instance, this Phase's Manifest and ledger
export must be migrated or re-anchored deliberately — the git-committed
Manifest remains the durable declaration either way
(`specs/TrustServiceOnboarding.md` §1).
Git-committed Manifest remains the durable declaration
(`specs/TrustServiceOnboarding.md` §1). Public Trust Service is the live
ledger authority after WP-0011 T05.
---

View file

@ -30,7 +30,7 @@ spec:
fsGroup: 10001
containers:
- name: bootstrap
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.0
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
imagePullPolicy: IfNotPresent
env:
- name: TRF_BOOTSTRAP_BINKY

View file

@ -25,12 +25,16 @@ spec:
fsGroup: 10001
containers:
- name: target-revenue
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.0
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
env:
- name: TRF_SCHEMAS_DIR
value: /src/schemas
- name: TRF_SPECS_DIR
value: /src/specs
- name: TRF_DATABASE_URL
valueFrom:
secretKeyRef:

View file

@ -22,7 +22,8 @@ metadata:
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao
# Railiance01 interim store (token auth), not coulombcore's openbao store.
name: openbao-target-revenue
kind: ClusterSecretStore
target:
name: target-revenue-runtime

View file

@ -28,7 +28,7 @@ spec:
fsGroup: 10001
containers:
- name: migrate
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.0
image: forgejo.coulomb.social/coulomb/target-revenue:0.1.1
imagePullPolicy: IfNotPresent
env:
- name: TRF_RUN_MIGRATIONS

View file

@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Mint a read-limited OpenBao token and store it on railiance01 for
# ClusterSecretStore openbao-target-revenue (WP-0011).
set -euo pipefail
POLICY_NAME="${OPENBAO_TARGET_REVENUE_POLICY:-workload-kv-read-target-revenue-runtime}"
POLICY_FILE="${OPENBAO_TARGET_REVENUE_POLICY_FILE:-$HOME/railiance-platform/openbao/policies/workload-kv-read-target-revenue-runtime.hcl}"
BAO_ADDR="${BAO_ADDR:-https://bao.coulomb.social}"
RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-$HOME/.kube/config-hosteurope}"
SECRET_NAME="${OPENBAO_TARGET_REVENUE_ESO_SECRET:-openbao-target-revenue-eso-token}"
SECRET_NS="${OPENBAO_TARGET_REVENUE_ESO_NAMESPACE:-external-secrets}"
if ! command -v bao >/dev/null 2>&1; then
echo "ERROR: bao CLI not found" >&2
exit 1
fi
if [[ ! -f "$POLICY_FILE" ]]; then
echo "ERROR: policy file missing: $POLICY_FILE" >&2
exit 1
fi
echo "OpenBao addr: $BAO_ADDR"
echo "Policy: $POLICY_NAME"
if [[ -n "${BAO_TOKEN:-}" ]]; then
:
elif [[ -n "${OPENBAO_TOKEN_FILE:-}" && -f "${OPENBAO_TOKEN_FILE}" ]]; then
BAO_TOKEN="$(head -n 1 "${OPENBAO_TOKEN_FILE}")"
else
read -r -s -p "OpenBao operator token: " BAO_TOKEN
echo >&2
fi
if [[ -z "${BAO_TOKEN:-}" ]]; then
echo "ERROR: empty OpenBao token" >&2
exit 1
fi
export BAO_ADDR BAO_TOKEN
health="$(curl -fsS "$BAO_ADDR/v1/sys/health")"
if echo "$health" | grep -q '"sealed":true'; then
echo "ERROR: OpenBao at $BAO_ADDR reports sealed" >&2
exit 1
fi
bao policy write "$POLICY_NAME" "$POLICY_FILE"
token_json="$(bao token create -policy="$POLICY_NAME" \
-display-name="eso-target-revenue-runtime" -period=720h -format=json)"
token="$(python3 -c "import json,sys; print(json.load(sys.stdin)['auth']['client_token'])" <<<"$token_json")"
KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl create namespace "$SECRET_NS" --dry-run=client -o yaml \
| KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl apply -f -
KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl create secret generic "$SECRET_NAME" \
--namespace "$SECRET_NS" \
--from-literal=token="$token" \
--dry-run=client -o yaml \
| KUBECONFIG="$RAILIANCE01_KUBECONFIG" kubectl apply -f -
unset BAO_TOKEN token token_json
echo "ok: applied $SECRET_NS/$SECRET_NAME on railiance01 (token not printed)"

View file

@ -10,13 +10,32 @@ small markdown library for six profile pages and one policy page.
from __future__ import annotations
import os
from pathlib import Path
from typing import Any
import markdown
import yaml
_SPECS_DIR = Path(__file__).resolve().parents[3] / "specs"
def _resolve_specs_dir() -> Path:
if env := os.environ.get("TRF_SPECS_DIR"):
return Path(env)
here = Path(__file__).resolve()
candidates = [
here.parents[3] / "specs", # monorepo: .../target-revenue/specs
Path("/src/specs"), # container image layout
]
for candidate in candidates:
if candidate.is_dir():
return candidate
raise FileNotFoundError(
"cannot find TRF specs directory; set TRF_SPECS_DIR or install "
"specs at /src/specs"
)
_SPECS_DIR = _resolve_specs_dir()
# kind -> subdirectory name under specs/. Only these two exist today
# (WP-0015-T02); a third kind (e.g. "calculators") can be added here if

View file

@ -14,12 +14,33 @@ at import/call time (TSD §6.1 determinism).
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any
import jsonschema
_SCHEMAS_DIR = Path(__file__).resolve().parents[2] / "schemas"
def _resolve_schemas_dir() -> Path:
"""Locate `schemas/` in monorepo, container, or packaged installs."""
if env := os.environ.get("TRF_SCHEMAS_DIR"):
return Path(env)
here = Path(__file__).resolve()
candidates = [
here.parent / "schemas", # if schemas are package data
here.parents[2] / "schemas", # repo: src/target_revenue/../..
Path("/src/schemas"), # container image layout (WP-0011 Dockerfile)
]
for candidate in candidates:
if candidate.is_dir():
return candidate
raise FileNotFoundError(
"cannot find TRF schemas directory; set TRF_SCHEMAS_DIR or install "
"schemas next to the package / at /src/schemas"
)
_SCHEMAS_DIR = _resolve_schemas_dir()
# TSD §6.2/§3.3: an extension's allocation.rule must not redefine these.
CORE_TERMS = [

View file

@ -4,7 +4,7 @@ type: workplan
title: "Deploy Trust Service and Control Plane to the Railiance reef"
domain: infotech
repo: target-revenue
status: active
status: finished
owner: claude
topic_slug: infotech
created: "2026-07-30"
@ -100,23 +100,27 @@ bootstrap Jobs. ArgoCD Application:
```task
id: TREV-WP-0011-T05
status: todo
status: done
priority: medium
state_hub_task_id: "37847415-19c4-43b5-8b0d-de7a4211bfdd"
```
**Bootstrap and smoke-test the live deployment.**
**Remaining operator work** (not fully automatable from this workstation
without cluster credentials + OpenBao write + DNS + image push):
**Result (2026-08-05):** Live on **railiance01** (not coulombcore ArgoCD —
same interim model as forgejo/reuse; DNS points to Railiance01):
1. DNS A/CNAME for `revenue.coulomb.social`
2. Seed OpenBao runtime secrets + CNPG bootstrap Secrets
3. `docker build/push` image `0.1.0`
4. Commit/push Application + app manifests; wait for ArgoCD sync
5. Confirm TLS + `/ui/login` + `/healthz`
6. Migrate pilot Phase ledger from local `trf-pilot-pg` if public instance
becomes the new authority
| Check | Result |
| --- | --- |
| DNS | `revenue.coulomb.social` → railiance01 (operator) |
| Image | `forgejo.coulomb.social/coulomb/target-revenue:0.1.1` |
| OpenBao ESO | `ClusterSecretStore/openbao-target-revenue` Ready; runtime SecretSynced |
| CNPG | `target-revenue-pg` healthy |
| Migrations + bootstrap Jobs | complete; founding token local-only at `~/.config/target-revenue/revenue-coulomb-binky.token` |
| TLS | cert-manager Ready (`target-revenue-tls`) |
| `GET /healthz` | 200 |
| `GET /ui/login` | 200 |
| Pilot Phase re-registered | `trsl:phase:info-tech-canon-service-surface` on public API (empty ledger; public instance is now POR — local `trf-pilot-pg` is superseded) |
Docs ready: `docs/deployment.md`. No production smoke Phase will be
registered (append-only; pilot Phase already exists).
Schema-path fix in 0.1.1 (`TRF_SCHEMAS_DIR` / validation resolve) after
first image failed package-relative schema loads.