-- WP-0009-T03: Control Plane backend — audit log and proposed-entry review. -- Depends on migrations/0001_registries.sql, 0002_ledger.sql, and -- 0005_licensor_credentials.sql (credential_label/rights/licensor_identities). -- -- These tables belong to the Control Plane, a client layer on top of the -- already-finished hosted Trust Service (registry.py/ledger.py) — not new -- Trust Service surface itself. Per -- specs/TargetRevenueControlPlaneConcept.md §5: the audit log here is -- deliberately separate from the Trust Service's own signed records -- (phase_manifests, ledger_entries, attestations, breach_records), which -- only ever attest "the tenant did this," never the individual human. BEGIN; -- Append-only audit trail: which credential (hence which human) took -- which Control Plane action, and — where applicable — which Trust -- Service record resulted. `trf_app` has no UPDATE/DELETE grant: an audit -- log that could be edited after the fact isn't an audit log. CREATE TABLE IF NOT EXISTS control_plane_audit_log ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, actor_token text NOT NULL REFERENCES licensors(token), actor_licensor_id text NOT NULL, actor_credential_label text, action text NOT NULL, phase_id text, trust_service_record_id text, detail jsonb, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS control_plane_audit_log_phase_idx ON control_plane_audit_log (phase_id); GRANT SELECT, INSERT ON control_plane_audit_log TO trf_app; -- Contributor-tier proposed entries, per -- specs/TargetRevenueControlPlaneConcept.md §2's rights table: a -- Contributor may submit a proposed Development Credit entry but not -- append it directly; an Operator/Admin reviews and either approves -- (which actually appends it to the real Ledger under the reviewer's own -- credential) or rejects it. `trf_app` again has no UPDATE/DELETE grant — -- review decisions are recorded via a SECURITY DEFINER function, the same -- governance-action pattern as `set_extension_status`/`revoke_credential`. CREATE TABLE IF NOT EXISTS control_plane_proposed_entries ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, phase_id text NOT NULL, entry jsonb NOT NULL, proposed_by_token text NOT NULL REFERENCES licensors(token), status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')), reviewed_by_token text REFERENCES licensors(token), reviewed_at timestamptz, review_note text, appended_entry_id text, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS control_plane_proposed_entries_phase_idx ON control_plane_proposed_entries (phase_id, status); GRANT SELECT, INSERT ON control_plane_proposed_entries TO trf_app; CREATE OR REPLACE FUNCTION review_proposed_entry( p_id bigint, p_status text, p_reviewed_by_token text, p_review_note text, p_appended_entry_id text ) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN IF p_status NOT IN ('approved', 'rejected') THEN RAISE EXCEPTION 'review_proposed_entry only permits approved/rejected, got %', p_status; END IF; UPDATE control_plane_proposed_entries SET status = p_status, reviewed_by_token = p_reviewed_by_token, reviewed_at = now(), review_note = p_review_note, appended_entry_id = p_appended_entry_id WHERE id = p_id AND status = 'pending'; IF NOT FOUND THEN RAISE EXCEPTION 'no pending proposal with id %', p_id; END IF; END; $$; GRANT EXECUTE ON FUNCTION review_proposed_entry(bigint, text, text, text, text) TO trf_app; COMMIT;