-- WP-0006-T03: Phase Registry and Extension Registry hosting.
-- Enforces ADR-0002's storage decision at the database level, not merely by
-- application convention: settled Phase Manifests are append-only; an
-- Extension's canonicalization is a governance action that a plain
-- application role cannot perform via UPDATE.
--
-- This migration is idempotent (safe to re-run) and assumes it runs against
-- a dedicated database (e.g. `target_revenue`), not a shared instance's
-- default database. It does not assume, and must not be pointed at, the
-- state hub's own `custodian` database.

BEGIN;

CREATE TABLE IF NOT EXISTS licensors (
    token         text PRIMARY KEY,
    licensor_id   text NOT NULL UNIQUE,
    created_at    timestamptz NOT NULL DEFAULT now()
);

-- One row per Phase, keyed by the schema's own globally unique phase.id.
-- Per TSD §3.1: phase.id and phase.initial_target.amount are immutable
-- after first publication except through an explicit, versioned correction
-- record — which Stage 0 has no type for yet (validation.py's
-- check_manifest_immutability flags any change as an error). Consequently
-- this table has no supported update path at all: a Phase Manifest is
-- inserted once and never changed by this component.
CREATE TABLE IF NOT EXISTS phase_manifests (
    phase_id       text PRIMARY KEY,
    licensor_id    text NOT NULL REFERENCES licensors(licensor_id),
    manifest       jsonb NOT NULL,
    registered_at  timestamptz NOT NULL DEFAULT now()
);

-- Extension registrations. `status` starts at 'registered' (conformance
-- passed) and may only become 'canonical' or 'deprecated' through the
-- canonicalize_extension()/deprecate_extension() functions below — never a
-- direct UPDATE by the application role (TSD §4.1: "never automated").
CREATE TABLE IF NOT EXISTS extensions (
    extension_id      text NOT NULL,
    version           text NOT NULL,
    licensor_id       text NOT NULL REFERENCES licensors(licensor_id),
    contract          jsonb NOT NULL,
    status            text NOT NULL DEFAULT 'registered'
                        CHECK (status IN ('registered', 'canonical', 'deprecated')),
    registered_at     timestamptz NOT NULL DEFAULT now(),
    status_changed_by text,
    status_changed_at timestamptz,
    PRIMARY KEY (extension_id, version)
);

-- Governance-only status transition. SECURITY DEFINER so it can run with
-- the owning role's privilege even though the calling application role has
-- no UPDATE grant on extensions.status itself (see grants below).
CREATE OR REPLACE FUNCTION set_extension_status(
    p_extension_id text,
    p_version text,
    p_new_status text,
    p_changed_by text
) RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
    IF p_new_status NOT IN ('canonical', 'deprecated') THEN
        RAISE EXCEPTION 'set_extension_status only permits canonical/deprecated, got %', p_new_status;
    END IF;
    UPDATE extensions
       SET status = p_new_status,
           status_changed_by = p_changed_by,
           status_changed_at = now()
     WHERE extension_id = p_extension_id AND version = p_version;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'no extension %/%', p_extension_id, p_version;
    END IF;
END;
$$;

-- Application role: adjust the name to match the deployment's actual role.
-- Created here (idempotent) rather than assumed to pre-exist, so this
-- migration is self-contained for a fresh `target_revenue` database.
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'trf_app') THEN
        CREATE ROLE trf_app LOGIN PASSWORD 'changeme-in-deployment';
    END IF;
END
$$;

GRANT SELECT, INSERT ON licensors TO trf_app;
GRANT SELECT, INSERT ON phase_manifests TO trf_app;
-- Deliberately no UPDATE, no DELETE on phase_manifests for trf_app: this is
-- the database-level enforcement ADR-0002 requires for append-only Phase
-- Manifests, not merely an API design intention.
GRANT SELECT, INSERT ON extensions TO trf_app;
-- Deliberately no UPDATE, no DELETE on extensions for trf_app either — the
-- only sanctioned status transition is via set_extension_status(), a
-- SECURITY DEFINER function, so canonicalization is always a recorded,
-- attributable governance action, never a route the application's own
-- ordinary write path can take.
GRANT EXECUTE ON FUNCTION set_extension_status(text, text, text, text) TO trf_app;

COMMIT;
