21 KiB
R2.1a — Preparación de lectores compatibles
Fecha: 24 de octubre de 2026. Implementación limitada a representación, lectura compatible y protección de los flujos locales. No se ha desplegado ni ejecutado DDL. Los resultados de validación figuran al final; la integración MariaDB y el ensayo posterior al DDL siguen pendientes.
1. Ratificación R2.0 y autorización
El usuario ratifica D01–D10 y D04-R en los términos recomendados en 02. Se selecciona la variante A: un único Resource, ámbito explícito company | global, propietario UUID obligatorio para company y null para global. El ámbito y la propiedad permanecen inmutables en la edición ordinaria.
| Decisión | Contrato ratificado; ejecución según su fase |
|---|---|
| D01 | Resource único; scope explícito; invariantes del par ámbito/propiedad. |
| D02 | Elegibilidad inicial exclusivamente serie stockKey=template:application, con campos globales y sin integración; ni el nombre ni usage=application conceden elegibilidad. |
| D03 | Administración global exclusivamente por el administrador inicial en la primera versión; sin delegación nueva. |
| D04 | Se mantiene el soporte compartido actual; no se añade ACL operador–compañía. |
| D04-R | Resolución por propósito; el inventario local no se ensancha para servir a consumidores futuros. |
| D05 | Proyecciones públicas mínimas separadas de la administración, notas e historial privados. |
| D06 | Actividad y ciclo de vida conservando referencias históricas; sin disponibilidad local nueva. |
| D07 | UUID estables, coexistencia de productos locales/globales y política de duplicados/edición recomendada. |
| D08 | Sin consolidación automática ni reescritura de relaciones/historia; sucesión con mutación diferida. |
| D09 | Superficies y DTO explícitos; no ensanchar silenciosamente el contrato legacy. |
| D10 | Lectores compatibles antes del DDL, despliegue progresivo y rollback compatible con los datos. |
La autorización de ejecución comprende solo R2.1a. R2.1b–R2.6 necesitan un encargo posterior. Las licencias y las entidades operativas siguen siendo locales. Quedan excluidos DDL, migraciones ejecutadas, recursos globales persistidos, altas/ediciones/asociaciones globales, nuevos permisos/endpoints, selectores mixtos, consolidación, cambios de UI y dependencias. No se consideran implementados D02/D03/D05/D09 para globales por disponer de tipos de lectura.
Los diez documentos R2.0 reciben únicamente una nota de ratificación con referencia aquí. Se preservan sus decisiones, estados originales como evidencia y referencias de la inspección inicial. Los doce documentos de auditoría no se modifican.
2. Inspección, reglas y skills
Se consultaron los diez documentos R2.0, las normas de arquitectura del repositorio y los puntos de inspección exigidos: agregado, DTO, servicio de inventario, mapper, compatibilidad, lector relacional, modelo, casos de uso, rutas y propagación. Se comprobó el estado Git previo y se conservaron los cambios concurrentes de UI, búsquedas, documentación y scripts.
Se consultaron .agents/skills/, skills-lock.json y rdx-solid, incluidas referencias aplicables de arquitectura, contratos compartidos, legacy y pruebas. Sus criterios condujeron a una unión discriminada validada, proyecciones explícitas, un único adaptador legacy y pruebas de comportamiento. No se instalaron skills ni dependencias. No se aplicaron skills visuales: no se modificó frontend ni el DTO que consume. Node 24.20.0 y PNPM 11.25.0, coincidente con packageManager.
3. Implementación y archivos
Las rutas siguientes son relativas a modules/resources/.
| Archivos | Responsabilidad |
|---|---|
src/api/domain/resource-scope.ts |
Schemas de ámbito/propiedad y tipos inferidos. |
src/api/domain/resource.aggregate.ts |
Propiedad discriminada, validación runtime, pertenencia y guard local; creación solo company. |
src/common/inventory.ts |
Añade contrato scoped separado y exporta tipos; mantiene ResourceSchema local. |
src/api/infrastructure/persistence/resource-persistence.mapper.ts |
Adaptador legacy conocido y entrada scoped estricta; misma salida de persistencia local. |
src/api/application/resource-response.builder.ts |
Proyección local existente y nueva proyección scoped interna, con serialización común del contenido/historial. |
src/api/application/resource.use-cases.ts, resource-inventory.service.ts |
Lectura y edición restringidas a recursos company de la compañía solicitada. |
src/api/infrastructure/persistence/sequelize-resource.repository.ts |
Comprueba pertenencia al cargar y antes de escribir el resultado del callback. |
src/api/infrastructure/persistence/prepare-resource-template-updates.ts |
Valida UUID antes de buscar compañía y exige propiedad local al propagar. |
src/api/infrastructure/persistence/resource-compatibility.ts, directory-domain-projection.ts, network-share-projection.ts, remove-resource.ts; src/api/application/network-share-diagnostics.ts |
Exigen compañía local antes de utilizar su identidad. Las proyecciones de escritura rechazan globales incluso sin integración. |
src/api/infrastructure/persistence/migrate-legacy-resources.ts |
Adaptación mecánica de la llamada existente a rehydrate: declara company. No cambia SQL ni se ejecuta el importador. |
src/api/domain/resource-scope.fixture.ts, resource-scope.test.ts |
Fixtures compartidas de prueba e invariantes runtime del dominio/DTO. |
src/api/infrastructure/persistence/resource-scope-reader.test.ts |
Lectura, roundtrip e impedimento de escritura/proyección global. |
src/api/application/resource-scope.test.ts |
Lectores locales y recorrido HTTP real con repositorio en memoria. |
package.json |
Incorpora las tres suites nuevas al comando test existente. |
resource.model.ts, resource-inventory-relational.ts, las sentencias SQL y las rutas permanecen sin cambios. En apps/server/src/network-shares.integration.test.ts se adapta una llamada de prueba al guard requireCompanyId; no se ejecuta esa suite persistente. No se cambian implementaciones de módulos consumidores, vendor ni archive.
4. Tipos e invariantes
ResourceScopeSchema y ResourceOwnershipSchema son schemas puros Zod. ResourceScope y ResourceOwnership se infieren de ellos. El agregado adapta la identidad UUID a UniqueID mediante una unión discriminada de sus propiedades:
- company implica
UniqueIDobligatorio. - global implica
nullobligatorio. - Scope desconocido, owner inválido/ausente y pares contradictorios fallan con
INVALID_RESOURCE_SCOPE.
La reconstrucción moderna del agregado exige scope. La validación runtime también comprueba que el propietario de dominio sea un UniqueID, no un string que el tipado haya dejado pasar. create solo construye company y devuelve Result de fallo ante una compañía inválida. No hay creación global ni setters de scope/owner.
belongsToCompany exige company y compara identidades. requireCompanyId devuelve UniqueID o falla con RESOURCE_SCOPE_UNSUPPORTED; evita que null se convierta en argumento de operaciones locales. Actualización y sincronización de especificación rechazan globales en esta fase.
5. Compatibilidad legacy y mappers
ResourcePersistenceMapper.toDomain es la entrada de la tabla legacy conocida. Solo ella interpreta ausencia material de scope como company. Exige UUID de propietario y valida cualquier scope explícito. Una propiedad presente con undefined, un ámbito desconocido o un owner vacío nunca activan el fallback.
toScopedDomain exige una representación moderna ScopedInventoryRow, derivada del contrato de propiedad. No admite ausencia de scope. Puede reconstruir global/null para pruebas y lectores futuros; no se conecta a un endpoint ni a una nueva consulta SQL.
Ambas entradas comparten la reconstrucción existente de valores, especificación y revisiones. La validación del par se realiza antes de reconstruir contenido. El lector relacional existente hace SELECT * filtrado por compañía y conserva los atributos de la fila al proyectarla: si recibe un scope explícito lo valida el mapper; hoy no necesita seleccionar ninguna columna nueva. No se añade detección de esquema por flag ni se presupone una estrategia SQL futura.
toPersistence exige propietario local y emite exactamente los campos anteriores: ID, compañía, plantilla, especificación, valores y revisiones. No emite scope, no añade columnas a INSERT/UPDATE y rechaza globales antes de llegar al escritor SQL.
6. DTO y proyecciones
ResourceSchema, ResourceDTO, InventoryResponseSchema, ResourceResponseSchema y los requests HTTP conservan su contrato local estricto sin scope. No cambia la nulabilidad de SoftwareInstallation, ApplicationConfiguration, WorkProfile, Asset ni licencia.
Se añade ScopedResourceSchema/ScopedResourceDTO como representación completa preparada para lectura interna y pruebas. Su discriminante obliga al par correcto. buildScopedResourceResponse no tiene ruta pública; no es el futuro DTO mínimo de producto ni implementa autorización administrativa. No deben reutilizarse su historial o notas para futuros candidatos públicos: D05 sigue pendiente de implementación en su fase.
buildResourceResponse exige compañía y conserva los mismos valores y campos del DTO anterior. Los intentos de enviar scope/owner en el request o dentro de values son rechazados por los schemas estrictos existentes.
7. Lectores, seguridad y consumidores
ResourceUseCases.get y IResourceInventoryService.list(company) conservan únicamente recursos company del propietario solicitado, incluso si un repositorio entrega objetos globales o de otra compañía. La forma del servicio, clasificación de uso, estado, nombre y caducidad no cambian.
Edición y estado buscan por UUID y pertenencia local. El contexto de guardado se filtra antes de validar nombres/referencias. El repositorio Sequelize conserva las consultas por compañía y añade comprobaciones de pertenencia al cargar y antes de persistir el resultado del callback; eliminación utiliza ese mismo contexto local. No se introduce consulta de detalle global ni bypass por UUID.
SoftwareInstallation, ApplicationConfiguration, Access, SMB y RemoteConnection siguen consumiendo sus contratos locales. Los globales sintéticos no aparecen en el servicio compartido, y las proyecciones locales fallan antes de SQL. No se introducen candidatos mixtos, políticas de autorización nuevas ni acceso entre compañías. Las referencias de Access mantienen su semántica previa de catálogo, independiente del scope de Resource.
La prueba de clasificación usa una plantilla de serie con nombre modificado y stockKey template:application; continúa clasificándose como aplicación. No se concede elegibilidad global por nombre, por uso de plantilla personalizada ni a otras plantillas: todas las escrituras globales siguen cerradas.
8. Historia y propagación
Se conservan UUID de raíz y revisiones, actores, timestamps, valores y especificaciones históricas. Interpretar una raíz legacy como company no añade revisiones ni modifica snapshots. El ámbito/propietario inmutables se heredan de la raíz; no se añade scope a tablas de historial ni se infiere global a partir de una ausencia.
La propagación comprueba el UUID de compañía antes del lock/lectura y la pertenencia de cada Resource antes de sincronizar. El catálogo global de plantillas sigue produciendo recursos locales mediante create. Ni una actualización de plantilla ni un owner nulo pueden crear o convertir un recurso global. La sincronización global está cerrada en el agregado durante R2.1a.
9. Pruebas y validación
Los resultados finales y comandos se registran en la tabla de cierre de esta sección. Las pruebas nuevas ejecutan validación runtime; no dependen solo del compilador.
Cobertura dirigida: legacy sin scope; company explícito; global/null; scope moderno ausente, desconocido o undefined; pares contradictorios; owner inválido/ausente; roundtrip de dos revisiones; proyección scoped y DTO local; inmutabilidad; rechazo de escritura global; filtrado local; plantilla identificada por stockKey; proyecciones operativas sin acceso SQL.
La prueba HTTP utiliza Express y las rutas reales, con autenticación sintética y repositorio en memoria. Usa 15 compañías: 50 recursos de la principal, uno de cada una de las otras 14 y un global sintético. Verifica las 15 lecturas, exclusión del global, UUID ajenos/globales rechazados en edición/estado, requests con scope rechazados, alta local, edición, desactivación/activación, idempotencia e historial. No crea personas ni activos: ese volumen y sus recorridos pertenecen a la integración persistente/visual pendiente. Los recursos se crean con identidades/revisiones independientes, nombres distintos por compañía, tildes y estados variados; el catálogo y los actores son fixtures en memoria, sin seed MariaDB.
La prueba de proyecciones usa una instancia Sequelize cerrada sin conexión ni transacción iniciada: el rechazo esperado de globales ocurre antes de cualquier acceso SQL. No se ha abierto una conexión a MariaDB ni ejecutado DDL.
La primera ejecución HTTP en sandbox falló con listen EPERM; la ejecución autorizada fuera del sandbox pasó. No se ha desactivado ni omitido la prueba para sortear esa restricción.
| Comando / comprobación | Resultado observado |
|---|---|
pnpm --filter @metis/resources typecheck |
Correcto. |
pnpm --filter @metis/server typecheck |
Correcto tras adaptar la llamada de prueba SMB a requireCompanyId. |
pnpm --filter @metis/web typecheck |
Correcto. |
pnpm -r --workspace-concurrency=3 --filter='!@repo/shadcn-ui' typecheck |
Correcto en los 11 paquetes con script, incluidos Resources, Companies, Assets, servidor y web. |
pnpm --filter @metis/resources test |
88 pruebas correctas, 0 fallos, 0 omitidas; incluye ocho pruebas nuevas entre las tres suites de R2.1a. HTTP con 15 compañías, 50 recursos locales y global sintético. |
Biome: pnpm exec biome check sobre los 21 archivos de código/package enumerados en §3 |
Correcto, sin errores, avisos ni modificaciones pendientes. |
ESLint: pnpm exec eslint sobre los 20 archivos TypeScript de §3 |
Correcto; reglas de espaciado incluidas. |
git diff --check sobre los archivos de la fase |
Correcto. |
pnpm build |
Correcto, servidor y web; aviso de bundle web mayor de 500 kB. |
pnpm typecheck |
Falla en vendor: packages/shadcn-ui/src/components/calendar.tsx:165, TS2375 sobre locale opcional de DayPicker con exactOptionalPropertyTypes. Archivo intacto respecto al baseline. No se modifica vendor. |
pnpm lint |
Falla: 15 errores, 1 warning y 4 infos en archivos ajenos al diff, entre ellos person-photo, process y assets de la skill shadcn. |
pnpm check |
Falla: 168 errores, 1 warning y 4 infos; añade problemas de formato, incluidos archivos de skills. Sin corrección fuera del alcance. |
pnpm -r --workspace-concurrency=2 --filter='!@metis/server' test |
Pasa en Relationship Contracts (13), rdx-ddd (3), Core (11), rdx-ui (78), Identity (10), Resources (87 en esa ejecución, antes de añadir la prueba de proyecciones) y Assets (144). Companies: 228/230 pasan; falla en dos assertions de render de companies-page.test.tsx. Se detiene antes de web; se ejecuta web aparte. |
pnpm --filter @metis/web test |
Correcto en los tres archivos del script; la ejecución aislada reporta archivos, no número de casos internos. |
Servidor: node --import tsx --test --test-isolation=none src/app.test.ts src/infrastructure.http.test.ts src/config/env-schema.test.ts src/infrastructure/http/http-error-diagnostic.test.ts src/infrastructure/http/relationship-read-access.test.ts desde apps/server |
Ocho pruebas correctas, sin fixtures MariaDB ni migraciones. |
pnpm test global / integración MariaDB |
No ejecutados: el script completo del servidor ejecuta preparación de esquema/migraciones. |
Los fallos de Companies comprueban los textos «Personas (1)» y «Esta persona no tiene acceso a Metis Desk», en modules/companies/src/web/companies/shared/companies-page.test.tsx:180 y :210. Ese archivo y las implementaciones de person-photo señaladas por lint permanecen intactos respecto al baseline. Se clasifican como ajenos al diff; no se ejecutó una reproducción completa del baseline anterior para atribuirles antigüedad. Hay cambios concurrentes de UI en el árbol de trabajo y no se revierten ni se ajustan estas pruebas en R2.1a.
Durante el desarrollo se corrigieron dos incidencias nuevas de tipos: la firma genérica de Result.ok y la llamada SMB antes indicada. La primera limitación HTTP EPERM se resolvió mediante la ejecución autorizada con puerto local. Los resultados dirigidos de la tabla corresponden a la versión corregida. No se declara el monorepo globalmente en verde.
10. Límites y problemas encontrados
No se ejecutó pnpm test global completo: el script de @metis/server incluye fixtures de integración que crean esquema y ejecutan migraciones; sería incompatible con la prohibición de DDL de este encargo. Se ejecutan las suites unitarias disponibles por separado sin esa fase.
No se valida contra MariaDB el recorrido de instalaciones/configuraciones existentes ni el despliegue sobre una instalación real. La revisión de SQL/modelos sin cambios, typecheck de consumidores, pruebas de dominio/render y el HTTP con repositorio en memoria aportan evidencia complementaria; no sustituyen la integración persistente. No se inició la aplicación completa, cuyo arranque incluye inicialización de persistencia.
No se realizó Playwright: no hay cambios de UI ni de contrato HTTP local; el procedimiento visual prepara servicios/esquema desechables. No se han tocado datos reales ni capturado sesiones. No hay nuevas dependencias, permisos ni flags.
11. Dependencias exactas para R2.1b y fases posteriores
| Punto | Trabajo posterior no ejecutado |
|---|---|
| MariaDB | Preflight de versión/modos, SHOW CREATE, índices/FKs, checks habilitados, backup/restauración y ensayo aislado. DDL explícito, backfill company, nulabilidad de owner solo en raíz, CHECK del par y conservación de UUID/relaciones. |
| Modelos Sequelize | Evolucionar el modelo/instalador de esquema cuando el DDL esté autorizado; no basta con este cambio de dominio. |
| Lectores SQL | Ensayar estrategia de detección/despliegue dual, validar selección explícita de scope tras expansión y tipos de filas nullable; mantener predicados locales. Inventarios por propósito siguen posteriores. |
| Mapper | Usar toScopedDomain en lectores modernos verificados; mantener el adaptador legacy solo donde el origen sin scope esté demostrado; no aceptar payload moderno incompleto. El escritor actual seguirá rechazando globales hasta su fase autorizada. |
| Constraints y secundarios | Conservar y ensayar claves compuestas/FKs locales e índices; verificar que todos los hijos operativos sigan exigiendo compañía. |
| DTO | HTTP local intacto; DTO públicos mínimos/admin y versionado por propósito en R2.2–R2.5. No exponer la proyección completa preparada como catálogo público. |
| Historial | No backfill de scope en snapshots; herencia de raíz inmutable. Ensayar lectura histórica con nuevo esquema y mismos UUID/valores. |
| Propagación | Separar enumeración/locks y tratamiento de raíces locales/globales cuando SQL permita owner null; validar serie/campos globales y contexto sin pasar null como compañía. Ensayar atomicidad y orden de locks. |
| Seguridad/catálogo/consumidores | D02/D03/D04-R/D05, altas globales idempotentes, permisos y contratos por propósito en R2.2/R2.3; software/perfiles en R2.4/R2.5, coexistencia en R2.6. |
Aclaración de alcance respecto a R2.0: el lector dual por versión/esquema, flags y guards de elegibilidad/autorización descritos en los planes son objetivos del despliegue progresivo, no capacidades activadas aquí. El encargo R2.1a exige compatibilidad con el esquema actual y permite diferir ajustes SQL a R2.1b sin fijar su estrategia. Se han implementado dos entradas de mapper y validación de scope explícito cuando está presente, manteniendo el SQL actual. No debe darse por ensayado el cambio de esquema ni habilitar globales basándose en estas pruebas.
12. Riesgos residuales y cierre de alcance
R2.1a prepara representación y conserva el uso local. No se ha probado el DDL, ni la lectura de globales reales, ni su autorización, elegibilidad, propagación persistente o uso por entidades operativas. Se requieren ensayos MariaDB aislados y encargo posterior para cada puerta de despliegue. No hay conversión automática, fusión ni migración de referencias. Los fallos globales ajenos que figuran en validación permanecen sin corregir para respetar el alcance.