Railiance
+Decisions
++ This host is reserved for the informed-decision + approver surface. The service is not deployed yet. +
++ The origin is live so that its OIDC redirect URI can be registered + against a host that actually answers. +
+diff --git a/docs/informed-decision-origin.md b/docs/informed-decision-origin.md new file mode 100644 index 0000000..af0b271 --- /dev/null +++ b/docs/informed-decision-origin.md @@ -0,0 +1,94 @@ +# decisions.coulomb.social — origin for the informed-decision approver surface + +**Status:** manifests written and dry-run clean; **not applied**. +Last reviewed: 2026-09-10 + +## Why this host exists before the app does + +`informed-decision` owns the browser-facing approver UI that `approval-engine` +deliberately does not contain. Its workplan task `INFD-WP-0001-T07` must publish +two strings to `key-cape` — an OIDC `client_id` and a full callback URI — and +close `KEY-WP-0013-T02`, which has been blocked on them since 2026-09-08. + +Both strings are now fixed except for the host: + +| Field | Value | +| --- | --- | +| `client_id` | `informed-decision-approver` | +| Redirect URI | `https://decisions.coulomb.social/auth/callback` | +| Flow | authorization code + S256 PKCE, public client | +| Scopes | `openid`, `approval:read`, `approval:approve` | + +Redirect URIs match byte-exactly at `/authorize`. A registration pointing at a +host that does not answer fails closed at first login and presents as a rejected +approval rather than as a registration defect — which is exactly the failure +`approval-engine` avoided by refusing to invent these strings. So the origin has +to be real before the registration is submitted, and that is S5 work here rather +than in `informed-decision`. + +The approver UI itself is `INFD-WP-0001-T08`, still gated on `approval-engine` +`APPROVAL-WP-0002-T01` and on intake `INFD-IN-0003` (the evidence copy must reach +`audit-core` independently of this component). This host therefore ships a +placeholder first and the real surface later. + +## What is in the repo + +| File | Contents | +| --- | --- | +| `manifests/informed-decision-origin.yaml` | Namespace, placeholder nginx ConfigMap/Deployment/Service on `informed-decision:80` | +| `manifests/informed-decision-ingress.yaml` | Traefik Ingress + `letsencrypt-prod` certificate for `decisions.coulomb.social` | + +The placeholder is `nginxinc/nginx-unprivileged`, read-only root filesystem, +non-root, `noindex`. When the real surface lands it takes over the same Service +name and the origin file shrinks to the Namespace. + +## Deliberate single path rule + +The Ingress carries exactly one rule: `/` Prefix to one backend. + +`reuse-surface` reported on 2026-07-07 that an Ingress declaring an `Exact` +`/health` rule alongside a catch-all `/` Prefix rule on the same host had the +exact match swallowed by the catch-all — the public health URL returned 404 from +the landing container while the pod was `1/1 Ready` and its own probes passed. +Splitting `/auth`, `/api` and `/` across backends here would reproduce that on a +host whose whole purpose is an exact-match redirect URI. If a second backend ever +becomes necessary, set `traefik.ingress.kubernetes.io/router.priority` +explicitly rather than relying on rule order. + +## Preconditions verified 2026-09-10 + +- DNS `decisions.coulomb.social` → `92.205.62.239` (same A record as `reuse`). +- `letsencrypt-prod` ClusterIssuer `Ready=True`. +- No existing Ingress claims `decisions.coulomb.social`. +- Server-side dry-run of all five objects against the live API is clean. + The namespaced four were validated against an existing namespace, since a + server dry-run cannot create the new one first; see + `DRY_RUN_CREATE_NAMESPACES` in `tools/k8s-server-dry-run.sh`. + +## To deploy (operator approval required — not yet given) + +```bash +export KUBECONFIG=$HOME/.kube/config-hosteurope +kubectl apply -f manifests/informed-decision-origin.yaml +kubectl apply -f manifests/informed-decision-ingress.yaml +kubectl -n informed-decision get pods,svc,ingress +kubectl -n informed-decision get certificate informed-decision-tls -w +curl -sSI https://decisions.coulomb.social/ | head -1 +``` + +Only when that `curl` succeeds does `informed-decision` submit +`docs/keycape-client-registration.md` to `key-cape`. + +**Note on the kubeconfig:** `~/.kube/config-hosteurope` names port `16443`, but +the `k3s-api-railiance01` ops-bridge tunnel currently listens on `16444`. +Override with `--server https://127.0.0.1:16444` or fix the kubeconfig; `bridge +check` reports the tunnel healthy either way, so it does not surface the +mismatch. + +## HTTP → HTTPS + +The Ingress is `websecure`-only, following the `forgejo` and `coulomb-social` +pattern, so port 80 stays free for cert-manager HTTP-01 solvers. Plain +`http://decisions.coulomb.social/` will not redirect. `reuse-surface` adds a +separate `-http-redirect` Ingress for this; add one here if a bare-host redirect +is wanted. It is not required for the OIDC flow, which is always `https`. diff --git a/manifests/informed-decision-ingress.yaml b/manifests/informed-decision-ingress.yaml new file mode 100644 index 0000000..c5c8ae0 --- /dev/null +++ b/manifests/informed-decision-ingress.yaml @@ -0,0 +1,40 @@ +# Ingress for the informed-decision approver surface on decisions.coulomb.social. +# +# Requires cert-manager ClusterIssuer letsencrypt-prod and DNS +# decisions.coulomb.social -> cluster IP (A record confirmed 2026-09-10). +# +# ONE path rule on purpose. reuse-surface reported (2026-07-07) that a host +# carrying an Exact /health rule alongside a catch-all `/` Prefix rule had the +# exact match swallowed by the catch-all, so the public health URL 404'd while +# the pod was healthy. Splitting /auth, /api and / across backends here would +# reproduce that. Everything on this host stays on one backend; if the surface +# ever needs a second one, set traefik.ingress.kubernetes.io/router.priority +# explicitly rather than relying on rule order. +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: informed-decision + namespace: informed-decision + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + # websecure only (forgejo/coulomb-social pattern): leave HTTP free for + # cert-manager HTTP-01 solvers. TLS app traffic stays on 443; the ACME + # challenge uses the solver ingress on 80. + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" +spec: + ingressClassName: traefik + tls: + - hosts: [decisions.coulomb.social] + secretName: informed-decision-tls + rules: + - host: decisions.coulomb.social + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: informed-decision + port: + number: 80 diff --git a/manifests/informed-decision-origin.yaml b/manifests/informed-decision-origin.yaml new file mode 100644 index 0000000..377bbf8 --- /dev/null +++ b/manifests/informed-decision-origin.yaml @@ -0,0 +1,173 @@ +# Origin holder for decisions.coulomb.social. +# +# `informed-decision` (INFD-WP-0001-T07) must publish an exact OIDC redirect URI +# to key-cape: https://decisions.coulomb.social/auth/callback. Redirect URIs match +# byte-exactly at /authorize, so the origin has to be real before the registration +# is submitted — a host that resolves but does not answer over TLS fails the same +# way a wrong hostname does, only later and less legibly. +# +# The approver UI itself does not exist yet (INFD-WP-0001-T08, the walking +# skeleton, is still gated on approval-engine and on intake INFD-IN-0003). This +# placeholder exists solely so the host answers and cert-manager can issue. +# When the real surface lands it replaces this Deployment/Service behind the same +# Service name, and this file shrinks to the Namespace. +apiVersion: v1 +kind: Namespace +metadata: + name: informed-decision + labels: + app.kubernetes.io/part-of: informed-decision +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: informed-decision-placeholder + namespace: informed-decision + labels: + app.kubernetes.io/name: informed-decision + app.kubernetes.io/component: placeholder +data: + index.html: | + + +
+ + + +Railiance
++ This host is reserved for the informed-decision + approver surface. The service is not deployed yet. +
++ The origin is live so that its OIDC redirect URI can be registered + against a host that actually answers. +
+