Files
motor-de-encuestas/sprints/sprint-5/ETAPA_AUDIT_LOG_PROMPT.md

5.4 KiB

ETAPA AUDIT LOG — Registro de auditoría append-only (Sprint 5, SEC-002)

1. Objetivo

Implementar un registro de auditoría append-only en Postgres que loggee eventos de configuración crítica: cambios en user_roles (altas/bajas/modificaciones de acceso) y cambios de status en surveys (activación/desactivación de encuestas). Opcionalmente, si la implementación es simple y sin fricción, agregar también un trigger DDL para loggear migraciones aplicadas.

2. Contexto

  • Migración de referencia para patrones: 20260627000001_auth_user_roles.sql (patrón SECURITY DEFINER + GRANT puntual a reutilizar).
  • Helpers RLS existentes a reusar: public.current_app_role() y public.current_org_id() — no reinventar.
  • Gap conocido y aceptado (SEC-003): esto NO protege contra TRUNCATE/DROP ejecutado por superusuario directo en el VPS. Documentar en comentario de la migración, no resolver.
  • Acceso a datos sensibles: NO loggear en esta etapa — hoy no existe código que lea columnas is_sensitive=true. Deferido a cuando ese código exista.
  • Naming de migración: después de 20260627000001 — usar timestamp del día de ejecución, formato YYYYMMDDHHMMSS.

3. DECISIONES YA TOMADAS — no consultar

  • Tabla nueva: public.audit_log, append-only real.
  • Ningún rol (anon, authenticated, service_role vía API) tiene INSERT/UPDATE/DELETE directo en audit_log. Solo escriben funciones SECURITY DEFINER de los triggers.
  • Segunda barrera: trigger BEFORE UPDATE OR DELETE ON audit_log que aborta con excepción — defensa en profundidad.
  • Lectura de audit_log: RLS, solo platform_admin via current_app_role().
  • Patrón de función trigger: SECURITY DEFINER, propiedad de postgres, GRANT EXECUTE solo al rol que lo necesita (no a PUBLIC).
  • Columnas de audit_log:
    id           bigserial PRIMARY KEY,
    occurred_at  timestamptz NOT NULL DEFAULT now(),
    actor_role   text,
    actor_user_id uuid,
    action       text NOT NULL,
    target_table text NOT NULL,
    target_id    text,
    detail       jsonb
    
  • Triggers obligatorios (Prioridad 1):
    • AFTER INSERT/UPDATE/DELETE en user_roles
    • AFTER UPDATE en surveys cuando cambia status
  • Trigger DDL (Prioridad 2 — opcional): solo implementar si es simple y sin fricción. Si requiere extensión nueva o configuración de infraestructura, NO incluir y reportarlo.

4. Tareas

4.1 Diagnóstico previo — DDL trigger

Antes de implementar, verificar si el DDL trigger (ddl_command_end) está disponible sin extensiones adicionales en esta versión de Postgres self-hosted:

docker exec -i supabase-db psql -U postgres -d postgres -c "
SELECT event_object_schema, trigger_name
FROM information_schema.triggers
WHERE trigger_name LIKE '%ddl%' LIMIT 5;"

Reportar si es viable sin fricción. Si requiere configuración extra, documentarlo y omitirlo de esta etapa.

4.2 Migración principal

Archivo: supabase/migrations/YYYYMMDDHHMMSS_audit_log.sql

Contenido en orden:

  1. Crear tabla audit_log con columnas definidas arriba.
  2. RLS en audit_log: solo platform_admin puede SELECT.
  3. Trigger BEFORE UPDATE OR DELETE ON audit_log → función que lanza excepción (append-only enforcement).
  4. Función log_user_roles_change() SECURITY DEFINER → INSERT en audit_log.
  5. Trigger AFTER INSERT/UPDATE/DELETE en user_roles → llama a la función.
  6. Función log_survey_status_change() SECURITY DEFINER → INSERT en audit_log solo cuando NEW.status IS DISTINCT FROM OLD.status.
  7. Trigger AFTER UPDATE en surveys → llama a la función.
  8. Si DDL trigger es viable (4.1): función + trigger DDL.
  9. Comentario al final documentando el gap SEC-003.

4.3 Verificación en VPS

NO aplicar la migración — solo generar el archivo y reportar el SQL completo para revisión del director. El director aplica manualmente después de aprobar.

5. DoD antes de reportar

  • git log --name-only -1 — solo el archivo de migración
  • SQL completo de la migración en el reporte (no resumido — el director necesita leerlo antes de aplicar)
  • Diagnóstico DDL trigger reportado (4.1)
  • Build sin errores: npm run build
  • Commit + push a Gitea
  • NO aplicado en VPS — esperar aprobación del director

6. Qué reportar

Diagnóstico DDL trigger (4.1):
- ¿Viable sin extensiones? ✓/✗
- Si no: razón y alternativa

SQL completo de la migración:
[pegar el SQL íntegro — no resumido]

Verificado:
- Build: ✓/✗
- git log --name-only -1: [lista]
- Commit: [hash]

NO aplicado en VPS — pendiente aprobación del director.

7. Qué NO hacer

  • No aplicar la migración en el VPS sin aprobación explícita del director
  • No implementar logging de accesos a datos sensibles (deferido)
  • No agregar extensiones nuevas a Postgres
  • No tocar el formulario público, las RPCs de dashboard, ni el middleware de auth
  • No usar GRANT EXECUTE a PUBLIC — solo al rol específico que necesita cada función
  • No omitir el comentario del gap SEC-003 en la migración

8. Versionado

feat(compliance): audit_log append-only — user_roles y surveys.status [SEC-002]

Tabla audit_log con RLS (solo platform_admin), trigger BEFORE UPDATE/DELETE
que aborta (append-only enforcement), triggers AFTER en user_roles y
surveys.status. Patrón SECURITY DEFINER reutilizado de auth_user_roles.
Gap SEC-003 (TRUNCATE por superusuario) documentado y aceptado.