Modelo de datos — Integration Layer
Implementado por: migraciones 0003_integration_foundation,
0004_connector_owns_tokens y 0005_tenant_hardening
Validación: PostgreSQL 17 efímero y PostgreSQL persistente; Alembic en
0005_tenant_hardening
Tablas
| Tabla | Propósito | Boundary |
|---|---|---|
integration_connections |
Cuenta externa vinculada y estado neutral al proveedor | organization_id + RLS |
integration_connection_intents |
Prueba de enlace corta, hasheada y de un solo uso | organization_id + user_id + RLS |
shopify_installations |
Identidad, scopes y estado Shopify sin credenciales del proveedor | organization_id + connection_id + RLS |
integration_webhook_receipts |
Inbox idempotente y auditable antes del procesamiento | organization_id + connection_id + RLS |
Las cuatro tablas tienen UUID, timestamps, deleted_at, filtro explícito obligatorio y ENABLE/FORCE ROW LEVEL SECURITY. El rol commerce_app recibe únicamente SELECT, INSERT y UPDATE; no recibe DELETE.
Invariantes de base de datos
(provider, external_account_id)es único mientras la conexión no esté eliminada lógicamente.(id, organization_id)identifica una conexión para foreign keys tenant-aware;- una conexión tiene como máximo una instalación Shopify;
- instalación y webhook deben referenciar una conexión del mismo
organization_id; - usuario de intent y
connected_by_user_iddeben tener membresía en la misma organización; shop_idShopify activo es único globalmente;- el hash del intent es único;
(provider, webhook_id)deduplica deliveries;- ningún registro de integración puede insertarse o actualizarse si su
organization_idno coincide conapp.current_organization_id.
Credenciales
TimeLiber Connect es el único dueño de access/refresh tokens Shopify y los
persiste cifrados en su tabla Session, dentro de commerce_connect_db.
Commerce no recibe ni copia esos valores. Las columnas históricas de credencial
en shopify_installations quedaron nullable en 0004 para compatibilidad de
esquema y deben permanecer NULL en el flujo Shopify actual.
Borrado lógico
La misma migración añade deleted_at a users, organizations y organization_members. Sus repositories excluyen registros eliminados. El borrado físico queda reservado para procesos de compliance específicos y auditados, no para endpoints ordinarios.
Evidencia automatizada
tests/integration/test_tenant_rls.py ejecuta upgrade completo y siembra tres
organizaciones. Como runtime del tenant A comprueba:
- sólo una conexión, instalación y receipt visibles;
- la conexión del tenant B no es observable;
- un insert dirigido al tenant B es rechazado por RLS;
row_security=offno permite bypass;- roles runtime/app sin
SUPERUSERniBYPASSRLS.
Desde 0005, la prueba deja que Testcontainers destruya la base en vez de
intentar bajar a través de la migración irreversible 0004. También comprueba
autoafiliación, membresías eliminadas, foreign keys tenant-compuestas y creación
válida del owner inicial.
Casos de uso actuales
src/domains/integrations/service.py expone una primera superficie provider-neutral:
- crear intent Shopify de 10 minutos;
- almacenar únicamente SHA-256 del secreto;
- consumirlo una sola vez con organización, usuario, provider e
intent_idcoincidentes; - listar conexiones como allowlist sin ciphertext.
La capa HTTP está montada y probada con dobles. No existe todavía una validación OAuth contra la Development Store y no debe interpretarse este milestone como una instalación Shopify real.