metis-desk/docs/auditorias/resources-global-scope/03-sequelize-mariadb.md
2026-10-25 01:19:17 +02:00

13 KiB

03 — Sequelize y MariaDB

Esquema verificado en fuentes

CONFIRMADO: resource.model.ts no declara un ResourceModel extends Model; migrateInventory crea tablas mediante QueryInterface y el adaptador usa SQL parametrizado. Sí hay modelos Sequelize para catálogo, bindings y disponibilidad. No inventar asociaciones belongsTo/hasMany que no están declaradas. Las relaciones importantes aquí son FKs SQL y reconstrucción manual. sql: resource.model.ts:19 catalogSQL: resource-catalog.model.ts:48 read: resource-inventory-relational.ts:195.

Tabla PK / índices / referencias Consecuencia
resources id PK; companyId NOT NULL → companies; templateId NOT NULL → resource_definitions Todo Resource válido tiene dueño
resources, añadidos posteriores unique directory_resource_scope_template(id,companyId,templateId); unique resource_company_identity(companyId,id) Soportan FKs compuestas de dominios/SMB/asistencia; no unicidad de nombre
resource_revisions id PK; unique resource_revision_position_unique(resourceId,position); resourceId/actorId/templateId FK Conserva referencias y orden
resource_characteristics PK resourceId,fieldId; FK a resources/definitions Valores tipados normalizados
resource_revision_characteristics PK revisionId,fieldId; resourceId y revisionId FK Valores históricos, sin companyId repetido
resource_specification_fields / sections / field_choices PK compuestas con resourceId; FK de raíz Nombres, tipos, orden, obligatoriedad, opciones
resource_revision_specifications revisionId PK/FK; resourceId FK Cabecera histórica templateId/templateName/sectionsDefined
resource_revision_specification_fields / sections / choices PK compuestas con revisionId; resourceId FK Instantáneas de estructura
resource_definitions id PK; stockKey unique nullable; companyId nullable Ámbito global ya representado por NULL para definiciones
resource_template_fields id PK; unique templateId,fieldId; FK de sección compuesta Vínculos de campos
resource_availability id PK; unique companyId,definitionId Override de definiciones, no de Resource
resource_directory_domains resourceId PK, companyId NOT NULL; FK (resourceId,companyId,templateId); unique companyId,normalizedDns y environmentId Operación local de directorio
resource_network_shares resourceId PK, companyId NOT NULL; FK compuesta; unique companyId,activeAddress Carpeta local; inactive libera dirección activa
software_installations / application_configurations id PK; companyId FK NOT NULL; resourceId FK simple; currentKey unique nullable Ámbito de relaciones validado en Application, no por FK compuesta a Resource

Fuentes: sql: resource.model.ts:19 typed: resource-relational-schema.ts:98 history: resource-revision-specifications.ts:39 directorySQL: directory-domain-migration.ts:5 shareSQL: network-share-migration.ts:4 supportSQL: support-migration.ts:86 softwareSQL: software-installation-migration.ts:6 applicationSQL: application-configuration-migration.ts:6.

resource_revision_specifications.templateId se declara UUID NOT NULL sin FK en esa cabecera; la revisión base sí tiene FK de templateId. No atribuir todas las restricciones de la raíz a todas las tablas históricas. Las columnas de integración tienen políticas RESTRICT/CASCADE distintas según tabla referenciada; ver fk de resource-relational-schema y el borrado explícito. history: resource-revision-specifications.ts:39 columns: resource-relational-schema.ts:25.

No hay unique SQL (companyId,templateId,name) en Resource. La regla de nombres vive en ResourceUseCases sobre un contexto bloqueado; el caso access distingue sistema/entorno/tipo. No sustituirla por un unique simplista. save: resource.use-cases.ts:64.

Qué rompe el diseño scope + nullable

DERIVABLE: cambian simultáneamente dominio UniqueID obligatorio, InventoryRow, mapper, contrato ResourceSchema, captura de plantilla, load(company), borrado por compañía, propagación y dashboard. El scope explícito requiere impedir pares contradictorios; una columna nullable no resuelve semántica ni seguridad. owner: resource.aggregate.ts:11 mapper: resource-persistence.mapper.ts:24 dto: inventory.ts:35 propagation: prepare-resource-template-updates.ts:15 dashboard: resource-dashboard-reader.ts:15.

Las FKs locales con companyId NOT NULL deben conservarse. Una referencia global no debe convertirse en SMB/dominio/asistencia. No hacer nullable la compañía de los hijos operativos. Las FKs simples de instalaciones/perfiles ya permiten apuntar al mismo resources.id desde compañías distintas; eso preserva identidad, pero no valida autorización ni ámbito. softwareFK: software-installation-snapshot.ts:7 applicationFK: configuration-snapshots.ts:57.

