> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ADR-022 — identity como bounded context separado

> DEC-B1 nombró cuatro schemas de Postgres: core, loyalty, messaging y crm. La autenticación y la autorización quedaron repartidas entre ellos en vez de tener casa propia.

Estado: Propuesto · Fecha: 2026-08-17 · Extiende: **DEC-B1** · Refs: DEC-D5, DEC-D6, ADR-009, ADR-010

> Traducción. Autoritativo: [`../../adr/adr-022-identity-bounded-context.md`](/adr/adr-022-identity-bounded-context).

## Contexto

DEC-B1 nombró cuatro schemas de Postgres: `core`, `loyalty`, `messaging`, `crm`. La autenticación y
la autorización nunca se asignaron a ninguno, porque hasta ahora se trataban como infraestructura que
simplemente existía.

No pueden quedar sin asignar. La plataforma necesita usuarios que entran a una consola, roles que
agrupan permisos, API keys y clientes OAuth que llevan esos mismos roles como máquinas (DEC-D5), un
provider OAuth2 para que los terceros se integren, MFA, gestión de sesiones e impersonación de
soporte acotada en el tiempo. Eso es un dominio con sus propias tablas, invariantes y superficie de
cumplimiento — no una librería.

La pregunta real es dónde va. Había tres opciones.

**Dentro de `core`.** Tentador, porque `core` ya tiene el realm de member (ADR-010, FS-CORE-0009): un
member es un contacto autenticado, así que la auth de member sí pertenece junto al contacto. Pero los
usuarios de consola no son contactos. Son el personal de nuestros clientes, una población totalmente
distinta con otro ciclo de vida, otro radio de daño y otra posición regulatoria — para los usuarios
de consola Softcrum es el **responsable**, mientras que para los contactos es el **encargado**
(DEC-J1). Poner ambos en un schema pondría los datos de nuestros propios usuarios y los de los
clientes de nuestros clientes bajo el mismo techo, que es exactamente el límite que traza el DPA.

**Repartido en cada módulo.** Cada módulo dueño de sus propios permisos. Rechazado de inmediato: el
registro de permisos tiene que ser global para que `{module}.{resource}.{action}` signifique algo, y
un rol que no puede cruzar módulos no es un rol.

**Su propio contexto.** Más superficie de gobierno, un schema más que razonar.

## Decisión

**Un quinto bounded context, `identity` (schema `identity`), es dueño del realm organizacional y del
plano de control de acceso de la plataforma.**

Contiene: organizaciones y usuarios, membresías, invitaciones, el registro de permisos, roles y
asignaciones, métodos de autenticación, login social y SSO empresarial, MFA, sesiones y dispositivos,
credenciales de máquina (API keys y clientes OAuth), el provider OAuth2, y la impersonación de
soporte.

**El realm de member se queda en `core`** (FS-CORE-0009), sin cambios. Un member es un contacto
autenticado y el contacto pertenece a `core`; mover las sesiones de member a `identity` crearía una
dependencia de `identity` hacia `core` sin beneficio, y difuminaría la separación de realms que
ADR-010 hace estructural.

Reglas de dependencia, agregadas al conjunto existente:

* `identity` no depende de **nada**. Es una fundación, junto a `core`, ni por encima ni por debajo.
* `core`, `loyalty`, `messaging` y `crm` **nunca importan las tablas de `identity`**. Reciben un
  contexto de autorización ya resuelto desde la capa de API.
* El registro de permisos lo consume cada módulo como **constante de build**, no como FK de runtime.
  Una ruta que declara `loyalty.redemptions.create` se verifica contra un union generado, el mismo
  mecanismo que los catálogos paramétricos.
* Existen dos realms de auth y nunca se superponen: `identity` (organizacional) y `core` (member). Un
  token de uno nunca puede satisfacer al otro, por construcción y no por verificación.

## Consecuencias

**+** Los datos de nuestros propios usuarios quedan en un schema distinto de los datos de los
clientes de nuestros clientes, lo que calza con la división responsable/encargado que el DPA ya
describe. Eso es una propiedad de cumplimiento, no de prolijidad.
**+** Un registro global de permisos se vuelve posible, que es lo que DEC-D5 necesita y lo que
renderiza una pantalla de consentimiento OAuth2.
**+** El realm organizacional se puede endurecer de forma independiente — política de sesión más
estricta, MFA obligatorio, auditoría más apretada — sin tocar la experiencia del member.
**+** Better Auth se configura dos veces, en dos lugares, lo que hace visible la separación de realms
en el árbol de archivos en vez de dejarla como una convención que alguien tiene que recordar.

**−** Un quinto contexto que gobernar, y la frase "cuatro bounded contexts" de la constitución hay
que actualizarla.
**−** Dos configuraciones de Better Auth que mantener y actualizar en conjunto.
**−** El contexto de autorización hay que hilarlo desde la capa de API hacia cada módulo, ya que los
módulos no pueden leer `identity` directamente. Ese es el costo del límite y es deliberado: un módulo
que pudiera consultar roles directamente terminaría aplicándolos de forma inconsistente.

## Trabajo de seguimiento

* `AGENTS.md` §1 y §3: cinco contextos, no cuatro.
* `standards/data.md` §1: se agrega `identity`; la regla de FK entre schemas no cambia (solo hacia
  `core` — `identity` no tiene ninguna FK entre schemas).
* `standards/api.md`: el registro de permisos pasa a ser un artefacto generado con su propio chequeo
  de CI.
* Spec de módulo, PRD y nueve feature specs bajo `modules/identity/`.

## Alternativas consideradas

**Auth dentro de `core`** — perdió por el límite responsable/encargado. La posición regulatoria de
nuestros usuarios y la de los clientes de nuestros clientes es distinta, y un límite de schema es el
lugar más barato para hacerlo explícito.

**Un proveedor de identidad de terceros (Auth0, Clerk, WorkOS) en vez de Better Auth** — genuinamente
atractivo para MFA, SSO y el provider OAuth2, que son trabajo real. Perdió por tres razones: el
pricing por MAU sobre una plataforma cuyo propio pricing es por contacto se apila mal; ser *provider*
OAuth2 para las integraciones de nuestros tenants es una superficie de producto de primera clase que
no deberíamos arrendar; y la regla de stack cerrado hace que adoptar uno sea un ADR propio con
camino de migración, no un default. Better Auth se mantiene, y esta decisión se revisa si MFA o SSO
resultan más caros de construir de lo esperado.
