INFD-WP-0001-T07 has been blocked since 2026-09-08 on an OIDC redirect URI it cannot publish without a real deployed origin, which in turn blocks key-cape's KEY-WP-0013-T02. The operator assigned decisions.coulomb.social; DNS already resolves to the cluster address. Adds the Ingress + letsencrypt-prod certificate for the host and a placeholder nginx backend, so the origin answers before the approver UI itself exists (INFD-WP-0001-T08 is still gated on approval-engine and on intake INFD-IN-0003). A redirect URI matches byte-exactly at /authorize, so a host that resolves but does not complete a TLS handshake fails closed at first login and presents as a rejected approval rather than a registration defect. The Ingress carries one path rule on purpose: reuse-surface reported on 2026-07-07 that an Exact rule alongside a catch-all Prefix rule on the same host was swallowed by the catch-all. That trap is worth avoiding on a host whose entire purpose is exact-match redirect handling. Dry-run clean against the live API; deliberately not applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EJbh7o7UWF4tQ5jxygnNGu Assistant: claude-code Assistant-Model: opus Assistant-Process: 2072522@bnt-lap001 Assistant-Session: 46173adf-7302-4ede-99d6-963b61359928
173 lines
5.3 KiB
YAML
173 lines
5.3 KiB
YAML
# 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: |
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="robots" content="noindex, nofollow">
|
|
<title>Decisions — Railiance</title>
|
|
<style>
|
|
:root { color-scheme: light dark; }
|
|
body {
|
|
margin: 0; min-height: 100vh;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font: 16px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
background: #f7f7f5; color: #1a1a19;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
body { background: #14140f; color: #e8e8e3; }
|
|
}
|
|
main { max-width: 34rem; padding: 2rem 1.5rem; }
|
|
h1 { font-size: 1.5rem; margin: 0 0 .5rem; }
|
|
p { margin: 0 0 .75rem; }
|
|
.eyebrow {
|
|
text-transform: uppercase; letter-spacing: .08em;
|
|
font-size: .75rem; opacity: .6; margin-bottom: .25rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<p class="eyebrow">Railiance</p>
|
|
<h1>Decisions</h1>
|
|
<p>
|
|
This host is reserved for the <strong>informed-decision</strong>
|
|
approver surface. The service is not deployed yet.
|
|
</p>
|
|
<p>
|
|
The origin is live so that its OIDC redirect URI can be registered
|
|
against a host that actually answers.
|
|
</p>
|
|
</main>
|
|
</body>
|
|
</html>
|
|
# nginx serves the page on 8080 so the container needs no root.
|
|
default.conf: |
|
|
server {
|
|
listen 8080;
|
|
server_name _;
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
location = /healthz {
|
|
access_log off;
|
|
add_header Content-Type text/plain;
|
|
return 200 'ok';
|
|
}
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
}
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: informed-decision
|
|
namespace: informed-decision
|
|
labels:
|
|
app.kubernetes.io/name: informed-decision
|
|
app.kubernetes.io/component: placeholder
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: informed-decision
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: informed-decision
|
|
app.kubernetes.io/component: placeholder
|
|
spec:
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 101
|
|
runAsGroup: 101
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: nginx
|
|
image: nginxinc/nginx-unprivileged:1.27-alpine
|
|
ports:
|
|
- name: http
|
|
containerPort: 8080
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: [ALL]
|
|
readinessProbe:
|
|
httpGet: { path: /healthz, port: http }
|
|
initialDelaySeconds: 2
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet: { path: /healthz, port: http }
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 30
|
|
resources:
|
|
requests: { cpu: 10m, memory: 24Mi }
|
|
limits: { memory: 64Mi }
|
|
volumeMounts:
|
|
- name: content
|
|
mountPath: /usr/share/nginx/html/index.html
|
|
subPath: index.html
|
|
readOnly: true
|
|
- name: content
|
|
mountPath: /etc/nginx/conf.d/default.conf
|
|
subPath: default.conf
|
|
readOnly: true
|
|
- name: cache
|
|
mountPath: /var/cache/nginx
|
|
- name: run
|
|
mountPath: /tmp
|
|
volumes:
|
|
- name: content
|
|
configMap:
|
|
name: informed-decision-placeholder
|
|
- name: cache
|
|
emptyDir: {}
|
|
- name: run
|
|
emptyDir: {}
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: informed-decision
|
|
namespace: informed-decision
|
|
labels:
|
|
app.kubernetes.io/name: informed-decision
|
|
spec:
|
|
type: ClusterIP
|
|
selector:
|
|
app.kubernetes.io/name: informed-decision
|
|
ports:
|
|
- name: http
|
|
port: 80
|
|
targetPort: http
|