diff --git a/reference/core/control-center/evidence/gap-closure-evidence.json b/reference/core/control-center/evidence/gap-closure-evidence.json index 932950a2..9c72117d 100644 --- a/reference/core/control-center/evidence/gap-closure-evidence.json +++ b/reference/core/control-center/evidence/gap-closure-evidence.json @@ -8428,6 +8428,23 @@ ], "dependencyDisposition": "none" }, + { + "id": "GT-602", + "closedAt": "2026-07-30", + "closureCommit": "086111db", + "evidence": [ + "src/packages/mcp-server/src/mcp/abac-rego-parity.spec.ts", + ".harness/scripts/compile-opa-wasm.mjs", + ".harness/scripts/generate-abac-tool-sets.mjs", + "src/rulesets/opa/abac-mcp-tool-access.rego", + ".github/workflows/ci-cd.yml" + ], + "validationCommands": [ + "npm --workspace @beyondnet/evolith-mcp test", + "node .harness/scripts/ci/08-validate-tracking.mjs" + ], + "dependencyDisposition": "none" + }, { "id": "GT-607", "evidence": [ diff --git a/reference/core/control-center/gaps/gap-reference-catalog.es.md b/reference/core/control-center/gaps/gap-reference-catalog.es.md index 8d0aa4bd..07e6e110 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -7252,9 +7252,9 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre - **Componente:** `Evolith MCP` · **Criticidad:** P0 · **Complejidad:** M - **Procedencia:** Evaluación del código componente a componente realizada el 2026-07-26 en el repositorio compañero `why-architecture` (`docs/evolith-diagnostico-es.md`), verificada contra el código de este repositorio antes de registrarse. - **Criterios de aceptación:** - - [ ] Un test evalúa el `policy.wasm` compilado sobre todos los nombres registrados y exige ALLOW para un `architect` en `production`. + - [x] Un test evalúa el `policy.wasm` compilado sobre todos los nombres registrados y exige ALLOW para un `architect` en `production`. - [x] Los conjuntos de herramientas del rego se generan desde el registro en vez de mantenerse a mano. - - [ ] CI falla cuando una herramienta existe en el registro TypeScript y no en la política compilada. + - [x] CI falla cuando una herramienta existe en el registro TypeScript y no en la política compilada. #### GT-603 diff --git a/reference/core/control-center/gaps/gap-reference-catalog.md b/reference/core/control-center/gaps/gap-reference-catalog.md index 2384d10f..2831f548 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -7347,9 +7347,9 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo - **Component:** `Evolith MCP` · **Criticality:** P0 · **Complexity:** M - **Provenance:** Component-by-component source assessment conducted 2026-07-26 in the companion `why-architecture` repository (`docs/evolith-assessment-en.md`), verified against this repository's code before registration. - **Acceptance criteria:** - - [ ] A test evaluates the compiled `policy.wasm` over all registered tool names and asserts ALLOW for an `architect` in `production`. + - [x] A test evaluates the compiled `policy.wasm` over all registered tool names and asserts ALLOW for an `architect` in `production`. - [x] The rego tool sets are generated from the tool registry rather than hand-maintained. - - [ ] CI fails when a tool exists in the TypeScript registry and not in the compiled policy. + - [x] CI fails when a tool exists in the TypeScript registry and not in the compiled policy. #### GT-603 diff --git a/reference/core/control-center/gaps/gap-tracking.es.md b/reference/core/control-center/gaps/gap-tracking.es.md index bec64aab..1695410e 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -11,654 +11,654 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida > Una sola tabla con todos los gaps y actividades rastreadas. Los IDs `GT-*` enlazan a su detalle completo en el catálogo; los IDs `MT-A*` enlazan al plan de implementación Multi-Topology de apoyo, pero esta tabla sigue siendo la fuente canónica de estado. Orden: pendientes primero (por criticidad P0→P3, luego complejidad XS→XL), después completados (mismo criterio). GitHub renderiza Markdown de forma estática (sin orden ni búsqueda interactivos): la columna **Componente** categoriza y la búsqueda de archivo de GitHub (`/`) encuentra un ID o término. -| ID | Gap | Qué significa | Ejemplo | Componente | Fase | Criticidad | Complejidad | Estado | -|---|---|---|---|:---:|:---:|:---:|:---:|:---:| -| [`GT-643`](./gap-reference-catalog.es.md#gt-643) | **`evolith agents install --dry-run` escribe en disco — la bandera está declarada, documentada y nunca se lee.** `agents.command.ts:647` declara `-d, --dry-run`, `AgentsCommandOptions.dryRun` la tipa, y `installAgent()` no la consulta jamás: llama a `this.registry.installAgent(process.cwd(), config, rulesetContent)` sin condición (`agents.command.ts:218`). **`init`, `upgrade` y `adr` sí leen la suya** — `init` cambia a un `DryRunFileSystem`, `upgrade` retorna antes de aplicar — así que esto es un comando fuera de la convención, no una convención ausente. **DEMOSTRADO, no inferido:** correr `test/agents.e2e-spec.ts -t "dry-run"` en solitario contra un árbol limpio deja tres archivos VERSIONADOS modificados — `src/sdk/cli/rulesets/agents/agents-registry.json` y los dos bajo `rulesets/agents/test-value/`. **La salida del test está commiteada como si fuera dato de producto:** `test-value` es la cadena que el mock de prompts devuelve a toda pregunta (`test/mock-prompt.service.ts:11-12`), y `6bb43cfa` versionó ese agente dentro de los propios rulesets del repositorio. **Un simulacro que escribe es peor que no tener simulacro**, porque es la bandera a la que un usuario recurre justo cuando no quiere que se le confíe la de verdad — y el tester de conformidad de superficies nunca lo detectó, porque ningún oráculo pregunta si `--dry-run` dejó el disco intacto. **ARREGLADO 2026-07-30:** la rama seca no alcanza el escritor en absoluto — en vez de escribir a través de un filesystem inerte, que regresa en cuanto el escritor gana una llamada nueva — reporta las rutas que habría creado con un nuevo `planInstall`, y la ruta del menú ahora reenvía las opciones del llamador, porque `evolith agents --dry-run` sin subcomando las descartaba y escribía. Los tres artefactos commiteados quedan eliminados, la suite e2e corre en un directorio temporal, y su oráculo ahora AFIRMA que el disco no cambió, más un caso de contraste que prueba que una instalación real sí escribe: `no reventó` pasó todo el tiempo que el defecto existió. **Barrido registrado:** `init`, `upgrade`, `adr`, `scaffold`, `docs`, `fixtures` y `generate-domain` condicionan sus escrituras a la bandera — `agents` era la única declaración del CLI sin lector. **SIGUE ABIERTO:** el tester cross-superficie no tiene oráculo para las banderas sin efecto como clase, así que la siguiente se vuelve a encontrar a mano. **Borrar el residuo destapó dos dependientes ocultos de él**, ambos reparados aquí: `47-validate-joined-paths` anclaba una declaración de subárbol generado en que existiera `src/sdk/cli/rulesets` —cierto en un checkout limpio solo porque el residuo estaba dentro— y se puso rojo señalando a `policy.wasm`; y un spec del resolver seguía leyendo el árbol real, así que su simulación de EACCES dejó de dispararse. Verificado apartando el artefacto de build: 101 suites / 1447 tests en verde con el directorio ausente. | | | `CLI` | Cross | P1 | S | `EN-PROGRESO` | -| [`GT-642`](./gap-reference-catalog.es.md#gt-642) | **Ningún check falla ante un ciclo de import en tiempo de ejecución, y los dos que existían se encontraron a mano.** [`GT-641`](./gap-reference-catalog.es.md#gt-641) eliminó dos ciclos `require` reales de `core-domain` — habían llegado sin ser vistos a un paquete de 346 módulos porque la medición que los encuentra no corre en ninguna parte. `lint:boundaries` vigila la dirección entre capas, no la ciclicidad; `tsc` compila un ciclo sin quejarse; ningún guard de gobernanza lee el grafo de módulos. **La medición ya existe y no es alcanzable:** [`GT-589`](./gap-reference-catalog.es.md#gt-589) entrega `@beyondnet/evolith-repo-facts` y `findImportCycles`, y ambos solo viven en `feat/gt-589-repo-facts` (PR #309). **BLOQUEADO, no simplemente pendiente** — un guard en `main` no puede correr un extractor que no está en `main`, y reimplementar la detección de ciclos al lado sería la segunda fuente de verdad que GT-589 existe para evitar. Cuando #309 aterrice: un script que corre el extractor sobre cada paquete del workspace y falla con `findImportCycles(...).length > 0`, cableado en `Governance guards (GT-578)`, con el fixture negativo que demuestre que se le ha visto en rojo. **Debe barrer todos los paquetes, no solo el ya medido** — `cli`, `mcp-server`, `core`, `agent-runtime` y `sdk-client` nunca han pasado por el extractor, y la forma "el barril es dueño del tipo" que produjo ambos ciclos de GT-641 no es específica de `core-domain`. | | | `Governance` | Cross | P2 | S | `PENDIENTE` | -| [`GT-641`](./gap-reference-catalog.es.md#gt-641) | **Las fuentes del propio Core arrastran dos ciclos de import en tiempo de ejecución, y nada de lo que corre hoy en CI puede verlos.** La base de hechos estructural de [`GT-589`](./gap-reference-catalog.es.md#gt-589), apuntada a `src/packages/core-domain`, reporta `application/services/index.ts` ⇄ `application/use-cases/initialize-project.use-case.ts` y `application/validators/blocking-criteria-validator.ts` ⇄ `application/validators/phase-gate-validator.service.ts`. **Ninguno es solo de tipos**, así que ninguno se borra al compilar: son ciclos `require` reales, y la base de hechos los clasifica así porque distingue las aristas type-only de las de valor. **Una misma forma, dos veces:** un módulo publica un tipo Y ADEMÁS construye o reexporta al colaborador que lo consume, así que el colaborador tiene que importar de vuelta a través de él para nombrar sus propios parámetros. **Las cadenas de dos nodos que nombra el reporte son la mitad pequeña del hallazgo** — los componentes fuertemente conexos son más anchos: `project-scaffolder.service` y `phase-transition.use-case` están en el primero, `evidence-validator` y `ruleset-loader` en el segundo, de modo que un arreglo limitado a los dos archivos de la cadena habría movido el ciclo en vez de eliminarlo. **ARREGLADO 2026-07-30:** las formas compartidas se mudan a `application/services/use-case.types.ts` y `application/validators/phase-gate-validator.types.ts`; ambos dueños anteriores las reexportan, así que ninguna ruta de import existente cambia — incluida `@beyondnet/evolith-core-domain/application/services`, de donde el CLI y el servidor MCP importan `InitProjectInput`. **EL GUARD NO ESTÁ CONSTRUIDO, y decir por qué importa más que el arreglo:** `@beyondnet/evolith-repo-facts` solo existe en `feat/gt-589-repo-facts` (PR #309) y no está en `main`, así que ningún check de la rama por defecto puede correr esta medición. Hasta que #309 aterrice, un ciclo nuevo vuelve a pasar en silencio igual que estos dos. | | | `Core` | Cross | P2 | S | `COMPLETADO` | -| [`GT-640`](./gap-reference-catalog.es.md#gt-640) | **El guard comparaba el snapshot contra seis números copiados del propio snapshot, así que solo podía pasar — y el archivo que no estaba vigilando se estampa en 388 filas de un artefacto mayor.** `native-evaluability-snapshot.json` declaraba en su propia cabecera que era "un SNAPSHOT CAPTURADO, no la fuente de verdad". **No existía ningún script de captura.** Se mantenía a mano y derivó: fijaba `documentation-only: 129` mientras Core fijaba 136, y seguía llamando `unimplemented-native` a reglas que ya tenían handler. Su guard en `iso-5055-mapping.test.mjs` asertaba los seis conteos de clase del snapshot contra seis números escritos a mano en el test — los mismos seis literales que el snapshot ya contenía. **El valor esperado y el valor real eran copias el uno del otro**, así que la aserción se sostenía mientras el archivo no cambiara, fijara Core lo que fijara; informaba de una concordancia que nunca comprobó, lo cual es peor que no tener guard, porque la fila que protege se lee como verificada. **La deriva no se queda donde empieza.** `build-iso-5055-mapping.mjs` estampa `nativeEvaluability` en TODAS las filas del mapeo ISO/IEC 5055 desde ese archivo — 388 tras la recaptura, 381 antes — así que una sola clase rancia se blanquea en un artefacto derivado cinco veces mayor que su entrada, y sobredimensiona el backlog de handlers que es el entregable entero de [`GT-598`](./gap-reference-catalog.es.md#gt-598). El orden recaptura→reconstrucción no estaba escrito en ningún sitio, y el guard del propio mapeo no corría en **ningún workflow**. **CORREGIDO el 2026-07-29:** un script de captura que ejecuta el triage REAL de Core vía `ts-node` en vez de reimplementar la clasificación, la medición extraída del spec de jest a `test/rule-corpus-triage.ts` para que un script pueda alcanzarla — esa inalcanzabilidad es la razón de que el archivo se mantuviera a mano —, aserciones snapshot-contra-triage-fresco en ambas direcciones, un guard del job de documentación que lee los conteos que Core fija LEYENDO el spec y **lanza excepción** cuando el literal falta o cambia de forma, y ambos pasos declarados como eslabones de la cadena de artefactos derivados de [`GT-630`](./gap-reference-catalog.es.md#gt-630). **Recapturado el 2026-07-29 tras mergear el fix en la línea actual, y la deriva había crecido mientras estaba en vuelo:** `native-handler` 139 → 151, `documentation-only` 129 → 136, el backlog de handlers 60 → **48**, corpus 379 → 386, mapeo 381 → 388 filas. Doce de esos handlers los cerraron [`GT-595`](./gap-reference-catalog.es.md#gt-595) y [`GT-632`](./gap-reference-catalog.es.md#gt-632) mientras el snapshot mantenido a mano seguía reportándolos como backlog — el defecto demostrándose una vez más, sobre números que nadie escribió dos veces. **RENUMERADA GT-633 -> GT-640 el 2026-07-30**, después de que `49-validate-gap-id-allocation` detectara que `GT-633` ya nombraba otro gap en `develop` (el scaffolding de `evolith init`, `c8270e48`, registrado un día antes). Se mueve la fila más nueva, según el propio consejo del guard. | | | `Governance` | Cross | P1 | S | `COMPLETADO` | -| [`GT-639`](./gap-reference-catalog.es.md#gt-639) | **Nada hace visible el trabajo en vuelo entre ramas, así que el mismo gap se arregla dos veces.** El 2026-07-30 el mismo trabajo se hizo dos veces, en tres ocasiones distintas, por sesiones que no podían verse: [`GT-640`](./gap-reference-catalog.es.md#gt-640) (registrado como GT-633 entonces) arreglado por un script de captura en `main` (#276) Y por un renderer independiente dentro del spec en `develop` (#279) — los dos correctos, los dos recapturando los mismos números, y dos generadores para un artefacto es el defecto del propio GT-640 un nivel más arriba, así que una tercera sesión gastó una reconciliación entera en dejar uno; el parser del guard de evidencias arreglado dos veces (`d1ea72a3` en main, `5acb29ed` en develop); y un id de GT asignado dos veces, forzando la renumeración que hay detrás de [`GT-638`](./gap-reference-catalog.es.md#gt-638). **El coste no es el conflicto de merge** — es el trabajo duplicado antes de que nadie llegara a él, más la reconciliación, que introdujo un defecto propio (una comparación sensible al orden que reescribía la fecha de captura en cada corrida, invisible un día porque ambas estampaban la misma fecha). Una fila lleva estado pero no QUIÉN la trabaja ni DÓNDE, así que dos sesiones pueden leer ambas `PENDIENTE` y empezar ambas, correctamente. **La convención de un solo driver ya existe; lo que falta es un mecanismo que la haga observable**, así que en un día cargado se degrada en silencio. **GUARD CONSTRUIDO el 2026-07-30 — tres criterios de cuatro.** `50-validate-gap-claim` deriva las reclamaciones de los PULL REQUESTS ABIERTOS (cada `GT-*` en título, cuerpo o nombre de rama) y falla cuando un id lo reclaman dos, nombrando ambos con su rama. Derivado y no a mano a propósito: una reclamación escrita a mano se queda rancia en la dirección que importa. Cableado en `Governance guards (GT-578)` con `GH_TOKEN`; observado en rojo por `43-validate-guard-negative-fixtures` (39/39). **El criterio 1 sigue abierto a propósito:** una lista commiteada en el tablero derivaría de estado vivo de GitHub y estaría rancia en cuanto alguien abriera un PR — y la cadena de [`GT-630`](./gap-reference-catalog.es.md#gt-630) existe para exigir que los derivados alcancen un punto fijo, así que uno perpetuamente rancio sería peor que ninguno. Tampoco ve una rama sin PR abierto, y lo dice en su propio texto de fallo. | | | `Governance` | Cross | P2 | M | `EN-PROGRESO` | -| [`GT-638`](./gap-reference-catalog.es.md#gt-638) | **El tablero no tiene asignador de ids, así que dos ramas paralelas reparten el mismo número GT.** Un id se elige leyendo el `GT-*` más alto de la rama en la que uno está, y nada lo contrasta con otra rama — así que dos sesiones en paralelo asignan el mismo número y una se entera en el merge. Ya pasó: `8449af3d` en `develop` dice *"renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"*; esa fila y [`GT-634`](./gap-reference-catalog.es.md#gt-634) en `main` tomaron el mismo id con horas de diferencia, desde sesiones que no podían verse. **La renumeración es manual y con pérdidas**, que cuesta más que el choque: un id vive en la fila del tablero, el ancla del catálogo, el registro de evidencia de cierre, referencias cruzadas en dos idiomas, mensajes de commit y cuerpos de PR — y sólo los tres primeros son comprobables mecánicamente. `08-validate-tracking` no puede ayudar por construcción: valida dentro de un único árbol de trabajo, y las ramas quedan fuera de su mundo. Tampoco es un desliz aislado — en el merge `develop` → `main` del 2026-07-30 el mismo patrón de duplicación convergente aparece tres veces: GT-640 (entonces GT-633) arreglado dos veces, el parser del guard de evidencias arreglado dos veces, este id asignado dos veces. **CERRADO el 2026-07-30 — y en su primera corrida real encontró una SEGUNDA colisión que nadie conocía.** `49-validate-gap-id-allocation` compara el **Title:** de cada id en HEAD contra el título que ese id lleva en la rama base; un título distinto para el mismo id es un número nombrando dos gaps. Contra `origin/develop` reportó **`GT-633`**: allí es "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29) y en main era la fila del guard tautológico (`c3221276`, 2026-07-30), renumerada después a `GT-640`. El merge no lo había sacado a la luz. Se niega a adivinar qué lado está mal — un retítulo y una colisión se ven igual desde fuera —, así que nombra los dos. Cableado en `Governance guards (GT-578)` con un `git fetch` explícito de la base, porque un checkout superficial de CI no la tiene. Observado en rojo por `43-validate-guard-negative-fixtures` (38/38). Sigue sin ver un id asignado en una rama sin PR abierto, y su salida `--verbose` lo advierte en cada id nuevo. | | | `Governance` | Cross | P2 | S | `COMPLETADO` | -| [`GT-637`](./gap-reference-catalog.es.md#gt-637) | **El ratchet de referencias muertas de GT-578 estaba atascado en 305 por dos bugs reales en el propio GUARD, no por el tamaño del backlog de registros que decía medir.** Al cerrar el PR de `GT-633` apareció un fallo en `Governance guards (GT-578)` y, al investigarlo, un refactor sin commitear y sin terminar que ya estaba en el árbol de trabajo: `resolveCommand` llamaba a `npmScriptOperand(...)`, una función que nunca se definió, así que el guard crasheaba con `ReferenceError` en cada invocación — incluso en modo reporte plano. Reparar esa función (restaurar su comportamiento y luego corregirlo) bajó el conteo de 309 a 78, lo que destapó el segundo bug: el corpus usa `npm run --workspace