88 lines
3.1 KiB
PL/PgSQL
88 lines
3.1 KiB
PL/PgSQL
-- ============================================================
|
|
-- Migration 004: Constraints y triggers de privacidad
|
|
--
|
|
-- 1. Trigger: rechazar respondent_id no nulo en encuestas anónimas
|
|
-- → Regla no negociable #2 de CLAUDE.md (barrera técnica)
|
|
--
|
|
-- 2. Trigger: inmutabilidad de consent_records
|
|
-- → ADR-003: sin UPDATE/DELETE, evidencia permanente del consentimiento
|
|
--
|
|
-- 3. Trigger: inmutabilidad de consent_versions
|
|
-- → Textos de consentimiento históricos son inmutables
|
|
-- ============================================================
|
|
|
|
-- ============================================================
|
|
-- 1. Trigger: respondent_id prohibido en encuestas anónimas
|
|
-- ============================================================
|
|
CREATE OR REPLACE FUNCTION public.enforce_anonymous_no_respondent_id()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
v_is_anonymous boolean;
|
|
BEGIN
|
|
-- Leer la propiedad de la encuesta referida
|
|
SELECT is_anonymous INTO v_is_anonymous
|
|
FROM public.surveys
|
|
WHERE id = NEW.survey_id;
|
|
|
|
IF v_is_anonymous AND NEW.respondent_id IS NOT NULL THEN
|
|
RAISE EXCEPTION
|
|
'Privacy violation: respondent_id must be NULL in anonymous surveys (survey_id: %). '
|
|
'See CLAUDE.md rule #2.',
|
|
NEW.survey_id
|
|
USING ERRCODE = 'check_violation';
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER trg_responses_anonymous_no_respondent_id
|
|
BEFORE INSERT OR UPDATE ON public.responses
|
|
FOR EACH ROW EXECUTE FUNCTION public.enforce_anonymous_no_respondent_id();
|
|
|
|
-- ============================================================
|
|
-- 2. Trigger: inmutabilidad de consent_records (ADR-003)
|
|
-- El consentimiento es evidencia, no UI. Nunca se modifica.
|
|
-- ============================================================
|
|
CREATE OR REPLACE FUNCTION public.deny_consent_records_mutation()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
RAISE EXCEPTION
|
|
'consent_records is immutable: % is not permitted. '
|
|
'Each response consent is permanent evidence (ADR-003).',
|
|
TG_OP
|
|
USING ERRCODE = 'insufficient_privilege';
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER trg_consent_records_immutable
|
|
BEFORE UPDATE OR DELETE ON public.consent_records
|
|
FOR EACH ROW EXECUTE FUNCTION public.deny_consent_records_mutation();
|
|
|
|
-- ============================================================
|
|
-- 3. Trigger: inmutabilidad de consent_versions
|
|
-- Los textos históricos no se alteran. Cambiar el wording del
|
|
-- consentimiento crea una nueva versión, nunca edita la existente.
|
|
-- ============================================================
|
|
CREATE OR REPLACE FUNCTION public.deny_consent_versions_mutation()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
RAISE EXCEPTION
|
|
'consent_versions is immutable: % is not permitted. '
|
|
'Create a new version instead of modifying an existing one.',
|
|
TG_OP
|
|
USING ERRCODE = 'insufficient_privilege';
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER trg_consent_versions_immutable
|
|
BEFORE UPDATE OR DELETE ON public.consent_versions
|
|
FOR EACH ROW EXECUTE FUNCTION public.deny_consent_versions_mutation();
|