240 lines
27 KiB
Markdown
240 lines
27 KiB
Markdown
# R2.1b — Persistencia y migración de Resources Global Scope
|
||
|
||
Fecha: 24 de octubre de 2026. Este encargo autoriza implementar R2.1b y ensayarla exclusivamente en MariaDB desechable. No autoriza migrar la base habitual, administrar productos globales ni activar R2.2–R2.6. Los resultados finales y gates se detallan en §14–16.
|
||
|
||
## 1. Estado inicial y contrato
|
||
|
||
HEAD al comenzar: `34b8f915381de02f9e13334634455074a5dc55ee`. Node 24.20.0 y PNPM 11.25.0, coincidente con packageManager. Sequelize 6.37.8. `compose.yaml` declara MariaDB 11.8. El árbol ya contenía R2.1a sin commit y numerosos cambios concurrentes de UI, búsquedas y documentación: se conservaron. El inventario inicial de rutas está en `.artifacts/resources-global-scope/r2-1b/git-status-before.txt`.
|
||
|
||
Se leyeron los once documentos normativos y se contrastaron agregado, contratos, SQL, migradores, revisiones, consumidores y código real de R2.1a. Las decisiones D01–D10/D04-R continúan ratificadas; no se reescriben. Esta entrega extiende [R2.1a](11-r2-1a-lectores-compatibles.md) sin sustituir su evidencia histórica.
|
||
|
||
Se utilizó **rdx-solid**, con referencias aplicables de arquitectura backend, legacy, contratos y pruebas. No hay una skill independiente de MariaDB/Sequelize instalada; no se instaló ninguna. Sus criterios se concretan en migración explícita, estados verificables, validación runtime, controles en la escritura y pruebas sobre MariaDB real. No hay cambios de UI ni de DTO HTTP, por lo que no se aplicaron skills visuales ni Playwright.
|
||
|
||
## 2. Preflight efectivo
|
||
|
||
El lanzador reutiliza el aislamiento del procedimiento visual: imagen fijada, credenciales aleatorias, puerto publicado únicamente en localhost, base `metis_desk_test`, contenedor exclusivo y limpieza al terminar. No carga `.env` locales ni reutiliza servicios habituales. La imagen es:
|
||
|
||
`mariadb:11.8@sha256:6422478cb8e159f080fb1d8ccf65101e26fe51385787fde7d16c3b165a331f15`.
|
||
|
||
Se verificó **11.8.9-MariaDB-ubu2404**, InnoDB, `foreign_key_checks=1`, `check_constraint_checks=1` y collation de base `utf8mb4_uca1400_ai_ci`. Los UUID de Resource usan realmente `CHAR(36)` con `utf8mb4_bin`; no se sustituyen por la collation de base. SQL mode observado: `IGNORE_SPACE,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION`.
|
||
|
||
El preflight captura SHOW CREATE, columnas/nulabilidad/defaults, índices, CHECK y FKs con orden y acciones; también incluye vistas SQL. Los ensayos independientes demostraron:
|
||
|
||
- Dos pares válidos y rechazo de pares contradictorios, scope NULL/desconocido/otra capitalización y literales con espacio/tabulador final.
|
||
- ALTER del propietario del padre a nullable conservando su FK y la FK compuesta entrante.
|
||
- Rechazo de referencia compuesta cruzada entre compañías con hijos NOT NULL.
|
||
- Un hijo nullable puede eludir la comprobación compuesta: esa variante se descarta para entidades operativas.
|
||
- `ascii_bin` distingue mayúsculas pero compara `global ` como igual a `global`: el CHECK final añade longitud exacta para cerrar ese caso.
|
||
|
||
La sintaxis y los límites se contrastaron con [CHECK/CONSTRAINT](https://mariadb.com/docs/server/reference/sql-statements/data-definition/constraint), [Foreign Keys](https://mariadb.com/docs/server/ha-and-performance/optimization-and-tuning/optimization-and-indexes/foreign-keys), [ALTER TABLE y WAIT](https://mariadb.com/docs/server/reference/sql-statements/data-definition/alter/alter-table) y [commits implícitos](https://mariadb.com/docs/server/reference/sql-statements/transactions/sql-statements-that-cause-an-implicit-commit). La evidencia de esta fase procede de los ensayos ejecutados, no solo de esas referencias.
|
||
|
||
## 3. Esquema anterior y posterior
|
||
|
||
| Elemento | Antes | Después de ejecutar la migración explícita |
|
||
| --- | --- | --- |
|
||
| resources.id | PK UUID estable | Sin cambios. |
|
||
| resources.companyId | CHAR(36), utf8mb4_bin, NOT NULL, FK companies.id RESTRICT | Mismo tipo/collation/FK y acciones; nullable en la raíz. |
|
||
| resources.scope | Ausente | VARCHAR(7), ascii/ascii_bin, NOT NULL, DEFAULT company. |
|
||
| resource_scope_owner | Ausente | Pares exactos company/UUID y global/NULL, con longitud 7/6. |
|
||
| resource_global_no_integration | Ausente | Un global no puede tener ningún campo de integración. |
|
||
| Historial y especificaciones | Tablas normalizadas existentes | Sin columnas, IDs ni datos nuevos por migración. |
|
||
| Compañía de entidades operativas | Obligatoria | Sin modificaciones. |
|
||
| FKs e índices existentes | Activos | Conservados íntegramente por la expansión. |
|
||
|
||
El discriminante pertenece a la raíz. El CHECK de propietario equivale a:
|
||
|
||
```sql
|
||
(scope = 'company' AND CHAR_LENGTH(scope) = 7 AND companyId IS NOT NULL)
|
||
OR
|
||
(scope = 'global' AND CHAR_LENGTH(scope) = 6 AND companyId IS NULL)
|
||
```
|
||
|
||
El CHECK de integración exige, para un global, NULL en `integrationKind`, `addressFieldId`, `contextFieldId`, `mailboxKind`, `accountId`, `referenceFieldId`, `resourceTypeId`, `systemId` y `environmentId`.
|
||
|
||
No se añaden UUID de plantilla al DDL. La FK templateId conserva existencia, no elegibilidad global: validar serie `template:application`, propietario/campos globales y autorización corresponde a R2.2 antes de habilitar altas en R2.3. Solo se sembró un global sintético válido de esa serie mediante SQL interno de prueba, con UUID e historial independientes.
|
||
|
||
## 4. Migración expansiva y backfill
|
||
|
||
`migrateResourceScope` no se invoca desde el arranque. El bootstrap existente de `resource.model.ts` conserva la creación legacy; no usa sync/alter automático. Una instalación nueva puede inicializar ese esquema y después ejecutar explícitamente la expansión. Una instalación existente no cambia físicamente de ámbito por desplegar este código.
|
||
|
||
Precondiciones del migrador: MariaDB 11.8, CHECK/FKs activos, compañía sin huérfanos, PK por id, FKs de compañía/plantilla y FKs compuestas operativas compatibles, todos los componentes de estas FKs de hijos NOT NULL y representación de columna/constraints reconocida. Una contradicción detiene el proceso; no captura errores para continuar ni elimina datos.
|
||
|
||
Pasos DDL independientes, cada uno con `WAIT 30` para acotar la espera de metadata locks:
|
||
|
||
1. Añadir scope NOT NULL DEFAULT company. MariaDB materializa su valor efectivo para todas las filas anteriores: backfill sin UPDATE de datos funcionales.
|
||
2. Añadir resource_scope_owner.
|
||
3. Añadir resource_global_no_integration.
|
||
4. Hacer nullable companyId conservando CHAR(36), charset, collation y FKs.
|
||
|
||
Se inspecciona el estado real y se omiten únicamente pasos ya presentes con definición reconocida. Un scope nullable, de longitud/collation/default distintos, un CHECK homónimo con otra expresión o una FK incompatible producen error explícito. Se admiten índices adicionales sin modificarlos.
|
||
|
||
No se promete atomicidad de cuatro DDL mediante transaction: MariaDB puede hacer commit implícito. La recuperación reanuda desde el estado comprobado. No se añade una tabla artificial de versiones; el proyecto usa migradores explícitos y metadatos de esquema como fuente de estado.
|
||
|
||
## 5. Ejecución operativa y arranque
|
||
|
||
Comandos reproducibles desde WSL, con configuración de conexión suministrada de forma segura al proceso:
|
||
|
||
```bash
|
||
# Siempre crea y retira su propia MariaDB de pruebas.
|
||
pnpm test:resource-scope
|
||
|
||
# Preflight aislado sin aplicar la expansión a Resource.
|
||
python3 scripts/resource-scope-test.py --preflight
|
||
|
||
# Ampliación de pruebas; incluye auditorías ajenas que pueden devolver error.
|
||
python3 scripts/resource-scope-test.py --regressions --monorepo
|
||
|
||
# CLI explícita: esta línea NO se ha ejecutado contra una base habitual.
|
||
pnpm db:resource-scope --apply --writes-paused
|
||
```
|
||
|
||
La CLI requiere ambos argumentos, pero esos argumentos no sustituyen una pausa operativa real ni un backup verificado. **Esta tarea solo la ejecutó con credenciales del contenedor desechable.** Para una instalación habitual siguen siendo necesarios autorización específica, preflight de su esquema/patch, backup y ventana de mantenimiento.
|
||
|
||
Procedimiento de despliegue: lectores R2.1a disponibles → detener todos los escritores y tareas → capturar/verificar copia coherente → ejecutar un único proceso de CLI → comprobar esquema/datos/FKs → reiniciar los procesos → verificar flujos locales. La versión de esquema se fija por instancia de lector; no se modifica mientras atiende peticiones. Se detectan legacy, scoped completo y parcial; un esquema parcial impide inicializar Resources antes de sembrar catálogos o registrar rutas. No se deduce la versión de una consulta fallida.
|
||
|
||
## 6. FKs, índices y consumidores
|
||
|
||
La expansión ensayada no necesita retirar/recrear FKs. Se conservan:
|
||
|
||
- resources.companyId → companies.id y templateId → resource_definitions.id.
|
||
- `directory_resource_scope_template(id,companyId,templateId)` y `resource_company_identity(companyId,id)`.
|
||
- `directory_resource`, `network_share_resource` y `support_resource_company`, con propietarios de hijos NOT NULL.
|
||
- Referencias simples actuales e históricas de software, licencias, aplicaciones, certificados y remoto.
|
||
- FKs de revisiones, actores, especificaciones, campos y características.
|
||
|
||
Las FKs compuestas siguen impidiendo que una proyección operativa local coincida con el propietario NULL de un global. Las FKs simples de software/perfil/remoto prueban existencia; por sí solas no validan compañía ni propósito. Se mantienen las políticas y el inventario local actuales: no se crea autorización global mediante DDL ni se ensanchan esas referencias.
|
||
|
||
La copia anterior al DDL contiene referencias reales de instalación/licencia, configuración de aplicación, certificado, asistencia, SMB y dominio/directorio. Se comparan todas las tablas antes/después, además de UUID/compañía/historial de Resource. Las verificaciones posteriores crean/editan datos locales y rechazan globales y referencias cruzadas en los flujos actuales.
|
||
|
||
No se añade un índice de scope para un endpoint inexistente. Se conservan los índices actuales y se guarda EXPLAIN. La consulta por compañía utiliza resource_company_identity en la muestra selectiva; la consulta por UUID utiliza PRIMARY; las revisiones disponen del índice por recurso/posición. La lectura interna por scope recorre la muestra: no se atribuye mejora porcentual ni se extrapola a escala de producción. El índice global recomendado en R2.0 se reconsiderará con las consultas reales de R2.2.
|
||
|
||
## 7. Sequelize, mappers y lectores
|
||
|
||
No se inventa un ResourceModel con asociaciones ORM: el inventario mantiene SQL y QueryInterface en Infrastructure.
|
||
|
||
`InventoryRow` admite propietario nullable y scope sin validar en la frontera SQL. El mapper valida el par y reconstruye UniqueID/historia. `readResourceOwnership` centraliza el único fallback legacy para un origen conocido. La entrada scoped exige scope explícito; ausencia, NULL o valores desconocidos no se convierten en company.
|
||
|
||
`ResourceScopeSchemaReader` inspecciona columnas y expresiones de CHECK, distingue estados y conserva el resultado por instancia. El módulo comprueba el esquema existente al arrancar; el repositorio fija su modo en la primera carga. La propagación comprueba una vez su modo por operación, no por recurso.
|
||
|
||
`toPersistence` conserva la salida legacy. `toScopedPersistence` añade company explícito y sigue rechazando globales. El repositorio elige el formato correspondiente al esquema. El escritor relacional acepta ambos formatos: solo emite la columna scope en el segundo. La compatibilidad de escritores anteriores se conserva mediante DEFAULT company y CHECK, nunca mediante inferencia de global a partir de NULL.
|
||
|
||
`readInventoryByIds` es un helper interno de Infrastructure para lectura por identidades y pruebas. Puede reconstruir un global del esquema completo; no se exporta en services ni se conecta a un endpoint. `get/list(company)` y los contratos HTTP mantienen únicamente company de esa compañía. Los dashboards excluyen raíces con propietario NULL en totales y filas.
|
||
|
||
## 8. Inmutabilidad y seguridad de escritura
|
||
|
||
La creación normal del agregado y las rutas existentes siguen produciendo company. No existen setters ni requests de edición de scope/owner. Antes del upsert, el escritor lee y bloquea la raíz por UUID dentro de la transacción y compara propietario, ámbito y plantilla persistidos. Un candidato que intente reutilizar un UUID de otra compañía/global o cambiar plantilla falla antes de escribir hijos o revisiones.
|
||
|
||
El CHECK verifica coherencia, no inmutabilidad OLD/NEW. Un operador SQL privilegiado puede modificar un recurso no referenciado si respeta las restricciones; esa frontera de confianza no se sustituye por un trigger nuevo. No se proporciona operación pública de cambio de ámbito ni down-migration automática.
|
||
|
||
Las proyecciones locales conservan el guard de R2.1a. Sin permisos globales, CRUD global, catálogo global ni consumidores mixtos. No se modifican Identity ni el modelo compartido de soporte.
|
||
|
||
## 9. Historial y propagación
|
||
|
||
Scope y propiedad se heredan de la raíz inmutable. La migración no añade revisiones ficticias ni cambia actores, timestamps, especificaciones, nombres o valores. La comparación incluye la raíz con dos revisiones y relaciones operativas con su propio historial anterior al DDL.
|
||
|
||
La propagación local utiliza el mapper correspondiente y conserva atomicidad/idempotencia. Detecta propietario NULL antes de llamar a UniqueID o de buscar una compañía: si una actualización de plantilla afectaría a un global, devuelve `RESOURCE_SCOPE_UNSUPPORTED` antes de persistir catálogo o recursos. Se prueba el rechazo por la ruta existente de catálogo con un global físico.
|
||
|
||
**Diferido deliberadamente:** propagación que escriba productos globales, autorización de cambios globales de plantilla y elegibilidad por stockKey/campos. Implementarlas ahora abriría una vía de edición global desde el catálogo actual sin el guard de R2.2. Esta fase prepara persistencia y lectura; todas las escrituras globales, incluidas las indirectas por plantilla, permanecen cerradas.
|
||
|
||
## 10. Recuperación y rollback ensayados
|
||
|
||
**A — Solo locales:** se verifica cero scope distinto de company y cero propietario NULL antes de revertir el esquema de prueba. El ensayo retira los dos CHECK y scope y restaura NOT NULL, conservando todas las FKs y los datos; vuelve a migrar y repite cada estado. Este procedimiento está en una fixture aislada, no en una CLI de downgrade para bases habituales.
|
||
|
||
**B — Existe un global:** el procedimiento de downgrade rechaza continuar y el ALTER directo a NOT NULL también falla. Una versión antigua puede fallar en lecturas/propagación aunque no haya vínculos globales; no basta con apagar altas. La recuperación ensayada restaura una copia coherente anterior del conjunto completo, descartando expresamente las operaciones sintéticas posteriores al backup. En una instalación habitual esa restauración exige autorizar la pérdida de cambios posteriores y el corte operativo; nunca asigna una compañía ficticia ni borra selectivamente el global para fingir compatibilidad.
|
||
|
||
La copia de ensayo se guarda con permisos privados fuera de Git, conservando tipos con serialización binaria de Node. Incluye tablas, datos, índices, CHECK, FKs y vistas; la restauración reconstruye el conjunto offline y valida nuevamente todas las FKs. No usa foreign_key_checks=0 ni desactiva CHECK. Los ficheros privados y el contenedor se retiran al acabar. No es una validación de mariadb-dump ni sustituye el backup operativo de una instalación habitual.
|
||
|
||
**C — Interrupción:** se ejecuta cada prefijo real de los cuatro DDL, se comprueba rechazo del servicio si el estado es parcial y se reanuda la migración. La repetición completa no aplica pasos adicionales. Tras cada caso se comparan datos y FKs. No se simula atomicidad ni se afirma haber ensayado pérdida de alimentación durante un ALTER.
|
||
|
||
Si un estado no coincide con las formas reconocidas, se mantiene el servicio detenido, se captura diagnóstico y se restaura una copia coherente o se prepara una reparación explícita. No se eliminan datos ni se ignoran errores para terminar.
|
||
|
||
## 11. Archivos de implementación
|
||
|
||
| Área | Archivos principales |
|
||
| --- | --- |
|
||
| Migración/inspección | `modules/resources/src/api/infrastructure/persistence/resource-scope-migration.ts`, `resource-scope-schema.ts`. |
|
||
| Composición | `modules/resources/src/api/module.ts`: comprobación temprana y export de la operación explícita, sin invocarla. |
|
||
| Persistencia/lectura | `resource-persistence.mapper.ts`, `resource-inventory-relational.ts`, `sequelize-resource.repository.ts`. |
|
||
| Proyecciones | `resource-dashboard-reader.ts`, `prepare-resource-template-updates.ts`. |
|
||
| CLI | `apps/server/src/migrate-resource-scope.ts`. |
|
||
| Ensayos | `apps/server/src/resource-scope.preflight.ts`, `resource-scope.integration.test.ts`, `resource-scope-backup.fixture.ts`, `resource-scope-http.fixture.ts`, `resource-scope-consumers.fixture.ts`. |
|
||
| Regresión | `modules/resources/src/api/infrastructure/persistence/resource-scope-reader.test.ts`; ajuste de columna de la fixture aislada de `apps/server/src/template-propagation.integration.test.ts`. |
|
||
| Lanzador | `scripts/resource-scope-test.py`, scripts de package.json y exclusión de artefactos en `.gitignore`. |
|
||
| Documentación | Este informe y precisión del gate G2 en [07](07-plan-despliegue.md). |
|
||
|
||
Los archivos de persistencia sin ruta completa en la tabla pertenecen al mismo directorio de Infrastructure de Resources. No se cambian implementaciones de Companies/Assets, vendor, archive, UI, dependencias, permisos ni los doce documentos históricos de auditoría.
|
||
|
||
## 12. Evidencias conservadas
|
||
|
||
Directorio local excluido de Git: `.artifacts/resources-global-scope/r2-1b/`.
|
||
|
||
- `preflight-results.json`, `preflight.log`: versión efectiva, CHECK/FKs y ensayo independiente.
|
||
- `schema-before.json`, `schema-after.json`: SHOW CREATE, columnas, nulabilidad/defaults, índices, FKs y CHECK.
|
||
- `recovery.json`: prefijo interrumpido, pasos reanudados y preservación observada.
|
||
- `data-preservation.json`: recuentos y SHA-256 antes/después por tabla, incluidos UUID, propietarios, valores y revisiones; no almacena los valores de las filas. Para Resource se excluye únicamente el scope añadido del digest y se comprueba por separado que cada fila sea company.
|
||
- `integration.log`, `integration-results.json`: casos ejecutados y verificaciones efectivamente alcanzadas.
|
||
- `explain.json`: planes obtenidos, sin porcentajes inventados.
|
||
- `migration-cli.log`, `regressions.log`, `legacy-server.log`, `monorepo-tests.log`, `commands.json`: resultados separados por alcance.
|
||
- `git-status-before.txt`, `baseline.json` y registros dirigidos de calidad: estado y validación de esta entrega.
|
||
|
||
No contienen contraseñas de conexión ni sesiones. La copia privada usada para restaurar no se conserva entre ejecuciones ni se versiona. Los artefactos de pruebas son evidencia local, no datos de clientes ni una migración aplicada fuera del contenedor.
|
||
|
||
## 13. Volumen y comportamiento comprobado
|
||
|
||
La instalación de pruebas utiliza 15 compañías, 64 recursos particulares antes de migrar y una principal con 50 personas, 50 activos, 50 perfiles, 50 puestos y 50 recursos de varias familias. Las otras 14 compañías aportan recursos de aislamiento. Se añaden referencias operativas antes del DDL y se contrastan sus tablas e historias al reanudar. Se crea un único global sintético después de la expansión; no se convierte una raíz local.
|
||
|
||
Los casos SQL rechazan pares incoherentes, scope NULL/desconocido/mayúsculas/espacio/tabulador e integración global. Se detecta CHECK desactivado antes de DDL y se rechaza un CHECK homónimo falsificado. HTTP verifica aislamiento local, alta y estado, DTO exacto, software/licencia/perfil/soporte, referencias ajenas/globales y propagación global cerrada. Las regresiones adicionales cubren directorio, Access/SMB, publicaciones y relaciones tipadas sobre el esquema expandido.
|
||
|
||
## 14. Resultados de validación
|
||
|
||
| Comando / ámbito ejecutado | Resultado |
|
||
| --- | --- |
|
||
| `python3 scripts/resource-scope-test.py --preflight` | Correcto: MariaDB 11.8.9, CHECK/FKs activos, pares válidos e inválidos, FK compuesta y padding comprobados. |
|
||
| `python3 scripts/resource-scope-test.py --regressions` | Correcto: **13/13 pruebas de R2.1b** y **50/50 regresiones HTTP/MariaDB**, cero omitidas; CLI de expansión aplicada al contenedor antes de las regresiones. Código de salida 0. |
|
||
| `pnpm --filter @metis/resources test` | **89/89 correctas**, cero omitidas. |
|
||
| `pnpm --filter @metis/resources typecheck` | Correcto. |
|
||
| `pnpm --filter @metis/server typecheck` | Correcto. |
|
||
| `pnpm -r --workspace-concurrency=3 --filter='!@repo/shadcn-ui' typecheck` | Correcto en los 11 paquetes con script, incluidos consumidores y web. |
|
||
| `pnpm exec biome check` sobre los 17 archivos TypeScript/package de la fase | Correcto, sin errores ni avisos. |
|
||
| `pnpm exec eslint` sobre los 16 archivos TypeScript de la fase | Correcto, incluido espaciado. |
|
||
| `python3 -m py_compile scripts/resource-scope-test.py` | Correcto. |
|
||
| `git diff --check` sobre archivos de la fase | Correcto. |
|
||
| `pnpm build` | Correcto, servidor y web; aviso de bundle web mayor de 500 kB. |
|
||
| `pnpm typecheck` global | Falla en vendor: calendar.tsx:165, TS2375 relativo a locale opcional de DayPicker. Vendor permanece intacto. |
|
||
| `pnpm lint` global | 15 errores, 1 warning y 4 infos ajenos al diff; entre ellos person-photo, process y assets de skills. |
|
||
| `pnpm check` global | 169 errores, 1 warning y 4 infos en la ejecución de cierre; incluye formato ajeno. No se aplica formato global. |
|
||
| `python3 scripts/resource-scope-test.py --regressions --monorepo` | Verificación ampliada: la ejecución propia de R2.1b y las regresiones pasan; la auditoría opcional devuelve error por las dos filas siguientes. |
|
||
| `pnpm exec turbo run test --filter=!@metis/server` dentro del lanzador aislado | Falla en los dos tests de render de companies-page.test.tsx ya documentados en R2.1a; Companies pasa 238/240 en esta ejecución. Los tests de Resources pasan; no se presenta como validación global completa de todas las apps. |
|
||
| `node --import tsx --test --test-isolation=none apps/server/src/identity.test.ts` dentro del contenedor de pruebas | Falla en identity.test.ts:82: esperaba 404 y obtiene 401 en una comprobación inicial de rutas archivadas. No llega a ejecutar su recorrido completo. Archivo de prueba y composition root de Identity intactos respecto al baseline. |
|
||
|
||
El script global `pnpm test` no se ejecutó literalmente: su script de servidor carga `.env`. Se separaron tests de paquetes y suites de servidor con las credenciales explícitas del contenedor, sin cargar esa configuración. La suite dirigida de persistencia/regresiones es independiente y devuelve 0; la ampliación opcional conserva visibles los fallos ajenos.
|
||
|
||
La incidencia nueva de la fixture de propagación se corrigió haciendo que su columna Resource.companyId reproduzca CHAR(36) utf8mb4_bin NOT NULL del esquema legacy real. Las primeras incidencias del lanzador/fixtures (vistas en backup, puestos necesarios para perfiles, argumentos CSRF/origen y contrato 409 TOOL_UNAVAILABLE) se corrigieron en la infraestructura de prueba; los resultados anteriores corresponden a las ejecuciones finales. Se evitó reutilizar la simulación de conversión de herramientas de soporte sobre conexiones actuales: el ensayo usa la API activa y conserva su referencia anterior al DDL.
|
||
|
||
Los fallos globales de vendor/lint/check y las dos assertions de Companies se volvieron a comprobar; no se asume que sus recuentos sean los mismos de R2.1a. El fallo de la suite histórica de Identity se clasifica como ajeno al diff, con antigüedad no comprobada mediante replay del código anterior. No se ha corregido ni ocultado. El recorrido HTTP amplio de esa suite, incluido cualquier caso posterior a su assertion inicial, no se declara ejecutado; se informa por separado de las 50 regresiones que sí pasaron.
|
||
|
||
La evidencia de preservación abarca **128 tablas/vistas**, con SHA-256 antes/después iguales en las 128. Resource conserva **64 filas** y sus revisiones **65 filas**. Instalación, revisión de instalación, configuración de aplicación, revisión de configuración, soporte y revisión de soporte conservan sus filas previas; SMB y directorio conservan sus proyecciones. Las FKs (398 componentes) y los índices (606 componentes) de los metadatos antes/después coinciden. Estas cifras pertenecen al esquema sintético ensayado.
|
||
|
||
|
||
## 15. Gates de aceptación
|
||
|
||
| Gate | Estado y evidencia |
|
||
| --- | --- |
|
||
| G1 — Integridad física | Cumplido en la instalación desechable: scope persistente exacto, owner nullable solo en Resource, CHECK efectivo, PK/FKs/índices conservados y guard ante FK compuesta ausente. |
|
||
| G2 — Preservación | Cumplido en la muestra: 64 Resource siguen company, mismos UUID/owners, 65 revisiones intactas, huellas de todas las tablas iguales. Ninguna base habitual fue utilizada. |
|
||
| G3 — Compatibilidad operativa | Cumplido para el cambio: CRUD/DTO local, software/licencia/perfil, certificado previo y sus datos, soporte, Access/SMB, dominio, publicaciones y referencias de compañía; 50 regresiones pasan. La suite amplia de Identity tiene un fallo ajeno inicial y no amplía esta evidencia a sus casos no alcanzados. |
|
||
| G4 — Migración segura | Cumplido en test: instalación vacía, datos existentes, cuatro interrupciones/reanudaciones, repetición, esquema/CHECK/FK incompatibles, reversión sin global y restauración completa con global. |
|
||
| G5 — Validación | Cumplido para R2.1b: SQL real y regresiones dirigidas correctos, typecheck afectado, calidad dirigida y build correctos. El monorepo global conserva los fallos ajenos descritos. |
|
||
| G6 — Preparación de R2.2 | Cumplido: ambos ámbitos representables físicamente y en lectura interna; administración, propagación escrita global y consumidores globales siguen cerrados. |
|
||
|
||
**R2.1b queda implementada y ensayada en el entorno desechable.** No equivale a una migración aplicada a la instalación habitual ni a autorización para R2.2. Los gates operativos de backup real, pausa y aplicación a otra instalación deben verificarse cuando exista un encargo que la autorice.
|
||
|
||
|
||
## 16. Riesgos y prerrequisitos de R2.2
|
||
|
||
No se ha aplicado DDL a una base habitual ni se ha desplegado una release. Un entorno habitual puede tener patch, FKs, collation, volumen o modificaciones diferentes: requiere su propio preflight y ventana autorizada. Las esperas de metadata lock están acotadas; duración de copia/DDL, rendimiento a escala, corte operacional y fallos de almacenamiento no se extrapolan desde esta muestra.
|
||
|
||
R2.2 debe incorporar autorización administrativa, elegibilidad de serie/campos, propagación global protegida, resolución por propósito en la transacción del consumidor y proyecciones mínimas. Las FKs simples no sustituyen esa política. No debe usarse el helper interno de lectura como servicio público ni la proyección completa de R2.1a como catálogo autenticado.
|
||
|
||
Antes del primer global real se exigen todos los lectores y tareas compatibles, recuperación operativa probada y gates de seguridad. El primer global ya impide un downgrade trivial. R2.3 habilitará catálogo; R2.4/R2.5 consumidores; ninguna de esas capacidades está activada aquí.
|