Agent Squad · Substrato Postgres

Wave 6 · RLS Defense-in-Depth

Que la base de datos misma impida que un inquilino vea los datos de otro, aunque una consulta se olvide el filtro por workspace_id. Red de seguridad por debajo del código.

Estado RLSOFF
Fase actual1 · Cutover T6G
Runtime corre comosubstrate_app
NOSUPERUSER

La protección todavía NO está encendida. Lo hecho hasta hoy es el andamiaje: poner el runtime en un rol acotado al que RLS pueda aplicarle. RLS se enciende en la Fase 3 (T6J).

FASE 0

Fundación

Completa · en prod Roles nuevos, policies escritas pero inertes, grants y fencing. Nada cambia el comportamiento todavía.
  • 3 roles cross-tenant: substrate_app (runtime), substrate_reclaim (hot-path v2), substrate_maintenance (crons read-only)reclaim + maintenance ya LIVE en prod
  • 0088 T6D · policies RLS escritas, RLS aún OFF (inertes)
  • 0089 T6E · get_user_home
  • 0090 / 0092 / 0093 T6F · grants+policies de reclaim, fencing de lease_token, rol maintenance
FASE 1

Cutover T6G

En curso estás acá Cambiar el rol del runtime de substrate (superuser) a substrate_app (acotado). RLS sigue OFF: canario de privilegios puro, reversible en un flip.
  • Pre-check de grants + review adversarial (GPT sol)cerró 2 gaps: vector/vectorscale EXECUTE y fusionar_entendimiento + views
  • 0094 / 0095 grants faltantes de substrate_app (research logging, EXECUTE de extensiones, views)
  • 0096 particiones 2027-2029 de traces/step_executions (bomba de tiempo preexistente)
  • 0097 views a security_invoker (prep para T6J)
  • Flip SUBSTRATE_DB_URL → substrate_app + restart · verificado (health, rol efectivo, reclaim/maintenance intactos, 0 errores)
  • Fix del guard reset-contract (hojas de partición cubiertas por su padre)
  • Cobertura verificada bajo substrate_app/reclaim: 54 tablas DELETE del reset, ciclo compute (enqueue app + claim/complete reclaim), emitClaim+vector, fusionar, views, LISTEN/NOTIFY — 0 permission deniedhot-path cross-tenant de reclaim ejercitado en vivo · monitores de soak siguen activos
FASE 2

Endurecimiento

Pendiente Cerrar contratos y volver obligatorias las pruebas de aislamiento antes de tocar el interruptor de RLS.
  • T6F.3c · contrato de leases: CHECK constraint + CAS estricto sobre lease_token
  • T6H · tests de RLS con FORCE obligatorios en CI (que un aislamiento roto ponga el pipeline en rojo)
FASE 3

Encendido de RLS

Pendiente Acá deja de ser andamiaje: la base empieza a rechazar filas de otros inquilinos aunque el código falle. Lento a propósito, con soak entre tandas.
  • T6I · canario: encender RLS en 1 sola tabla + soak (validar en vivo con impacto mínimo)
  • T6J · encender RLS en las 67 tablas por tandas J1 → J4, con soak entre cada una, el worker al final
FASE 4

Cierre

Pendiente Limpieza y sello: revocar lo que quedó de más y saldar la deuda técnica que dejó el camino.
  • T6K / T6L · cierre, revocaciones finales y documentación del modelo de aislamiento

Referencia

Hecho · verificado en prod
En curso · estás acá
Pendiente

Deuda técnica / follow-ups anotados

Job anual de particiones antes de 2030 (crea la partición del año siguiente y alerta si el horizonte se acerca)
Grants de hoja bootstrap a revisar en T6J (superficie de bypass si se consulta la partición directo)
Filtro de CI "Qué tocó este push" saltea la suite vitest en cambios solo-test/json (validación local cubre el hueco por ahora)

Snapshot 2026-09-01 · substrato en 127.0.0.1:5433/substrate · rollback del cutover = restaurar .env + restart (instantáneo)