Saltar a contenido

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_id deben tener membresía en la misma organización;
  • shop_id Shopify 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_id no coincide con app.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=off no permite bypass;
  • roles runtime/app sin SUPERUSER ni BYPASSRLS.

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_id coincidentes;
  • 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.