93 lines
3.8 KiB
MySQL
93 lines
3.8 KiB
MySQL
|
|
-- 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;
|