-- WP-0015-T05 (specs/PhaseProvenanceSpecAddendum.md ยง4): the Forgejo -- hub -> service URI registry. -- Depends on migrations/0001_registries.sql (phase_manifests). -- -- Per WP-0012-T02's accepted decision: target-revenue is the generic -- framework, not a specific deployment's list of repos it monetizes, so -- this mapping is hosted Trust Service data (parallel to -- licensor_identities, migrations/0005_licensor_credentials.sql), never -- a file in this git repo. A Phase Manifest stays fully self-describing -- and offline-verifiable regardless -- it already carries -- repo_hub_uri/repo_name directly at registration time -- (specs/PhaseLifecycleUseCases.md use case 9). This table exists purely -- as an admin/repair convenience: if a hub's URI ever changes, there is -- one row to correct rather than every affected Phase Manifest. BEGIN; CREATE TABLE IF NOT EXISTS forgejo_hubs ( hub_slug text PRIMARY KEY, service_uri text NOT NULL, first_seen_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); GRANT SELECT, INSERT ON forgejo_hubs TO trf_app; -- No UPDATE/DELETE grant for trf_app -- correcting a hub's URI after the -- fact is a governance action (correct_forgejo_hub_uri() below), same -- pattern as set_extension_status()/revoke_credential(), not a route -- the application's ordinary write path can take. -- Auto-populate on first sight of a repo_hub during Phase registration -- -- registry.register_phase_manifest() never needs to know this table -- exists; the trigger reads repo_hub/repo_hub_uri straight out of the -- manifest JSONB it was already given, mirroring -- ensure_licensor_identity()'s auto-create-on-first-INSERT pattern. -- ON CONFLICT DO NOTHING: a hub already seen is left alone here -- URI -- corrections go through correct_forgejo_hub_uri() below, not a silent -- overwrite on the next unrelated Phase registration. CREATE OR REPLACE FUNCTION ensure_forgejo_hub() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_hub_slug text := NEW.manifest #>> '{phase,milestone_release,repo_hub}'; v_hub_uri text := NEW.manifest #>> '{phase,milestone_release,repo_hub_uri}'; BEGIN IF v_hub_slug IS NOT NULL AND v_hub_uri IS NOT NULL THEN INSERT INTO forgejo_hubs (hub_slug, service_uri) VALUES (v_hub_slug, v_hub_uri) ON CONFLICT (hub_slug) DO NOTHING; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS phase_manifests_ensure_forgejo_hub ON phase_manifests; CREATE TRIGGER phase_manifests_ensure_forgejo_hub BEFORE INSERT ON phase_manifests FOR EACH ROW EXECUTE FUNCTION ensure_forgejo_hub(); -- Correcting a hub's URI after the fact (a domain move, etc.) is a -- recorded governance action, not an ordinary UPDATE -- trf_app has no -- UPDATE grant on forgejo_hubs at all. CREATE OR REPLACE FUNCTION correct_forgejo_hub_uri( p_hub_slug text, p_new_uri text ) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN UPDATE forgejo_hubs SET service_uri = p_new_uri, updated_at = now() WHERE hub_slug = p_hub_slug; IF NOT FOUND THEN RAISE EXCEPTION 'unknown forgejo hub: %', p_hub_slug; END IF; END; $$; GRANT EXECUTE ON FUNCTION correct_forgejo_hub_uri(text, text) TO trf_app; COMMIT;