Go-live T05 + WP-0013/0014: first Phase and Control Plane completion

Accept WP-0008-T05 for trsl:phase:info-tech-canon-service-surface
(history/260805-T05-GoLive-info-tech-canon.md). Finish WP-0013 remission
automation and WP-0014 extension/breach/attestation Control Plane UI.
Update SCOPE, README, and pilot-candidate notes for pilot Stage 1.
This commit is contained in:
tegwick 2026-08-05 16:00:06 +02:00
parent f56d82f09a
commit 3064c0fe0c
18 changed files with 1676 additions and 72 deletions

View file

@ -18,7 +18,7 @@ from fastapi import Depends, FastAPI, HTTPException, Request
from psycopg import Connection
from psycopg_pool import ConnectionPool
from .. import attestation, breach_record, ledger, metrics, registry
from .. import attestation, breach_record, ledger, metrics, registry, remission
from . import keys
app = FastAPI(title="Target Revenue Trust Service — Registries", version="0.1.0")
@ -152,7 +152,65 @@ def read_metrics(
if manifest is None:
raise HTTPException(status_code=404, detail="phase not found")
entries = ledger.get_ledger(conn, phase_id)
return metrics.compute_metrics(manifest, entries, metrics.utcnow())
activated_at = remission.get_phase_registered_at(conn, phase_id)
return metrics.compute_metrics(
manifest, entries, metrics.utcnow(), activated_at=activated_at
)
@app.post("/phases/{phase_id}/remission", status_code=201)
def apply_phase_remission(
phase_id: str,
licensor: registry.Licensor = Depends(get_licensor),
conn: Connection = Depends(get_connection),
signing_key=Depends(get_signing_key),
) -> dict[str, Any]:
"""On-demand linear-longstop remission apply (WP-0013).
Auth required (Operator+ of the Phase's Licensor). The ledger entry
itself is attributed to `system:policy-engine`, not the calling human
the caller only authorizes the run. Returns 200-shaped body with
`status: up_to_date` and no entry when the Phase is already current
(idempotent).
"""
if not registry.has_right(licensor.rights, "operator"):
raise HTTPException(status_code=403, detail="operator rights required")
manifest = registry.get_phase_manifest(conn, phase_id)
if manifest is None:
raise HTTPException(status_code=404, detail="phase not found")
owner = conn.execute(
"SELECT licensor_id FROM phase_manifests WHERE phase_id = %s", (phase_id,)
).fetchone()[0]
if owner != licensor.licensor_id:
raise HTTPException(status_code=403, detail="not authorized for this Phase")
try:
entry = remission.apply_remission_for_phase(conn, phase_id, signing_key)
except registry.RegistrationError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
if entry is None:
return {"phase_id": phase_id, "status": "up_to_date", "entry": None}
return {"phase_id": phase_id, "status": "appended", "entry": entry}
@app.post("/remission/run", status_code=200)
def run_remission_batch(
licensor: registry.Licensor = Depends(get_licensor),
conn: Connection = Depends(get_connection),
signing_key=Depends(get_signing_key),
) -> dict[str, Any]:
"""Batch scheduled-style run across all Phases (WP-0013).
Intended for a cron job holding an Operator+ credential. Each Phase
is planned independently; unsupported policies are skipped.
"""
if not registry.has_right(licensor.rights, "operator"):
raise HTTPException(status_code=403, detail="operator rights required")
written = remission.apply_remission_for_all_phases(conn, signing_key)
return {
"status": "ok",
"entries_appended": len(written),
"entry_ids": [e["id"] for e in written],
}
@app.get("/phases/{phase_id}/attestation")

View file

@ -25,7 +25,7 @@ from psycopg import Connection
from psycopg_pool import ConnectionPool
from starlette.middleware.sessions import SessionMiddleware
from .. import control_plane, ledger, metrics, registry
from .. import breach_record, control_plane, ledger, metrics, registry
from . import keys, reference_docs
_STATIC_DIR = os.path.join(os.path.dirname(__file__), "static")
@ -248,19 +248,56 @@ def phase_detail(
request: Request,
licensor: registry.Licensor = Depends(require_login),
conn: Connection = Depends(get_connection),
signing_key=Depends(get_signing_key),
):
from .. import remission
manifest = registry.get_phase_manifest(conn, phase_id)
if manifest is None:
raise HTTPException(status_code=404, detail="phase not found")
entries = ledger.get_ledger(conn, phase_id)
computed_metrics = metrics.compute_metrics(manifest, entries, metrics.utcnow())
activated_at = remission.get_phase_registered_at(conn, phase_id)
computed_metrics = metrics.compute_metrics(
manifest, entries, metrics.utcnow(), activated_at=activated_at
)
breaches = breach_record.get_breach_records(conn, phase_id)
phase_attestation = control_plane.get_or_publish_attestation(
conn, phase_id, signing_key
)
return templates.TemplateResponse(
request,
"phase_detail.html",
_template_context(request, licensor, manifest=manifest, ledger=entries, metrics=computed_metrics),
_template_context(
request,
licensor,
manifest=manifest,
ledger=entries,
metrics=computed_metrics,
breaches=breaches,
attestation=phase_attestation,
),
)
@app.post("/phases/{phase_id}/remission")
def phase_remission_apply(
phase_id: str,
request: Request,
licensor: registry.Licensor = Depends(require_login),
conn: Connection = Depends(get_connection),
signing_key=Depends(get_signing_key),
):
try:
entry = control_plane.apply_policy_remission(conn, licensor, phase_id, signing_key)
except (control_plane.ControlPlaneError, registry.RegistrationError) as exc:
return _redirect(f"/phases/{phase_id}", request, str(exc), "danger")
if entry is None:
flash = "Remission already up to date — no new entry written."
else:
flash = f"Remission credit {entry['id']} appended ({entry['amount']})."
return _redirect(f"/phases/{phase_id}", request, flash, "success")
@app.post("/phases/{phase_id}/ledger")
def phase_ledger_submit(
phase_id: str,
@ -298,6 +335,133 @@ def phase_ledger_submit(
return _redirect(f"/phases/{phase_id}", request, flash, "success")
@app.post("/phases/{phase_id}/breach")
def phase_breach_publish(
phase_id: str,
request: Request,
licensor: registry.Licensor = Depends(require_login),
conn: Connection = Depends(get_connection),
signing_key=Depends(get_signing_key),
record_id: str = Form(...),
case_id: str = Form(...),
event_type: str = Form(...),
category: str = Form(...),
event_at: str = Form(...),
evidence_reference: str = Form(""),
anonymized: str = Form("true"),
named_entitlement_holder: str = Form(""),
named_disclosure_authorized: str = Form(""),
):
"""Operator+: publish a breach/compliance event (WP-0014-T02)."""
is_anonymized = anonymized.lower() in ("true", "1", "on", "yes")
event_input: dict[str, Any] = {
"id": record_id,
"case_id": case_id,
"event_type": event_type,
"category": category,
"event_at": event_at,
"anonymized": is_anonymized,
}
if evidence_reference:
event_input["evidence_reference"] = evidence_reference
if not is_anonymized:
event_input["named_entitlement_holder"] = named_entitlement_holder
event_input["named_disclosure_authorized_under_cua"] = (
named_disclosure_authorized.lower() in ("true", "1", "on", "yes")
)
try:
stored = control_plane.publish_breach_event(
conn, licensor, phase_id, event_input, signing_key
)
except (control_plane.ControlPlaneError, registry.RegistrationError) as exc:
return _redirect(f"/phases/{phase_id}", request, str(exc), "danger")
return _redirect(
f"/phases/{phase_id}",
request,
f"Breach record {stored['id']} published ({stored['event_type']}).",
"success",
)
# --- Extension Registry (WP-0014-T01) ----------------------------------------
@app.get("/extensions")
def extensions_list(
request: Request,
licensor: registry.Licensor = Depends(require_login),
conn: Connection = Depends(get_connection),
):
extensions = registry.list_extensions(conn)
return templates.TemplateResponse(
request,
"extensions.html",
_template_context(request, licensor, extensions=extensions),
)
@app.post("/extensions")
def extensions_register(
request: Request,
licensor: registry.Licensor = Depends(require_login),
conn: Connection = Depends(get_connection),
extension_id: str = Form(...),
version: str = Form(...),
value_description: str = Form(...),
pricing_method: str = Form(...),
allocation_rule: str = Form(...),
default_rate: str = Form(""),
recognition_event: str = Form(...),
reversal_rule: str = Form(...),
evidence_requirement: str = Form(...),
):
extension: dict[str, Any] = {
"id": extension_id,
"version": version,
"value": {"description": value_description},
"pricing": {"method": pricing_method},
"allocation": {"rule": allocation_rule},
"recognition": {"event": recognition_event},
"reversal": {"rule": reversal_rule},
"evidence": {"requirement": evidence_requirement},
# Author-submitted status is always `registered`; the hosting column
# is authoritative and starts registered regardless (registry.py).
"status": "registered",
}
if default_rate.strip():
extension["allocation"]["default_rate"] = float(default_rate)
try:
control_plane.register_extension(conn, licensor, extension)
except (control_plane.ControlPlaneError, registry.RegistrationError) as exc:
return _redirect("/extensions", request, str(exc), "danger")
return _redirect(
"/extensions",
request,
f"Extension {extension_id}@{version} registered.",
"success",
)
@app.post("/extensions/promote")
def extensions_promote(
request: Request,
licensor: registry.Licensor = Depends(require_login),
conn: Connection = Depends(get_connection),
extension_id: str = Form(...),
version: str = Form(...),
):
try:
control_plane.promote_extension_canonical(conn, licensor, extension_id, version)
except (control_plane.ControlPlaneError, registry.RegistrationError) as exc:
return _redirect("/extensions", request, str(exc), "danger")
return _redirect(
"/extensions",
request,
f"Extension {extension_id}@{version} promoted to canonical.",
"success",
)
# --- Proposals (Operator+) --------------------------------------------------

View file

@ -0,0 +1,87 @@
{% extends "base.html" %}
{% block title %}Extensions — Target Revenue Control Plane{% endblock %}
{% block content %}
<wn-page-header>
<span slot="title">Monetization Extension Registry</span>
</wn-page-header>
<p style="color:#888;font-size:0.9rem;">
Rights: <strong>Operator+</strong> may register; <strong>Admin</strong> may
promote to <code>canonical</code> (governance action, never automated).
See <code>specs/TargetRevenueControlPlaneConcept.md</code> §2.
</p>
{% if extensions %}
<table class="wn-plain">
<thead>
<tr>
<th>id</th>
<th>version</th>
<th>status</th>
<th>licensor</th>
<th></th>
</tr>
</thead>
<tbody>
{% for ext in extensions %}
<tr>
<td><code>{{ ext.extension_id }}</code></td>
<td>{{ ext.version }}</td>
<td><wn-tag>{{ ext.status }}</wn-tag></td>
<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">
<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>
</form>
{% endif %}
</td>
</tr>
<tr>
<td colspan="5" style="color:#666;font-size:0.85rem;padding-top:0;">
{{ ext.contract.value.description }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<wn-empty-state>No extensions registered yet.</wn-empty-state>
{% endif %}
{% if session_rights in ("operator", "admin") %}
<h3>Register a new extension</h3>
<form class="wn-form" method="post" action="/extensions">
<wn-field-row label="Extension id (trsl:extension:...)">
<wn-input name="extension_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Version (e.g. 1.0)">
<wn-input name="version" required></wn-input>
</wn-field-row>
<wn-field-row label="Value description">
<wn-input name="value_description" required></wn-input>
</wn-field-row>
<wn-field-row label="Pricing method">
<wn-input name="pricing_method" required></wn-input>
</wn-field-row>
<wn-field-row label="Allocation rule">
<wn-input name="allocation_rule" required></wn-input>
</wn-field-row>
<wn-field-row label="Default rate (01, optional)">
<wn-input type="number" step="0.01" min="0" max="1" name="default_rate"></wn-input>
</wn-field-row>
<wn-field-row label="Recognition event">
<wn-input name="recognition_event" value="payment-settled" required></wn-input>
</wn-field-row>
<wn-field-row label="Reversal rule">
<wn-input name="reversal_rule" required></wn-input>
</wn-field-row>
<wn-field-row label="Evidence requirement">
<wn-input name="evidence_requirement" required></wn-input>
</wn-field-row>
<wn-button type="submit" variant="primary">Register extension</wn-button>
</form>
{% endif %}
{% endblock %}

View file

@ -31,8 +31,47 @@
&middot; <a href="/reference/policies/{{ policy_slug(manifest.phase.degeneration_policy) }}">view spec</a>
</td>
</tr>
<tr><td>Longstop</td><td>{{ metrics.facts.longstop_at or "—" }}</td></tr>
<tr><td>Activated (t0)</td><td>{{ metrics.facts.activated_at or "—" }}</td></tr>
</table>
<h3>Remission forecast <wn-tag>forecast</wn-tag></h3>
<table class="wn-plain">
<tr>
<td>If applied now</td>
<td>
{% if metrics.forecasts.remission_if_applied_now is not none %}
{{ metrics.forecasts.remission_if_applied_now }} {{ metrics.facts.initial_target_currency }}
{% else %}
{% endif %}
</td>
</tr>
<tr>
<td>Next scheduled (monthly UTC / longstop)</td>
<td>{{ metrics.forecasts.next_scheduled_remission_at or "—" }}</td>
</tr>
<tr>
<td>Amount at next schedule</td>
<td>
{% if metrics.forecasts.next_scheduled_remission_amount is not none %}
{{ metrics.forecasts.next_scheduled_remission_amount }} {{ metrics.facts.initial_target_currency }}
{% else %}
{% endif %}
</td>
</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;">
<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>
(idempotent — no double-remit if already current).
</p>
</form>
{% endif %}
<h3>Ledger ({{ ledger | length }} entries)</h3>
{% if ledger %}
<table class="wn-plain">
@ -78,4 +117,98 @@
</wn-button>
</form>
{% endif %}
<h3>Conversion Attestation</h3>
{% if attestation %}
<table class="wn-plain">
<tr><td>Phase</td><td>{{ attestation.phase }}</td></tr>
<tr><td>Milestone release</td><td>{{ attestation.milestone_release }}</td></tr>
<tr><td>Converted at</td><td>{{ attestation.conversion_timestamp }}</td></tr>
<tr><td>Future License</td><td><strong>{{ attestation.future_license }}</strong></td></tr>
<tr><td>Final development credit</td><td>{{ attestation.final_development_credit }}</td></tr>
<tr><td>Final remission credit</td><td>{{ attestation.final_remission_credit }}</td></tr>
<tr><td>Outstanding at conversion</td><td>{{ attestation.final_outstanding_target }}</td></tr>
<tr><td>Ledger checkpoint</td><td><code>{{ attestation.ledger_checkpoint }}</code></td></tr>
<tr><td>Signature</td><td><code style="font-size:0.75rem;word-break:break-all;">{{ attestation.signature }}</code></td></tr>
</table>
{% elif metrics.facts.is_converted %}
<p>Phase is converted; attestation will publish on next observation.</p>
{% else %}
<wn-empty-state>Not converted — no attestation yet.</wn-empty-state>
{% endif %}
<h3>Breach / Compliance Records ({{ breaches | length }})</h3>
{% if breaches %}
<table class="wn-plain">
<thead>
<tr>
<th>id</th>
<th>case</th>
<th>type</th>
<th>category</th>
<th>event_at</th>
<th>named?</th>
</tr>
</thead>
<tbody>
{% for b in breaches %}
<tr>
<td>{{ b.id }}</td>
<td>{{ b.case_id }}</td>
<td>{{ b.event_type }}</td>
<td>{{ b.category }}</td>
<td>{{ b.event_at }}</td>
<td>
{% if b.anonymized %}
anonymized
{% else %}
{{ b.named_entitlement_holder }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<wn-empty-state>No breach/compliance records published for this Phase.</wn-empty-state>
{% endif %}
{% if session_rights in ("operator", "admin") %}
<h3>Publish a breach/compliance event</h3>
<p style="color:#888;font-size:0.85rem;">
Anonymized by default (Phase + category only). Named disclosure requires
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">
<wn-field-row label="Record id (unique)">
<wn-input name="record_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Case id">
<wn-input name="case_id" required></wn-input>
</wn-field-row>
<wn-field-row label="Event type (alleged|cured|determined|terminated)">
<wn-input name="event_type" value="alleged" required></wn-input>
</wn-field-row>
<wn-field-row label="Category">
<wn-input name="category" required></wn-input>
</wn-field-row>
<wn-field-row label="Event at (ISO 8601)">
<wn-input name="event_at" required></wn-input>
</wn-field-row>
<wn-field-row label="Evidence reference (optional)">
<wn-input name="evidence_reference"></wn-input>
</wn-field-row>
<wn-field-row label="Anonymized (true/false)">
<wn-input name="anonymized" value="true" required></wn-input>
</wn-field-row>
<wn-field-row label="Named entitlement holder (only if anonymized=false)">
<wn-input name="named_entitlement_holder"></wn-input>
</wn-field-row>
<wn-field-row label="Named disclosure authorized under CUA (true if named)">
<wn-input name="named_disclosure_authorized" value="false"></wn-input>
</wn-field-row>
<wn-button type="submit" variant="secondary">Publish breach record</wn-button>
</form>
{% endif %}
{% endblock %}