# 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:
  ```sql
  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:
```bash
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.
```