ADR-018: Isolation of Subscriber data
Estado
Accepted — 2026-08-23
Amendment 2026-08-24: PlatformUser in factory; impersonation never uses a magic organizationId.
Amendment 2026-09-08: Product tenant ≠ Organization B2B. Isolation boundary = Subscriber. Filename kept (multi-tenancy) for stable links. Factory column organizationId is a leftover name until a remap slice.
Contexto
IVPrior es SaaS B2C: muchas cuentas de traders (Subscribers) en una sola app. El Subscriber A nunca ve watchlist, oportunidades, diary ni billing del Subscriber B.
La fábrica copiada (FoodHub/Cashlane) aislaba Organization (agencia / restaurante con staff). Eso no es el modelo de negocio (DOC-001, DOC-002). El patrón técnico (shared Postgres + columna de cuenta) sí se reutiliza.
Decisión
- Límite de aislamiento de producto = Subscriber (cuenta de un trader que compra un Plan). En docs y copy nueva:
subscriberId. En schema Prisma de fábrica: la columna puede seguir llamándoseorganizationIdhasta el slice de remap — un solo id de cuenta, no dos modelos. - Shared Postgres (una base). Aislamiento lógico, no schema-per-tenant ni DB-per-tenant en V1.
- Toda tabla de negocio del Subscriber (excepto catálogos globales / platform) lleva el id de cuenta NOT NULL + índice.
- Todo use case de Subscriber recibe el id del actor autenticado; prohibido tomarlo solo del body si contradice el token.
- Queries de dominio del Subscriber siempre filtran por ese id. Tests de aislamiento son obligatorios en módulos de cuenta.
- RLS de Postgres: opcional como defensa en profundidad más adelante, no gate de MVP.
- Staff IVPrior = PlatformUser (ADR-023). Opera
/platformcon JWTuniverse: platform. Nunca un id de cuenta “mágico” para leer todos los Subscribers. Impersonation (si existe) es sesión explícita, TTL + audit. - Catálogos globales (planes SaaS, branding default de plataforma, permisos de staff) no llevan id de Subscriber.
- V1 no modela Organization B2B, staff del subscriber, locations, ni “tenant restaurant”. Un humano = un Subscriber.
Alternativas
| Opción | Nota |
|---|---|
| Shared DB + id de cuenta | Elegida — costo, DX, fábrica |
| Organization B2B + empleados | Rechazada — no es el comprador de IVPrior |
| Schema-per-tenant | Operación pesada para retail B2C |
| DB-per-subscriber | Inviable en V1 |
Consecuencias
- Tenant en copy de producto está prohibido; el listado
/platform/tenantses leftover de UI hasta rename a Subscribers. - Customer de Cashlane (deudor) no es el Subscriber. No reutilizar esa entidad.
- AuditLog puede tener id de cuenta null cuando el actor es PlatformUser.
- Remap
organizationId→subscriberId+ rename de nav es un Spec Kit posterior; este ADR ya fija el significado.