Publicación
Abrir la tienda: Racoonity ya sabe quién eres
Cómo pasamos de tener un runtime con pulso a una instalación que puede configurarse, autenticar usuarios y proteger cada capacidad con permisos reales.
En la entrada anterior contamos que Racoonity ya tenía pulso.
La API podía arrancar, conectarse a PostgreSQL, registrar capacidades, ejecutar acciones y consultas, confirmar o revertir transacciones, guardar eventos en una outbox y proteger operaciones idempotentes.
Eso era mucho más que un proyecto vacío, pero todavía no era una tienda abierta.
Racoonity podía ejecutar trabajo, aunque aún no sabía quién estaba frente a la puerta, a qué compañía pertenecía, en qué sucursal operaba ni qué tenía permitido hacer.
El cambio initialize-workspace-and-auth, correspondiente a la iteración I1 — Abrir la tienda, nació para resolver justamente eso.
Instalación vacía
→ configuración inicial
→ administrador creado
→ login
→ contexto autenticado
→ operación mínima disponible
Después de siete oleadas de implementación, pruebas, correcciones y documentación, esa parte ya existe.
Dónde estábamos
Al cerrar I0 teníamos una columna vertebral técnica bastante completa:
- Una API en Go.
- PostgreSQL y migraciones controladas.
- Racoon Guildmaster registrando capabilities.
- Racoon Dispatcher ejecutando actions.
- Racoon Librarian resolviendo queries.
- Racoon Foreman controlando commit y rollback.
- Validación estructural mediante Racoon Inspector.
- Outbox transaccional.
- Idempotencia.
- Errores públicos normalizados.
- Correlation y causation IDs.
- Un composition root capaz de conectar todo.
El sistema ya entendía qué podía ejecutarse y cómo debía ejecutarse.
Lo que no entendía todavía era quién lo estaba ejecutando.
La autorización existente era apenas una política técnica para permitir las capabilities de I0. ActorContext ya formaba parte de los contratos, pero las solicitudes HTTP no tenían una identidad real. No existían compañía, sucursal, usuarios, credenciales, sesiones, roles ni permisos persistidos.
Tampoco existía una forma segura de convertir una instalación vacía en una operación mínima.
En términos simples: habíamos construido el sistema nervioso, pero todavía no había una puerta de entrada.
La primera apertura
La nueva pieza central es setup.initialize.
No es un script de seed ni una colección de inserts sueltos. Es un caso de uso que coordina varios dominios para convertir una base recién migrada en una instalación operativa.
En una sola transacción crea:
- La compañía.
- La sucursal inicial.
- Las preferencias básicas.
- El primer administrador.
- Sus credenciales.
- Los roles base.
- Los permisos y grants.
- Una caja inicial.
- Una ubicación predeterminada de venta.
- Los métodos de pago iniciales.
Si alguna parte falla, todo se revierte.
No puede quedar una compañía sin administrador, una sucursal sin contexto operativo o media configuración esperando una reparación manual.
Además, el setup es idempotente. Repetir la misma solicitud con la misma clave no duplica usuarios, roles, eventos ni infraestructura operativa. Intentar inicializar nuevamente con otra solicitud recibe un error funcional reconocible.
La instalación pasa de pending a completed una sola vez.
Ésa fue una de las decisiones más importantes de esta iteración:
La configuración inicial no es una tarea administrativa alrededor del sistema. Es una operación crítica del sistema.
Racoonity ya sabe quién eres
I1 introduce el dominio Identity y la primera versión real de Racoon Gatekeeper.
Ahora existen:
- Login mediante credenciales.
- Passwords protegidos con Argon2id.
- Tokens opacos.
- Sesiones almacenadas del lado del servidor.
- Resolución de sesión.
- Logout.
- Revocación.
- Expiración.
- Archivo y reactivación de usuarios.
El token que recibe el cliente no se guarda en claro. PostgreSQL conserva su hash, mientras que la identidad efectiva se construye internamente a partir de una sesión válida.
El boundary HTTP resuelve el Bearer una sola vez y produce dos contextos separados:
ActorContext, con usuario, compañía, sucursal y rol.AuthenticatedSessionContext, con la identidad interna de la sesión.
Los handlers no leen headers, no reciben el Bearer y no pueden aceptar un session_id fabricado desde el payload.
Probamos explícitamente intentos de falsificar usuario, compañía, sucursal, rol y sesión mediante JSON, query params y headers arbitrarios. Ninguno sustituye el contexto resuelto por Gatekeeper.
También unificamos los fallos públicos de sesión. Un token desconocido, expirado, revocado o perteneciente a un usuario archivado produce la misma respuesta pública. La causa real permanece dentro del sistema.
Una API no debería ayudar a enumerar usuarios o explicar demasiado amablemente por qué una credencial dejó de funcionar.
Saber quién eres no basta
Identity responde quién eres.
Organization responde dónde operas.
Permissions responde qué puedes hacer.
Esta separación, que parecía una frase arquitectónica en los documentos, ahora existe en código y en PostgreSQL.
Racoonity puede resolver:
- La compañía y sucursal activas.
- El perfil del usuario.
- Su rol.
- Sus permisos efectivos.
- La caja inicial.
- La ubicación de venta.
- Los métodos de pago disponibles.
Racoon Bouncer reemplazó la autorización técnica provisional de I0. Antes de ejecutar una action o query, verifica que la capability exista y después decide si el actor tiene permiso para usarla.
Esa distinción importa:
capability inexistente
≠
capability existente sin permiso
Lo primero significa que el sistema no ofrece esa capacidad. Lo segundo significa que la capacidad existe, pero el actor no puede utilizarla.
Mezclar ambas respuestas puede parecer un detalle pequeño, hasta que otros módulos, clientes o herramientas empiezan a depender de ellas.
Administrar usuarios también tiene reglas
La superficie de usuarios ya permite:
- Crear.
- Consultar.
- Listar.
- Actualizar.
- Cambiar rol.
- Restablecer contraseña.
- Archivar.
- Reactivar.
Pero la parte importante no es tener ocho endpoints. La parte importante son las invariantes que los rodean.
Por ejemplo, Racoonity no permite archivar o degradar al último administrador activo.
La regla LAST_ACTIVE_ADMIN se protege dentro de la transacción y fue probada bajo concurrencia. Dos solicitudes simultáneas no pueden dejar la instalación sin alguien capaz de administrarla.
Archivar un usuario también revoca sus sesiones. Reactivarlo no revive automáticamente tokens anteriores. Una sesión revocada permanece revocada.
Estas reglas son menos visibles que un formulario, pero son las que separan un CRUD de usuarios de un sistema de identidad coherente.
El sistema también debe desconfiar de sí mismo
Durante esta iteración añadimos validaciones de arranque fail-closed.
Cuando una instalación ya está completada, Racoonity comprueba que:
- Las capabilities requeridas estén registradas.
- Action y query conserven el tipo correcto.
- Los módulos obligatorios estén conectados.
- Gatekeeper y Bouncer existan.
- Los inspectores y proveedores de hechos estén disponibles.
- Los grants persistidos cubran realmente el catálogo aprobado.
- Los roles de negocio no reciban permisos técnicos.
Si algo no coincide, la API no arranca parcialmente ni intenta reparar la base de datos en silencio.
Falla.
Esto nos dejó uno de los aprendizajes más concretos del cambio. La primera versión de RoleGrantCoverage comprobaba únicamente que cada rol tuviera algún grant. El contrato, sin embargo, exigía que los grants persistidos cubrieran exactamente las capabilities requeridas por el catálogo.
La diferencia entre ambas frases es pequeña. La diferencia de seguridad no lo es.
La prueba correcta eliminó un único grant requerido de un rol que todavía conservaba otros permisos. La implementación anterior consideraba el estado válido. La nueva comprobación detecta la ausencia y detiene el arranque.
Una validación aproximada puede ser más peligrosa que no tener validación, porque entrega una falsa sensación de seguridad.
Lo que existe ahora
Al cerrar initialize-workspace-and-auth, Racoonity puede realizar este recorrido completo desde HTTP y PostgreSQL real:
consultar estado de instalación
→ inicializar compañía y sucursal
→ crear administrador
→ iniciar sesión
→ resolver identidad y permisos
→ consultar contexto operativo
→ administrar usuarios
→ cerrar sesión
La información persiste al reconstruir completamente la composición contra la misma base de datos. La sesión, la instalación completada, los usuarios, roles, caja, ubicación y métodos de pago no dependen de memoria del proceso.
También quedaron protegidos:
- Setup atómico e idempotente.
- Outbox sin eventos duplicados.
- Sesiones opacas y revocables.
- Errores públicos sin secretos.
- Resultados idempotentes sin credenciales.
- Ownership de tablas por módulo.
- Ausencia de SQL cross-domain.
- Fronteras entre HTTP, Identity, Permissions y Organization.
- Compatibilidad con las capabilities técnicas de I0.
La aceptación final cerró con:
- 98 tareas completadas de 98.
- 34 paquetes de pruebas en verde.
- Suite completa ejecutada tres veces sobre PostgreSQL real.
- Cero skips relevantes de integración.
- Validación OpenSpec strict satisfactoria.
La suite con -race no pudo ejecutarse porque el entorno actual no tiene CGO habilitado. La limitación quedó documentada en lugar de maquillarse como una prueba superada.
Lo que todavía no existe
Racoonity aún no tiene una interfaz de setup, pantalla de login ni shell visual.
Esta iteración entrega el backend que esas experiencias necesitarán, no pretende fingir que la aplicación ya está lista para una tienda real.
Tampoco introdujimos:
- Recuperación de contraseña por correo.
- MFA.
- Refresh tokens.
- Multiempresa.
- Multisucursal real.
- Roles personalizados.
- Consumers para la outbox.
- Atlas.
- Artifier.
- Courier.
- Workflows.
- Plugins.
- Reportes.
- POS.
Mantener fuera esas capacidades fue tan importante como terminar las incluidas.
Uno de los riesgos permanentes de Racoonity es construir hoy toda la potencia que imaginamos para mañana. I1 tenía que abrir la tienda, no construir el centro comercial entero.
Lo que aprendimos
1. Autenticación no significa “hacer un login”
Un login aislado es sencillo.
Un sistema de identidad necesita además sesiones, expiración, revocación, protección contra enumeración, límites de contexto, administración de usuarios, reglas de último administrador y una frontera clara entre credenciales y casos de uso.
La mayor parte del trabajo ocurrió alrededor del login, no dentro de él.
2. Setup pertenece al dominio de aplicación
Inicializar una operación involucra varias áreas, pero ninguna debería apropiarse de todas.
Setup coordina. Organization posee compañía y sucursal. Identity posee usuarios y credenciales. Permissions posee roles y grants. Registers, Inventory y Payments poseen su bootstrap mínimo.
El caso de uso conoce el recorrido. Cada módulo conserva la autoridad sobre sus datos.
3. Las fronteras deben probarse
No basta con decir que los handlers no deben leer el Bearer o que un módulo no debe consultar tablas ajenas.
Las architecture tests convierten esas decisiones en restricciones ejecutables.
Una arquitectura documentada puede degradarse lentamente. Una arquitectura probada al menos tiene una alarma.
4. La prueba debe representar el camino productivo
Durante la certificación detectamos una validación de startup correctamente probada, pero todavía desconectada de composition.Build.
El validator funcionaba. La aplicación real no lo usaba.
La solución no fue escribir más pruebas alrededor de la copia, sino conectar una sola fuente de verdad al arranque productivo y demostrar que un segundo Build contra grants corruptos falla cerrado.
Una prueba sobre una pieza que producción no ejecuta solo demuestra que esa pieza funciona.
5. También hay que definir cuándo detenerse
Esta iteración tuvo 98 tareas y una superficie amplia. Sin criterios de cierre explícitos, siempre habría otra repetición, otro grep o una prueba adicional “por seguridad”.
Aprendimos a limitar cada ronda:
alcance cerrado
→ evidencia exacta
→ pruebas focalizadas
→ una regresión final
→ reporte
→ detenerse
La disciplina no consiste únicamente en probar mucho. También consiste en saber cuándo la evidencia ya es suficiente.
Abrir la tienda sin pintar el escaparate
I0 demostró que Racoonity podía ejecutar trabajo.
I1 demuestra que puede comenzar a operar con identidad, organización y permisos reales.
Todavía no hay productos que vender ni una pantalla donde capturarlos. Ésos pertenecen a la siguiente iteración.
Pero ahora, cuando lleguen, no serán registros flotando en una base de datos. Tendrán una compañía, una sucursal, un usuario, un rol, un contexto operativo, una transacción y una capability autorizada detrás.
El siguiente cambio será I2 — Primer catálogo dinámico.
Ahí comenzaremos a acercarnos a una de las promesas centrales de Racoonity: construir categorías y productos sobre los primeros componentes de metadata, Racoon Atlas y Racoon Painter, sin abandonar la simplicidad de una operación pequeña.
Por ahora, la puerta ya abre.
Y Racoonity ya sabe a quién está dejando entrar. 🦝