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:
parent
3064c0fe0c
commit
9e4e84f9ee
26 changed files with 691 additions and 142 deletions
|
|
@ -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.
|
||||
|
|
|
|||
32
src/target_revenue/service/combined.py
Normal file
32
src/target_revenue/service/combined.py
Normal 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)
|
||||
|
|
@ -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 -----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
· <a href="/">Dashboard</a>
|
||||
· <a href="/extensions">Extensions</a>
|
||||
{% if session_rights in ("operator", "admin") %}· <a href="/proposals">Proposals</a>{% endif %}
|
||||
{% if session_rights == "admin" %}· <a href="/admin/credentials">Credentials</a>{% endif %}
|
||||
· <a href="/audit">Audit log</a>
|
||||
· <form method="post" action="/logout" style="display:inline"><button type="submit">Sign out</button></form>
|
||||
· <a >Dashboard</a>
|
||||
· <a >Extensions</a>
|
||||
{% if session_rights in ("operator", "admin") %}· <a >Proposals</a>{% endif %}
|
||||
{% if session_rights == "admin" %}· <a >Credentials</a>{% endif %}
|
||||
· <a >Audit log</a>
|
||||
· <form method="post" style="display:inline"><button type="submit">Sign out</button></form>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<p>
|
||||
Raw reference: <code>{{ manifest.phase.ledger }}</code>
|
||||
{% if manifest.phase.ledger.startswith('/phases/') %}
|
||||
· <a href="{{ manifest.phase.ledger }}">view live JSON</a>
|
||||
· <a >view live JSON</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</details>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
<td>Degeneration policy</td>
|
||||
<td>
|
||||
{{ manifest.phase.degeneration_policy }}
|
||||
· <a href="/reference/policies/{{ policy_slug(manifest.phase.degeneration_policy) }}">view spec</a>
|
||||
· <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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue