Skip to main content
Traducción. Autoritativo: fs-idn-0001-organizations-and-users.md.

Contexto

Todo lo demás en la plataforma cuelga de saber a qué organización pertenece un request. La decisión que conviene enunciar temprano es que el email de un usuario es único por organización, no a nivel de base de datos (standards/data.md §2b). La misma persona puede tener cuenta con dos de nuestros clientes, y esas cuentas no se relacionan: dos filas, dos credenciales, dos sesiones, y ninguna organización puede descubrir que existe la otra. La conveniencia que esto entrega es real y hay que nombrarla: una agencia que maneja tres clientes mantiene tres logins en vez de uno con selector. Lo aceptamos. Un email único global convertiría “invitar a esta dirección” en un oráculo que revela si esa persona ya trabaja con alguien más, y crearía una credencial cuyo compromiso alcanza a varios clientes nuestros a la vez. El aislamiento gana sobre la conveniencia; si el reclamo se vuelve fuerte, el account linking opcional se puede agregar después sin migración, porque sería una tabla nueva y no un constraint cambiado. La segunda es que una invitación es evidencia. Quién dejó entrar a quién a una cuenta, y cuándo, es de las primeras preguntas tras un incidente. Las invitaciones aceptadas se conservan.

Alcance (normativo)

  • identity.organizations, una por tenant, con slug y configuración de locale.
  • identity.users, email único por organización (UNIQUE (organization_id, email)).
  • identity.memberships con las asignaciones de rol.
  • identity.invitations con tokens de un solo uso que expiran.
  • Desactivación de una membresía sin borrar historia.
  • Creación de la organización en el registro, y el primer usuario como su owner.

Fuera de alcance (normativo)

  • La autenticación misma — FS-IDN-0003. Esto entrega quién existe, no cómo lo prueba.
  • Roles y permisos — FS-IDN-0002.
  • Facturación y configuración de plan. Los entitlements se leen acá, nunca se poseen.
  • SCIM y sincronización de directorio — F2.

Comportamiento (normativo)

  1. El email de un usuario es único por organización. La misma dirección en dos organizaciones son dos usuarios independientes con credenciales independientes. PROHIBIDO: cualquier índice, búsqueda o flujo de vinculación global sobre el email.
  2. Una organización siempre tiene al menos una membresía activa con el rol owner. El último owner no se puede quitar ni degradar — la operación se rechaza, no se advierte.
  3. El registro crea la organización y la primera membresía en la misma transacción.
  4. Una invitación es un token de un solo uso con expiración. Aceptarla crea la membresía y la marca aceptada; la fila se conserva, nunca se borra.
  5. Una invitación a un email que ya tiene usuario en esta organización se adhiere a ese usuario. Una dirección que existe en otra organización se trata como nueva acá — y el flujo nunca debe revelar, por respuesta, tiempo ni redacción, que la dirección se conoce en otra parte.
  6. Desactivar una membresía revoca las sesiones de ese usuario solo para esa organización.
  7. Una membresía nunca se borra en duro. Desactivarla conserva el rastro, y quien vuelve recupera su membresía en vez de recrearla.
  8. Toda mutación escribe en core.audit_log con actor tipado (ADR-017).
  9. PROHIBIDO: una membresía sin organización · una invitación reutilizable tras aceptarse o expirar.

Datos (normativo)

Ninguna tabla se particiona. El volumen lo acota la dotación de nuestros clientes, no la de los suyos.

API (normativo)

Eventos (normativo)

identity.user.invited, identity.user.joined y identity.user.deactivated. No se ofrecen como webhooks salientes en F1: los cambios de personal de un tenant no son algo a lo que sus integraciones deban suscribirse por defecto.

Criterios de aceptación (normativo)

  1. El registro crea organización y membresía owner en una transacción; una falla inducida no deja ninguna de las dos.
  2. El mismo email invitado a una segunda organización crea un usuario independiente ahí. La respuesta, su tiempo y su redacción son idénticos a invitar una dirección jamás usada.
  3. Quitar o degradar al último owner se rechaza con error tipado.
  4. Una invitación aceptada no se puede aceptar de nuevo; una expirada se rechaza con razón distinta.
  5. Desactivar una membresía revoca sesiones de esa organización y deja válidas las de otras.
  6. Una invitación aceptada sigue legible un año después, nombrando quién invitó a quién. 6b. Ningún endpoint, mensaje de error ni diferencia de tiempo revela que un email existe en otra organización.
  7. Test de denegación RLS entre organizaciones.
  8. Negativo: ningún endpoint borra en duro una membresía ni una organización.

Ejecución

Un solo slice, comando síncrono. Las invitaciones se entregan por messaging consumiendo el evento user.invited — este módulo nunca habla con un proveedor de correo directamente.

Preguntas abiertas

Changelog

Registro de entrega

Aún no implementado.