WP-0011: deploy scaffolding for revenue.coulomb.social

Record T01 package (in-repo k8s/railiance, combined /ui + API app,
dedicated CNPG). Add Dockerfile, healthz, migration/bootstrap scripts,
kustomize manifests, ArgoCD Application (in railiance-platform), and
docs/deployment.md. T05 left open for operator DNS/OpenBao/image push.
This commit is contained in:
tegwick 2026-08-05 16:41:00 +02:00
parent 3064c0fe0c
commit 9e4e84f9ee
26 changed files with 691 additions and 142 deletions

View file

@ -106,6 +106,12 @@ def read_extension(
return result
@app.get("/healthz")
def healthz() -> dict[str, str]:
"""Liveness/readiness probe target (WP-0011) — no auth, no DB."""
return {"status": "ok"}
@app.get("/public-key")
def read_public_key() -> dict[str, str]:
"""The Ed25519 public key ledger entry signatures verify against.

View file

@ -0,0 +1,32 @@
"""Single-process ASGI app: Trust Service + Control Plane (WP-0011).
Both FastAPI apps remain independently importable for local/dev
(`app.py`, `control_plane_app.py`). This module composes them for the
container entrypoint:
- ``GET /healthz`` liveness/readiness (no auth, no DB)
- ``/ui/*`` Control Plane (session UI)
- ``/*`` Trust Service public/API surface
Ingress may also expose path-based routing; the in-process mount is the
default so a single Service port works without Traefik strip-prefix.
"""
from __future__ import annotations
from fastapi import FastAPI
from .app import app as trust_service_app
from .control_plane_app import app as control_plane_app
app = FastAPI(title="Target Revenue — Trust Service + Control Plane", version="0.1.0")
@app.get("/healthz")
def healthz() -> dict[str, str]:
return {"status": "ok"}
# More specific mount first.
app.mount("/ui", control_plane_app)
app.mount("/", trust_service_app)

View file

@ -107,9 +107,15 @@ def require_login(
return licensor
def _root_path(request: Request) -> str:
"""URL prefix when mounted under combined.py ``/ui`` (WP-0011)."""
return request.scope.get("root_path", "") or ""
def _template_context(request: Request, licensor: registry.Licensor | None, **extra: Any) -> dict[str, Any]:
context = {
"request": request,
"root_path": _root_path(request),
"session_credential_label": licensor.credential_label if licensor else None,
"session_rights": licensor.rights if licensor else None,
"session_licensor_id": licensor.licensor_id if licensor else None,
@ -124,12 +130,24 @@ def _redirect(url: str, request: Request, flash: str | None = None, variant: str
if flash:
request.session["flash"] = flash
request.session["flash_variant"] = variant
# Prefix relative Control Plane paths when mounted under /ui.
if url.startswith("/") and not url.startswith("//"):
url = _root_path(request) + url
return RedirectResponse(url=url, status_code=303)
@app.exception_handler(_NotAuthenticated)
async def _redirect_to_login(request: Request, exc: _NotAuthenticated):
return RedirectResponse(url="/login", status_code=303)
return RedirectResponse(url=_root_path(request) + "/login", status_code=303)
# --- Health (WP-0011) -------------------------------------------------------
@app.get("/healthz")
def healthz() -> dict[str, str]:
"""Liveness/readiness probe target — no auth, no DB."""
return {"status": "ok"}
# --- Auth --------------------------------------------------------------
@ -138,7 +156,7 @@ async def _redirect_to_login(request: Request, exc: _NotAuthenticated):
@app.get("/login")
def login_form(request: Request, licensor: registry.Licensor | None = Depends(get_session_licensor)):
if licensor is not None:
return RedirectResponse(url="/", status_code=303)
return _redirect("/", request)
return templates.TemplateResponse(request, "login.html", _template_context(request, None))
@ -151,17 +169,15 @@ def login_submit(
try:
registry.authenticate(conn, token)
except registry.RegistrationError:
request.session["flash"] = "Invalid or revoked credential token."
request.session["flash_variant"] = "danger"
return RedirectResponse(url="/login", status_code=303)
return _redirect("/login", request, "Invalid or revoked credential token.", "danger")
request.session["token"] = token
return RedirectResponse(url="/", status_code=303)
return _redirect("/", request)
@app.post("/logout")
def logout(request: Request):
request.session.clear()
return RedirectResponse(url="/login", status_code=303)
return _redirect("/login", request)
# --- Dashboard -----------------------------------------------------------

View file

@ -13,7 +13,7 @@
{% endif %}
<h3>Issue a new credential</h3>
<form class="wn-form" method="post" action="/admin/credentials">
<form class="wn-form" method="post" >
<wn-field-row label="Credential label (human name)">
<wn-input name="credential_label" required></wn-input>
</wn-field-row>
@ -29,7 +29,7 @@
</form>
<h3>Revoke a credential</h3>
<form class="wn-form" method="post" action="/admin/credentials/revoke">
<form class="wn-form" method="post" >
<wn-field-row label="Token to revoke">
<wn-input name="token" required></wn-input>
</wn-field-row>

View file

@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<title>{% block title %}Target Revenue Control Plane{% endblock %}</title>
<link rel="stylesheet" href="/static/whynot-design/styles/colors_and_type.css">
<link rel="stylesheet" href="/static/whynot-design/styles/components.css">
<link rel="stylesheet" >
<link rel="stylesheet" >
<script type="importmap">
{
"imports": {
@ -12,7 +12,7 @@
}
}
</script>
<script type="module" src="/static/whynot-design/index.js"></script>
<script type="module" src="{{ root_path }}/static/whynot-design/index.js"></script>
<style>
body { max-width: 960px; margin: 0 auto; padding: 1.5rem; }
form.wn-form { display: flex; flex-direction: column; gap: 0.75rem; max-width: 480px; }
@ -30,12 +30,12 @@
<p>
Signed in as <strong>{{ session_credential_label }}</strong>
({{ session_rights }}) for <strong>{{ session_licensor_id }}</strong>
&middot; <a href="/">Dashboard</a>
&middot; <a href="/extensions">Extensions</a>
{% if session_rights in ("operator", "admin") %}&middot; <a href="/proposals">Proposals</a>{% endif %}
{% if session_rights == "admin" %}&middot; <a href="/admin/credentials">Credentials</a>{% endif %}
&middot; <a href="/audit">Audit log</a>
&middot; <form method="post" action="/logout" style="display:inline"><button type="submit">Sign out</button></form>
&middot; <a >Dashboard</a>
&middot; <a >Extensions</a>
{% if session_rights in ("operator", "admin") %}&middot; <a >Proposals</a>{% endif %}
{% if session_rights == "admin" %}&middot; <a >Credentials</a>{% endif %}
&middot; <a >Audit log</a>
&middot; <form method="post" style="display:inline"><button type="submit">Sign out</button></form>
</p>
{% endif %}

View file

@ -6,7 +6,7 @@
</wn-page-header>
{% if session_rights in ("operator", "admin") %}
<p><a href="/phases/new">+ Register a new Phase</a></p>
<p><a >+ Register a new Phase</a></p>
{% endif %}
{% if phases %}
@ -18,7 +18,7 @@
<td>{{ phase.phase.id }}</td>
<td>{{ phase.phase.milestone_release.name }}</td>
<td>{{ phase.phase.initial_target.amount }} {{ phase.phase.initial_target.currency }}</td>
<td><a href="/phases/{{ phase.phase.id }}">View</a></td>
<td><a >View</a></td>
</tr>
{% endfor %}
</tbody>

View file

@ -31,7 +31,7 @@
<td>{{ ext.licensor_id }}</td>
<td>
{% if session_rights == "admin" and ext.status == "registered" %}
<form method="post" action="/extensions/promote" style="display:inline" class="wn-form">
<form method="post" style="display:inline" class="wn-form">
<input type="hidden" name="extension_id" value="{{ ext.extension_id }}">
<input type="hidden" name="version" value="{{ ext.version }}">
<wn-button type="submit" variant="secondary">Promote to canonical</wn-button>
@ -53,7 +53,7 @@
{% if session_rights in ("operator", "admin") %}
<h3>Register a new extension</h3>
<form class="wn-form" method="post" action="/extensions">
<form class="wn-form" method="post" >
<wn-field-row label="Extension id (trsl:extension:...)">
<wn-input name="extension_id" required></wn-input>
</wn-field-row>

View file

@ -5,7 +5,7 @@
<span slot="title">Sign in</span>
</wn-page-header>
<p>Paste the credential token you were issued by an Admin.</p>
<form class="wn-form" method="post" action="/login">
<form class="wn-form" method="post" >
<wn-field-row label="Credential token">
<wn-input type="password" name="token" required></wn-input>
</wn-field-row>

View file

@ -11,7 +11,7 @@
<p>
Raw reference: <code>{{ manifest.phase.ledger }}</code>
{% if manifest.phase.ledger.startswith('/phases/') %}
&middot; <a href="{{ manifest.phase.ledger }}">view live JSON</a>
&middot; <a >view live JSON</a>
{% endif %}
</p>
</details>
@ -28,7 +28,7 @@
<td>Degeneration policy</td>
<td>
{{ manifest.phase.degeneration_policy }}
&middot; <a href="/reference/policies/{{ policy_slug(manifest.phase.degeneration_policy) }}">view spec</a>
&middot; <a >view spec</a>
</td>
</tr>
<tr><td>Longstop</td><td>{{ metrics.facts.longstop_at or "—" }}</td></tr>
@ -63,7 +63,7 @@
</tr>
</table>
{% if session_rights in ("operator", "admin") %}
<form class="wn-form" method="post" action="/phases/{{ manifest.phase.id }}/remission" style="margin-bottom:1.5rem;">
<form class="wn-form" method="post" style="margin-bottom:1.5rem;">
<wn-button type="submit" variant="secondary">Apply policy remission now</wn-button>
<p style="color:#888;font-size:0.85rem;margin-top:0.5rem;">
Writes a <code>remission-credit</code> delta under <code>system:policy-engine</code>
@ -90,7 +90,7 @@
<h3>
{% if session_rights == "contributor" %}Propose a Development Credit entry{% else %}Add a Development Credit entry{% endif %}
</h3>
<form class="wn-form" method="post" action="/phases/{{ manifest.phase.id }}/ledger">
<form class="wn-form" method="post" >
<wn-field-row label="Entry id (trsl:entry:...)">
<wn-input name="entry_id" required></wn-input>
</wn-field-row>
@ -180,7 +180,7 @@
an affirmative CUA authorization check (License V1C1 §7.4) — this form
records that assertion; it does not verify the CUA text.
</p>
<form class="wn-form" method="post" action="/phases/{{ manifest.phase.id }}/breach">
<form class="wn-form" method="post" >
<wn-field-row label="Record id (unique)">
<wn-input name="record_id" required></wn-input>
</wn-field-row>

View file

@ -9,7 +9,7 @@
It does not itself authorize going live for a repo — see
<code>workplans/TREV-WP-0008-governance-and-pilot-rollout.md</code> T05.
</p>
<form class="wn-form" method="post" action="/phases/new">
<form class="wn-form" method="post" >
<wn-field-row label="Phase id (trsl:phase:...)">
<wn-input name="phase_id" required></wn-input>
</wn-field-row>
@ -50,7 +50,7 @@
<wn-input name="degeneration_policy" value="trsl:policy:linear-longstop-v0@1.0" required></wn-input>
</wn-field-row>
<p style="margin-top:-0.5rem;color:#888;font-size:0.85rem;">
<a href="/reference/policies/linear-longstop-v0">view the linear-longstop-v0 spec</a>
<a >view the linear-longstop-v0 spec</a>
</p>
<wn-field-row label="Longstop date (ISO 8601)">
<wn-input name="longstop_at" required></wn-input>

View file

@ -16,10 +16,10 @@
<td>{{ p.entry.amount }}</td>
<td>{{ p.created_at }}</td>
<td>
<form method="post" action="/proposals/{{ p.id }}/approve" style="display:inline">
<form method="post" style="display:inline">
<wn-button type="submit" variant="primary">Approve</wn-button>
</form>
<form method="post" action="/proposals/{{ p.id }}/reject" style="display:inline">
<form method="post" style="display:inline">
<wn-button type="submit" variant="secondary">Reject</wn-button>
</form>
</td>