DOCUMENTADO en MariaDB oficial: UNIQUE admite varios NULL, y una FK compuesta con algún valor NULL en el hijo no exige coincidencia del padre. Por tanto, añadir unique(scope,companyId,name) no garantiza unicidad global, ni una FK nullable puede sustituir la regla «global permitido O propietario local». Mantener hijos operativos NOT NULL y resolver la regla en servicio propietario + Application. ALTER TABLE, Foreign Keys.

PROPUESTO A: scope VARCHAR restringido y NOT NULL; companyId nullable FK, con CHECK explícito de par: global implica NULL; company implica NOT NULL. Añadir validación global de plantilla/campos e integración en Resources. El CHECK de fila no resuelve la clasificación de una plantilla en otra tabla: no prometerlo como constraint automática. Alternativa B elimina el discriminador SQL redundante, ver 09.

Consultas e índices futuros

Evaluación teórica, sin EXPLAIN, volumen medido, estadísticas ni BD conectada. No se fijan mejoras en milisegundos ni se propone cada índice como obligatorio.

Consulta Hoy Adaptación propuesta Índice candidato / precaución
Compañía readInventory por companyId y carga completa Mantener gestión local companyId,id existente
Global No Resource global Endpoint de catálogo, solo application elegible scope,active,templateId,id según selectividad
Mixto No Candidatos autorizados: globals permitidos UNION ALL locales propios Dos ramas disjuntas; orden y paginación conjunta estable
Por plantilla Filtrado frecuente en navegador Predicado templateId opcional companyId,templateId,active,id si EXPLAIN lo justifica
Por uso Servicio clasifica en memoria Resolver stockKey/usage vía definitions Evitar duplicar usage mutable en Resource
Por nombre Filtro local del inventario completo Búsqueda servidor parametrizada y limitada Índice B-tree no garantiza acelerar substring/LOCATE; definir collation y criterio
Por ID Find en contexto completo / FKs simples Resolver lote/ID con compañía autorizada y propósito PK id + comprobar visibilidad, nunca findByPk sin policy
Activos Boolean en valores y filtros UI Candidatos activos; resolución de usados incluye inactivos Medir selectividad de active antes de indexar
Historial Carga todas revisiones por raíz Fuera del DTO de candidatos Índices resourceId y posición existentes
Dashboard Count incluye globales con filtro opcional, filas inner join companies Separar catálogo global de métricas operativas Evitar total que cuente NULL pero filas que lo omitan

read: resource-inventory-relational.ts:195 usage: resource-inventory.service.ts:54 shareOptions: network-share-options.ts:11 dashboard: resource-dashboard-reader.ts:15. No hay búsqueda Resource de servidor con paginación hoy; las options SMB sí tienen cursor y LIMIT 21 y no deben ensancharse.

Migración futura segura — no ejecutada

  1. Inventariar SHOW CREATE TABLE/FKs/índices y versión exacta de MariaDB sobre entorno de prueba, respaldar y ensayar recuperación; verificar diferencias respecto a migraciones idempotentes.
  2. Preparar autorización/lectores que toleren scope sin producir globales. Feature flag de escritura global desactivada, almacenada en configuración validada existente si se aprueba.
  3. Expansión: añadir scope con default company compatible con escritores antiguos y backfill explícito de filas existentes; permitir companyId NULL manteniendo FK. Si ALTER encuentra dependencia de FK/índice, retirarla/recrear solo en orden ensayado, sin desactivar foreign_key_checks como solución.
  4. CHECK de combinación, índices justificados y validaciones de mappers/DTO. Verificar companyId de todas las filas legacy no nulo; no cambiar IDs ni revisiones.
  5. Desplegar lectores completos (incluyendo propagación, historial, resolución de nombres y dashboard) a todas las instancias y clientes relevantes. No activar globales con lectores antiguos: NULL rompe mapper y Zod antiguos.
  6. Habilitar escrituras globales restringidas; luego candidatos para software y perfiles. Mantener gestión local y licencias aisladas. Probar concurrencia desactivación/asociación y eliminación.
  7. Activación gradual con verificación de invariantes, recuentos por ámbito y referencias cruzadas; no crear adopciones implícitas ni backfill de versiones/licencias.
  8. Contracción: retirar defaults de compatibilidad cuando no queden escritores antiguos, si el contrato final exige scope explícito. Versionar migración aplicada y evitar regenerar catálogo/IDs.

Rollback: antes de crear globales, revertir aplicación y comprobar que no quedan NULL antes de restablecer NOT NULL. Tras crear globales/referencias, deshabilitar nuevas escrituras, mantener lectores compatibles y corregir hacia delante o restaurar una copia coherente. No reasignar compañía, borrar globales ni copiar recursos automáticamente para forzar downgrade.

DOCUMENTADO: ALTER TABLE y otros DDL provocan commits implícitos; una transaction Sequelize no ofrece rollback de toda la migración. Usar pasos idempotentes verificables y ensayo de interrupción por paso. MariaDB, implicit commit. No se ejecutó DDL ni se midió bloqueo.