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

124 lines
5.4 KiB
Markdown

# 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.
```