"""Target Revenue Control Plane backend (WP-0009-T03). A client layer over the already-finished hosted Trust Service (`registry.py`, `ledger.py`) — not a replacement for it, per `specs/TargetRevenueControlPlaneConcept.md`. Every write-capable Control Plane action funnels through this module so it can (a) check the acting credential's rights tier before doing anything, and (b) record who did what in `control_plane_audit_log`, independent of the Trust Service's own signed records, which only ever attest "the tenant did this," never the individual human (concept §2; `migrations/0005_licensor_credentials.sql`). Rights enforcement lives here, not in `registry.py`/`ledger.py` — those modules only ever needed to answer "which tenant is this," and per WP-0009-T01's accepted decision, per-human rights tiers (Viewer/Contributor/Operator/Admin) are a Control Plane concern layered on top, not a Trust Service one. """ from __future__ import annotations from typing import TYPE_CHECKING, Any from . import ledger, registry from .registry import Licensor if TYPE_CHECKING: from psycopg import Connection class ControlPlaneError(ValueError): """Raised when a Control Plane action is rejected (insufficient rights, an unknown proposal id, etc.).""" def _require_right(licensor: Licensor, minimum: str) -> None: if not registry.has_right(licensor.rights, minimum): raise ControlPlaneError( f"insufficient rights: {minimum!r} required, this credential has " f"{licensor.rights!r}" ) def record_audit_event( conn: "Connection", actor: Licensor, action: str, phase_id: str | None = None, trust_service_record_id: str | None = None, detail: dict[str, Any] | None = None, ) -> None: """Append one row to the Control Plane's own audit log. Called by every function below after (never instead of, and never before confirming) the underlying Trust Service action actually succeeded.""" from psycopg.types.json import Jsonb conn.execute( """ INSERT INTO control_plane_audit_log (actor_token, actor_licensor_id, actor_credential_label, action, phase_id, trust_service_record_id, detail) VALUES (%s, %s, %s, %s, %s, %s, %s) """, ( actor.token, actor.licensor_id, actor.credential_label, action, phase_id, trust_service_record_id, Jsonb(detail) if detail is not None else None, ), ) def get_audit_log(conn: "Connection", phase_id: str | None = None) -> list[dict[str, Any]]: """Read the audit log, optionally scoped to one Phase, in append order.""" if phase_id: rows = conn.execute( """ SELECT id, actor_licensor_id, actor_credential_label, action, phase_id, trust_service_record_id, detail, created_at FROM control_plane_audit_log WHERE phase_id = %s ORDER BY id ASC """, (phase_id,), ).fetchall() else: rows = conn.execute( """ SELECT id, actor_licensor_id, actor_credential_label, action, phase_id, trust_service_record_id, detail, created_at FROM control_plane_audit_log ORDER BY id ASC """ ).fetchall() columns = ( "id", "actor_licensor_id", "actor_credential_label", "action", "phase_id", "trust_service_record_id", "detail", "created_at", ) return [dict(zip(columns, row)) for row in rows] # --- Human-user (credential) management: Admin tier ------------------------- def issue_user_credential( conn: "Connection", admin: Licensor, licensor_id: str, credential_label: str, rights: str ) -> Licensor: """Admin-only: issue a new credential for a human user acting as the given Licensor tenant. Wraps `registry.issue_sub_credential` with the rights check and audit log this module exists to add.""" _require_right(admin, "admin") new_credential = registry.issue_sub_credential( conn, licensor_id=licensor_id, credential_label=credential_label, rights=rights, issued_by=admin.credential_label or admin.token, ) record_audit_event( conn, admin, action="issue_user_credential", detail={"credential_label": credential_label, "rights": rights}, ) return new_credential def revoke_user_credential(conn: "Connection", admin: Licensor, token: str) -> None: """Admin-only: revoke a credential. Logs only the revoked token's suffix, not the full secret, in the audit detail.""" _require_right(admin, "admin") registry.revoke_sub_credential(conn, token) record_audit_event( conn, admin, action="revoke_user_credential", detail={"revoked_token_suffix": token[-6:]}, ) # --- Phase / Ledger actions -------------------------------------------------- def register_phase(conn: "Connection", licensor: Licensor, manifest: dict[str, Any]) -> None: """Operator+ only, per concept §2's rights table.""" _require_right(licensor, "operator") registry.register_phase_manifest(conn, licensor, manifest) record_audit_event( conn, licensor, action="register_phase", phase_id=manifest["phase"]["id"], detail={"milestone_release": manifest["phase"]["milestone_release"]["name"]}, ) def append_development_credit( conn: "Connection", licensor: Licensor, phase_id: str, entry_input: dict[str, Any], signing_key: Any, ) -> dict[str, Any]: """Operator+ only — appends directly. A Contributor must use `propose_ledger_entry` instead (concept §2: "not directly appended").""" _require_right(licensor, "operator") stored = ledger.append_entry(conn, licensor, phase_id, entry_input, signing_key) record_audit_event( conn, licensor, action="append_ledger_entry", phase_id=phase_id, trust_service_record_id=stored["id"], detail={"type": stored["type"], "amount": stored["amount"]}, ) return stored def propose_ledger_entry( conn: "Connection", licensor: Licensor, phase_id: str, entry_input: dict[str, Any] ) -> int: """Contributor+ — submit a proposed entry for an Operator/Admin to review. Not appended to the real Ledger until approved.""" from psycopg.types.json import Jsonb _require_right(licensor, "contributor") row = conn.execute( """ INSERT INTO control_plane_proposed_entries (phase_id, entry, proposed_by_token) VALUES (%s, %s, %s) RETURNING id """, (phase_id, Jsonb(entry_input), licensor.token), ).fetchone() proposal_id = row[0] record_audit_event( conn, licensor, action="propose_ledger_entry", phase_id=phase_id, detail={"proposal_id": proposal_id, "type": entry_input.get("type")}, ) return proposal_id def list_proposed_entries( conn: "Connection", phase_id: str | None = None, status: str = "pending" ) -> list[dict[str, Any]]: if phase_id: rows = conn.execute( """ SELECT id, phase_id, entry, proposed_by_token, status, created_at FROM control_plane_proposed_entries WHERE phase_id = %s AND status = %s ORDER BY id ASC """, (phase_id, status), ).fetchall() else: rows = conn.execute( """ SELECT id, phase_id, entry, proposed_by_token, status, created_at FROM control_plane_proposed_entries WHERE status = %s ORDER BY id ASC """, (status,), ).fetchall() columns = ("id", "phase_id", "entry", "proposed_by_token", "status", "created_at") return [dict(zip(columns, row)) for row in rows] def approve_proposed_entry( conn: "Connection", reviewer: Licensor, proposal_id: int, signing_key: Any, review_note: str | None = None, ) -> dict[str, Any]: """Operator+ approves: actually appends the entry, under the *reviewer's* own credential/attribution — the reviewer is the one authorizing it into the real Ledger. The original proposer remains on record in the proposal row and the audit log, not lost or overwritten. """ _require_right(reviewer, "operator") row = conn.execute( "SELECT phase_id, entry FROM control_plane_proposed_entries " "WHERE id = %s AND status = 'pending'", (proposal_id,), ).fetchone() if row is None: raise ControlPlaneError(f"no pending proposal with id {proposal_id!r}") phase_id, entry_input = row stored = ledger.append_entry(conn, reviewer, phase_id, entry_input, signing_key) conn.execute( "SELECT review_proposed_entry(%s, %s, %s, %s, %s)", (proposal_id, "approved", reviewer.token, review_note, stored["id"]), ) record_audit_event( conn, reviewer, action="approve_proposed_entry", phase_id=phase_id, trust_service_record_id=stored["id"], detail={"proposal_id": proposal_id}, ) return stored def reject_proposed_entry( conn: "Connection", reviewer: Licensor, proposal_id: int, review_note: str | None = None ) -> None: """Operator+ rejects: the proposal is marked rejected, nothing is appended to the real Ledger.""" _require_right(reviewer, "operator") row = conn.execute( "SELECT phase_id FROM control_plane_proposed_entries " "WHERE id = %s AND status = 'pending'", (proposal_id,), ).fetchone() if row is None: raise ControlPlaneError(f"no pending proposal with id {proposal_id!r}") phase_id = row[0] conn.execute( "SELECT review_proposed_entry(%s, %s, %s, %s, %s)", (proposal_id, "rejected", reviewer.token, review_note, None), ) record_audit_event( conn, reviewer, action="reject_proposed_entry", phase_id=phase_id, detail={"proposal_id": proposal_id}, )