diff --git a/reference/core/control-center/evidence/gap-closure-evidence.json b/reference/core/control-center/evidence/gap-closure-evidence.json index cf31dc1b..ded6f336 100644 --- a/reference/core/control-center/evidence/gap-closure-evidence.json +++ b/reference/core/control-center/evidence/gap-closure-evidence.json @@ -8310,6 +8310,28 @@ ], "dependencyDisposition": "accepted-scope", "dependencyRationale": "The action is dogfooded and under regression, which is what this gap was about. Making the workflow a REQUIRED check is branch-protection configuration, not a code change, and is deliberately left outside this closure — a gate nobody requires is how this action rotted in the first place, so it is worth doing, just not here." + }, + { + "id": "GT-570", + "closedAt": "2026-07-27", + "closureCommit": "9a82d9f2", + "evidence": [ + ".github/workflows/npm-release.yml", + ".harness/scripts/release/plan-npm-release.mjs", + "CHANGELOG.md", + "src/packages/mcp-server/package.json", + "src/sdk/cli/package.json", + "src/packages/agent-runtime/package.json" + ], + "validationCommands": [ + "gh run view 30273290417 -> conclusion=success, workflow_dispatch on main @ 9a82d9f2 with dry_run=false.", + "curl https://registry.npmjs.org/@beyondnet%2Fevolith-mcp -> latest 1.2.0, published 2026-07-27T14:06:39Z, dist.attestations present. Same shape for evolith-agent-runtime@1.2.0 (14:06:10), evolith-sdk@2.0.0 (14:06:24) and evolith-cli@1.2.0 (14:06:58) — timestamps confirm dependency order.", + "The audit finding \"0 of 8 published packages carry dist.attestations\" no longer holds: four now do, produced by GitHub Actions with id-token: write rather than by a developer machine.", + "Scope corrected before shipping: `git show --name-only` (not --stat, which truncates long paths) showed the security wave touches mcp (15 files), cli (7) and agent-runtime (1, the CWE-78 shell runner) — the original plan of bumping mcp alone would have left two vulnerable.", + "Rehearsals: run 30265298045 found no LICENSE in any of the four tarballs and repository.url auto-corrected in all eight; run 30271837941 found bin paths auto-corrected in two. All three fixed before publishing and all three would have been permanent." + ], + "dependencyDisposition": "accepted-scope", + "dependencyRationale": "Re-scoped rather than over-claimed. Two of the three original acceptance criteria are NOT met by this closure and were moved to GT-624: deprecating the 1.1.0 versions (needs npm credentials, owner action) and a release gate that fails when a security-tagged commit is absent from the published tag. This row closes on what it was about — an installable artifact older than its own published security fixes — and the remainder is tracked, not absorbed." } ] } 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 1cbd6713..d0b4b417 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -7058,9 +7058,11 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre - **Componente:** `Infra` · **Criticidad:** P0 · **Complejidad:** S - **Procedencia:** Auditoría de madurez de producto del 2026-07-26 (multi-agente con verificación adversarial). Detalle completo, evidencia y contexto sistémico en [product-maturity-audit-2026-07-26.es.md](../maturity-reports/product-maturity-audit-2026-07-26.es.md). - **Criterios de aceptación:** - - [ ] `npm view` reporta una versión cuyo `time.modified` es posterior a los commits de seguridad, y 1.1.0 está deprecada. - - [ ] La sección de seguridad está bajo un heading publicado, no bajo `[Unreleased]`. - - [ ] Un gate de release falla cuando faltan commits etiquetados de seguridad en el tag publicado. + - [x] La versión publicada de cada paquete que tocó la ola de seguridad es posterior a la propia ola, verificado contra el registry. — 1.2.0 / 2.0.0 publicados el 2026-07-27T14:06Z; la ola aterrizó el 2026-07-23. + - [x] La sección de seguridad está bajo un heading publicado, no bajo `[Unreleased]`. — `## [1.2.0] - 2026-07-27`. + - [x] Los artefactos publicados llevan provenance en vez de haberse publicado a mano. — `dist.attestations` en los cuatro, generadas por GitHub Actions con `id-token: write`. + + _Re-acotado el 2026-07-27: el criterio de deprecación de 1.1.0 y el del gate de release se movieron a `GT-624` en vez de marcarse cumplidos._ #### GT-571 @@ -7828,3 +7830,16 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre - [ ] Un commit con mensaje malformado se rechaza en local, demostrado intentándolo. - [ ] El tipo `security` está declarado en la config de commitlint con un mapeo explícito de salto de versión, o se deja de usar. - [ ] Ningún hook de `.husky/` imprime un mensaje de "skipping" y sale con cero — un hook que no puede correr se borra, no se silencia. + +#### GT-624 + +**Título:** Las versiones 1.1.0 vulnerables siguen instalables sin aviso, y nada impide que otro arreglo de seguridad se quede sin publicar + +- **Propósito:** Que nadie pueda instalar en silencio una versión vulnerable, y que un arreglo de seguridad sin publicar deje de ser invisible. +- **Evidencia:** **Extraído de [`GT-570`](./gap-reference-catalog.es.md#gt-570) para que su remanente quede registrado y no absorbido en un cierre.** 1.2.0 se publicó el 2026-07-27 con provenance y la exposición está cerrada para quien instale `latest` — pero dos de los tres criterios originales de GT-570 no se cumplen con eso, y fingir lo contrario es el patrón que este tablero lleva pillándose a sí mismo (GT-12, GT-568, GT-254, GT-424). **(a) Las versiones 1.1.0 no están deprecadas.** `npm install @beyondnet/evolith-mcp@1.1.0` sigue resolviendo la build anterior a la ola de seguridad del 2026-07-23, en silencio, y el CHANGELOG público nombra los ficheros vulnerables. Deprecar es un comando por paquete pero exige credenciales de npm, así que es acción del dueño: `npm deprecate @beyondnet/evolith-mcp@1.1.0 "Security fixes in 1.2.0 — see CHANGELOG"`, e igual para `evolith-cli@1.1.0` y `evolith-agent-runtime@1.1.0`. **(b) Ningún gate de release falla cuando un commit etiquetado de seguridad no está en el tag publicado.** Esa ausencia es exactamente lo que permitió que la ola siguiera sin publicar del 2026-07-23 al 2026-07-27 mientras `SECURITY.md` declaraba la línea 1.1.x "actively patched". No lo detectó nada; lo detectó una auditoría. Relacionado: `GT-623` — release-please deriva los saltos de versión de los mensajes de commit, y el tipo `security(...)` que usan 2 de los últimos 60 commits no es un tipo de Conventional Commits, así que no aporta nada al bump. Los dos defectos dejan que un cambio de seguridad no llegue a una versión, por caminos distintos. +- **Componente:** `Infra` · **Criticidad:** P1 · **Complejidad:** S +- **Procedencia:** Extraído de GT-570 el 2026-07-27, al cerrarlo. Dos de sus tres criterios originales no se cumplían con la publicación de 1.2.0; se registran aquí en vez de darse por buenos. +- **Criterios de aceptación:** + - [ ] `npm view @beyondnet/evolith-mcp@1.1.0` reporta la versión como deprecada, e igual cli y agent-runtime. + - [ ] Un gate de CI falla cuando un commit cuyo tipo o scope lo marca como de seguridad no está en el último tag publicado. + - [ ] El gate lleva una fixture negativa que lo pone rojo, para que no sea otro guard que nadie ha visto fallar. diff --git a/reference/core/control-center/gaps/gap-reference-catalog.md b/reference/core/control-center/gaps/gap-reference-catalog.md index cee0769c..a3456a07 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -7153,9 +7153,11 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo - **Component:** `Infra` · **Criticality:** P0 · **Complexity:** S - **Provenance:** Product maturity audit of 2026-07-26 (multi-agent with adversarial verification). Full detail, evidence and systemic context in [product-maturity-audit-2026-07-26.md](../maturity-reports/product-maturity-audit-2026-07-26.md). - **Acceptance criteria:** - - [ ] `npm view` reports a version whose `time.modified` is later than the security commits, and 1.1.0 is deprecated. - - [ ] The security section sits under a published heading, not under `[Unreleased]`. - - [ ] A release gate fails when security-tagged commits are missing from the published tag. + - [x] The published version of every package the security wave touched is newer than the wave itself, verified against the registry. — 1.2.0 / 2.0.0 published 2026-07-27T14:06Z; the wave landed 2026-07-23. + - [x] The security section sits under a published heading, not under `[Unreleased]`. — `## [1.2.0] - 2026-07-27`. + - [x] The published artifacts carry provenance rather than being hand-published. — `dist.attestations` present on all four, produced by GitHub Actions with `id-token: write`. + + _Re-scoped 2026-07-27: the 1.1.0 deprecation criterion and the release-gate criterion moved to `GT-624` rather than being ticked._ #### GT-571 @@ -7923,3 +7925,16 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo - [ ] A commit with a malformed message is rejected locally, demonstrated by trying one. - [ ] The `security` type is either declared in the commitlint config with an explicit version-bump mapping, or removed from use. - [ ] No hook in `.husky/` prints a "skipping" message and exits zero — a hook that cannot run is deleted, not silenced. + +#### GT-624 + +**Title:** The vulnerable 1.1.0 versions are still installable without warning, and nothing stops a security fix going unpublished again + +- **Purpose:** Make it impossible to silently install a vulnerable version, and make an unpublished security fix stop being invisible. +- **Evidence:** **Carved out of [`GT-570`](./gap-reference-catalog.md#gt-570) so its remainder is tracked rather than absorbed into a closure.** 1.2.0 shipped on 2026-07-27 with provenance and the exposure is closed for anyone installing `latest` — but two of GT-570's three original criteria are not met by that, and pretending otherwise is the pattern this board keeps catching in itself (GT-12, GT-568, GT-254, GT-424). **(a) The 1.1.0 versions are not deprecated.** `npm install @beyondnet/evolith-mcp@1.1.0` still resolves the build that predates the 2026-07-23 security wave, silently, and the public CHANGELOG names the vulnerable files. Deprecating is one command per package but requires npm credentials, so it is an owner action: `npm deprecate @beyondnet/evolith-mcp@1.1.0 "Security fixes in 1.2.0 — see CHANGELOG"`, likewise for `evolith-cli@1.1.0` and `evolith-agent-runtime@1.1.0`. **(b) No release gate fails when a security-tagged commit is absent from the published tag.** That absence is exactly what let the wave sit unpublished from 2026-07-23 to 2026-07-27 while `SECURITY.md` declared the 1.1.x line "actively patched". Nothing detected it; an audit did. Related: `GT-623` — release-please derives version bumps from commit messages, and the `security(...)` type used by 2 of the last 60 commits is not a Conventional Commits type, so it contributes nothing to a bump. Both defects let a security change fail to reach a version, by different routes. +- **Component:** `Infra` · **Criticality:** P1 · **Complexity:** S +- **Provenance:** Carved out of GT-570 on 2026-07-27 as it closed. Two of its three original criteria were not met by shipping 1.2.0; they are recorded here rather than assumed. +- **Acceptance criteria:** + - [ ] `npm view @beyondnet/evolith-mcp@1.1.0` reports the version as deprecated, likewise cli and agent-runtime. + - [ ] A CI gate fails when a commit whose type or scope marks it as security is absent from the latest published tag. + - [ ] The gate ships with a negative fixture that turns it red, so it is not another guard nobody has seen fail. diff --git a/reference/core/control-center/gaps/gap-tracking.es.md b/reference/core/control-center/gaps/gap-tracking.es.md index 6d43c766..e86720de 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -13,6 +13,7 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | ID | Gap | Qué significa | Ejemplo | Componente | Fase | Criticidad | Complejidad | Estado | |---|---|---|---|:---:|:---:|:---:|:---:|:---:| +| [`GT-624`](./gap-reference-catalog.es.md#gt-624) | **Extraído de [`GT-570`](./gap-reference-catalog.es.md#gt-570) para que su remanente quede registrado y no absorbido en un cierre.** 1.2.0 se publicó el 2026-07-27 con provenance y la exposición está cerrada para quien instale `latest` — pero dos de los tres criterios originales de GT-570 no se cumplen con eso, y fingir lo contrario es el patrón que este tablero lleva pillándose a sí mismo (GT-12, GT-568, GT-254, GT-424). **(a) Las versiones 1.1.0 no están deprecadas.** `npm install @beyondnet/evolith-mcp@1.1.0` sigue resolviendo la build anterior a la ola de seguridad del 2026-07-23, en silencio, y el CHANGELOG público nombra los ficheros vulnerables. Deprecar es un comando por paquete pero exige credenciales de npm, así que es acción del dueño: `npm deprecate @beyondnet/evolith-mcp@1.1.0 "Security fixes in 1.2.0 — see CHANGELOG"`, e igual para `evolith-cli@1.1.0` y `evolith-agent-runtime@1.1.0`. **(b) Ningún gate de release falla cuando un commit etiquetado de seguridad no está en el tag publicado.** Esa ausencia es exactamente lo que permitió que la ola siguiera sin publicar del 2026-07-23 al 2026-07-27 mientras `SECURITY.md` declaraba la línea 1.1.x "actively patched". No lo detectó nada; lo detectó una auditoría. Relacionado: `GT-623` — release-please deriva los saltos de versión de los mensajes de commit, y el tipo `security(...)` que usan 2 de los últimos 60 commits no es un tipo de Conventional Commits, así que no aporta nada al bump. Los dos defectos dejan que un cambio de seguridad no llegue a una versión, por caminos distintos. | | | `Infra` | Cross | P1 | S | `PENDIENTE` | | [`GT-623`](./gap-reference-catalog.es.md#gt-623) | **El hook existe, está cableado, y no puede aplicar nada.** `.husky/commit-msg` ejecuta `npx --no -- commitlint --version`; cuando falla imprime `commitlint is not installed — skipping commit message lint` y sale con **éxito**. Verificado: `commitlint` no aparece ni en `dependencies` ni en `devDependencies` del `package.json` raíz, no hay `commitlint.config.*` ni `.commitlintrc*`, y no existe clave `commitlint` en `package.json`. Así que la rama `else` es la única que se ejecuta, en cada commit. Observado en vivo el 2026-07-27 al mergear `develop` en una rama de trabajo. **Qué depende de la convención que no aplica:** `CONTRIBUTING.md` y `.github/pull_request_template.md` exigen Conventional Commits en tres sitios, y —la parte que cuesta dinero— **release-please deriva los saltos de versión de los mensajes de commit**, cableado en `sdk-cli-release.yml` y `sdk-cli-ci.yml`. **Ya está derivando, y con consecuencia:** 2 de los últimos 60 commits no-merge usan el tipo `security(...)` (`security(fase-7): add Docker/K8s hardening checklist`, `security(fase-6): add executable security rulesets`), que no es un tipo de Conventional Commits. release-please no lo reconoce, así que **un commit que se anuncia como cambio de seguridad no aporta nada al salto de versión** — el mismo modo de fallo que [`GT-570`](./gap-reference-catalog.es.md#gt-570), donde una ola de seguridad sigue sin publicarse. Fix: instalar y configurar commitlint para que el hook tome su rama real, o borrar el hook y dejar de reclamar la convención. Fallar abierto es la peor de las tres opciones, porque produce la apariencia de enforcement. Si se quiere el tipo `security`, declararlo en la config y mapearlo a un bump — no dejarlo a un linter que nunca corre. | | | `Governance` | Cross | P2 | S | `PENDIENTE` | | [`GT-622`](./gap-reference-catalog.es.md#gt-622) | **Cada pull request arrastra un check `CodeQL` en rojo que dice "1 configuration not found", y no es un hallazgo de seguridad — es contabilidad huérfana.** GitHub conserva **82 análisis de code scanning** en `refs/heads/main` bajo la clave `.github/workflows/ci.yml:codeql`. Esa configuración existió de verdad: el commit `87f50ce3` añadió un job `codeql` a `ci.yml`, y `f50030cd` lo quitó el 2026-06-06 al vaciar ese workflow — el último análisis bajo esa clave es de ese mismo día. Como la configuración sigue *registrada* en `main` pero nada la produce, GitHub la reporta como ausente en cada PR. Lleva así 51 días. Verificado: `code-scanning/analyses?ref=refs/heads/main` devuelve tres claves — `ci.yml:codeql` (82, último 2026-06-06), `sdk-cli-ci.yml:codeql-analysis` (159, al día) y `sdk-cli-ci.yml:trivy-scan` (159, al día). El escaneo que importa está sano; `CodeQL SAST` pasa y es check requerido. **El workflow muerto ya está eliminado** (corría un job `Disabled` que sólo hacía `echo`, en cada PR y push a `main` y `develop`); borrar el fichero NO limpia la configuración registrada, y por eso existe esta fila. **La acción restante queda deliberadamente sin automatizar:** eliminar los 82 análisis vía `DELETE /repos/{owner}/{repo}/code-scanning/analyses/{id}` es irreversible y destruye historial de code scanning de una rama protegida. Su valor histórico es nulo —describen una configuración muerta desde junio— pero tirar historial de escaneo de seguridad es decisión del dueño, no de la herramienta. **Por qué importa más allá del ruido:** un check permanentemente rojo enseña a los revisores a descontar los checks rojos, y `CodeQL SAST` —que comparte el nombre CodeQL y SÍ es requerido— es precisamente el check que nadie se puede permitir aprender a ignorar. | | | `Infra` | Cross | P2 | XS | `PENDIENTE` | | [`GT-609`](./gap-reference-catalog.es.md#gt-609) | **Fuga de autorización en la superficie de descubrimiento.** `mcp-cache.service.ts:8` declara `toolsList: 'mcp:tools:list'` — una única clave literal, sin principal, tenant ni scope — y la lista se cachea ANTES de aplicar el filtro de scope. Así que el primer llamador que calienta la caché decide el inventario que ven todos los siguientes durante el TTL: un admin que la caliente publica el inventario con capacidad de escritura a los lectores. **Verificado aquí contra el código.** Fix: indexar la caché por principal (hash de scopes + tenant), o eliminarla — una superficie de descubrimiento que responde con la vista de otro principal es peor que una sin caché. Origen: hallazgo 4.2 del diagnóstico de producto (https://github.com/beyondnetcode/why-architecture/blob/main/docs/evolith-diagnostico-es.md). | | | `MCP Server` | Cross | P0 | S | `PENDIENTE` | @@ -43,7 +44,7 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-600`](./gap-reference-catalog.es.md#gt-600) | **Registrado como oportunidad, no como deuda** — separable de [`GT-596`](./gap-reference-catalog.es.md#gt-596)…[`GT-599`](./gap-reference-catalog.es.md#gt-599), que se sostienen por sí solos como gobernanza interna. Cuatro de los cinco artefactos adoptados allí son implementables como rulesets de Evolith: las debilidades estructurales de **ISO/IEC 5055:2021**, las prácticas de **NIST SP 800-218 (SSDF) v1.1**, los build levels de **SLSA**, y los checks que puntúa **OpenSSF Scorecard**. Eso convierte la remediación de la auditoría en una capacidad vendible, y ataca el hallazgo de producto más afilado del tablero: la auditoría del 2026-07-26 encontró **cero** apariciones de un ICP en 929 documentos en inglés, y 0 estrellas / forks / issues externos en 80 días públicos. "Evalúo tu repositorio contra ISO 5055, SSDF y SLSA y te doy tu puntuación OpenSSF" nombra un comprador, una partida presupuestaria y un disparador de cumplimiento; "gobierno tu arquitectura" no. Además compone con el wedge ya implementado en código (`enforce edit` + el hook de editor), y autoevaluarse contra esos estándares es simultáneamente el arreglo de [`GT-597`](./gap-reference-catalog.es.md#gt-597) y la mejor demo de venta disponible. **Sujeto a decisión, no listo para construir:** presupone que el vehículo de monetización es el wedge del CLI y no el Tracker, contradicción sin resolver que la auditoría señaló y que el dueño tiene que zanjar primero. | | | `Evolith Core` | Cross | P2 | L | `PENDIENTE` | | [`GT-579`](./gap-reference-catalog.es.md#gt-579) | **Encontrado al verificar la remediación de GT-569, no por la auditoría.** Node bufferiza de forma asíncrona las escrituras a un stdout en pipe, así que las ~12 llamadas a `process.exit()` del grafo de comandos descartaban lo que no se hubiera vaciado — truncando la salida en el buffer de pipe del SO (65.536 bytes en macOS/Linux). Medido: `validate --format json \| wc -c` devolvía **exactamente 65536** mientras el mismo comando redirigido a fichero producía 121.408 bytes válidos. Todo envelope por debajo de 64 KiB ocultaba el bug, que es por lo que sobrevivió; GT-569 agrandó el envelope y lo destapó. Seis tests e2e del CLI fallaban en `JSON.parse` en la posición 65262 y se leían como una regresión de GT-569 — la regresión era preexistente e incondicional para cualquier envelope grande. **COMPLETADO (`44fe8dd3`):** arreglado centralmente en `src/sdk/cli/src/main.ts` (`makeStdioBlocking`, con guarda porque `_handle.setBlocking` es API interna) en vez de en cada sitio de salida, así un comando nuevo no puede reintroducirlo saliendo de la forma habitual. Verificado: un envelope de 123.506 bytes atraviesa ahora una pipe real y parsea; CLI 1305+132 verde. | | | `Evolith CLI` | Cross | P0 | XS | `COMPLETADO` | | [`GT-569`](./gap-reference-catalog.es.md#gt-569) | **El número insignia del producto redefine en silencio su propio denominador.** De 379 reglas el motor nativo evalúa 108 y reporta `rulesChecked: 111`; las 271 restantes — **192 de ellas `blocking`** — devuelven `skipped` y se filtran en `ruleset-validator.service.ts:88` (`engineResults.filter(r => r.result !== 'skipped')`) antes de sumar el conteo. Ningún campo lo revela: `grep -rn "rulesSkipped" src --include='*.ts'` devuelve 0 resultados y `ValidationResult` solo lleva `{status, rulesChecked, issues, coreRef, timestamp}`. El mismo campo también SOBRE-cuenta: `validate --format json --core --engine opa` devuelve `rulesChecked: 379` habiendo ejecutado **cero** políticas (el wasm no resuelve contra el layout del Core). Y `native-evaluator.ts:69-72` convierte cualquier excepción del handler en `skipped`, así que **un evaluador que revienta es indistinguible de una regla verde**. Fix: añadir `rulesSkipped` + el array de ids saltados a `ValidationResult`, introducir un estado `errored` distinto de `skipped`, emitir un issue WARNING por cada regla MUST saltada, y fallar el run cuando `skipped/total` supere un umbral configurado. Es un cambio de reporting, no de motor — cerrar la brecha de handlers es aparte y mucho mayor. **COMPLETADO (`44fe8dd3`):** nuevo resultado `errored` (una excepción de handler ya no se convierte en `skipped`, así que un crash se distingue de una regla verde); `rulesSkipped`/`rulesErrored`/`rulesTotal` + arrays de ids añadidos de forma ADITIVA con `rulesChecked` intacto; issues advisory por regla MUST saltada; gate opt-in `maxSkippedFraction`. En este repo `validate` ahora reporta 269 saltadas de 380 en vez de recortar su denominador en silencio. Verificado: core-domain 1237/1237, CLI 1305+132, contract 43/43 tras añadir las cinco entradas de `WIRE_VALIDATION_RESULT`. Falsificado contra una copia pristina `git archive HEAD`: 12 de los 17 tests nuevos fallan sin el fix. **Cerrar la brecha de handlers en sí (≈240 reglas no evaluables) NO forma parte de esto y sigue abierto** — esta fila era sobre que el denominador fuese honesto, no sobre ensancharlo. | | | `core-domain` | Cross | P0 | M | `COMPLETADO` | -| [`GT-570`](./gap-reference-catalog.es.md#gt-570) | **npm sirve 1.1.0 publicada el 2026-07-18; la ola de seguridad aterrizó el 2026-07-23.** Verificado con `npm view @beyondnet/evolith-cli version time.modified`. El `CHANGELOG.md` público enumera los ficheros corregidos por nombre bajo `[Unreleased]`, y `SECURITY.md` declara la línea 1.1.x "Current stable line — actively patched". Quien siga el README instala la build sin parchear desde un repositorio que publica dónde están los agujeros. Fix: publicar **1.2.0** con la ola de seguridad, deprecar 1.1.0 en npm con un mensaje que apunte a la versión corregida, mover la sección de seguridad de `[Unreleased]` al heading publicado, y emitir el advisory que `SECURITY.md` ya promete. Follow-on: un gate de release que falle cuando HEAD contenga commits etiquetados de seguridad ausentes del último tag publicado. | | | `Infra` | Cross | P0 | S | `PENDIENTE` | +| [`GT-570`](./gap-reference-catalog.es.md#gt-570) | **npm sirve 1.1.0 publicada el 2026-07-18; la ola de seguridad aterrizó el 2026-07-23.** Verificado con `npm view @beyondnet/evolith-cli version time.modified`. El `CHANGELOG.md` público enumera los ficheros corregidos por nombre bajo `[Unreleased]`, y `SECURITY.md` declara la línea 1.1.x "Current stable line — actively patched". Quien siga el README instala la build sin parchear desde un repositorio que publica dónde están los agujeros. Fix: publicar **1.2.0** con la ola de seguridad, deprecar 1.1.0 en npm con un mensaje que apunte a la versión corregida, mover la sección de seguridad de `[Unreleased]` al heading publicado, y emitir el advisory que `SECURITY.md` ya promete. Follow-on: un gate de release que falle cuando HEAD contenga commits etiquetados de seguridad ausentes del último tag publicado. **COMPLETADO (corrida 30273290417, `main` @ `9a82d9f2`, 2026-07-27).** El artefacto publicado ya no es anterior a la ola de seguridad. Verificado contra el REGISTRY, no contra un exit code — `@beyondnet/evolith-agent-runtime@1.2.0` (14:06:10), `@beyondnet/evolith-sdk@2.0.0` (14:06:24), `@beyondnet/evolith-mcp@1.2.0` (14:06:39), `@beyondnet/evolith-cli@1.2.0` (14:06:58) — marcas de tiempo que además confirman el orden de dependencias, porque los tres últimos consumen al primero. **Los cuatro llevan `dist.attestations`**, que es la sustancia de este gap: el hallazgo de la auditoría era 0 de 8 paquetes con provenance, y éstos los generó GitHub Actions con `id-token: write`, no una máquina de desarrollo. **El alcance se corrigió antes de publicar:** el plan era bumpear sólo mcp, y venía de que `git show --stat` TRUNCA las rutas largas — con `--name-only` se vio que la ola toca tres paquetes publicados (mcp 15 ficheros, cli 7, agent-runtime 1: el shell runner CWE-78). Publicar el plan original habría dejado dos vulnerables y cerrado este gap contra ellos. **Dos ensayos se pagaron solos:** la corrida 30265298045 detectó que ninguno de los cuatro tarballs llevaba LICENSE en un proyecto MIT y que `repository.url` se autocorregía en los ocho; la 30271837941 detectó rutas de `bin` autocorregidas en dos. Los tres habrían sido permanentes — npm no permite despublicar pasadas 72 horas. **RE-ACOTADO, y el resto NO se descarta en silencio:** deprecar las versiones 1.1.0 exige credenciales de npm y es acción del dueño, y el gate de release que fallaría cuando faltan commits de seguridad en el tag publicado nunca se construyó. Ambos pasan a `GT-624`. Esta fila cierra sobre lo que realmente trataba —un artefacto instalable más viejo que sus propios arreglos de seguridad publicados— y dice con claridad lo que no hizo. | | | `Infra` | Cross | P0 | S | `COMPLETADO` | | [`GT-571`](./gap-reference-catalog.es.md#gt-571) | **La superficie de mayor tráfico del producto no funciona tal como está escrita.** (a) `README.md:96-98` (y `README.es.md:95-97`) mandan `evolith init` / `evolith validate`, pero el bin map publicado solo declara `evolith-cli` y `evolith-mcp` — el comando fantasma aparece en **447 invocaciones en 49 ficheros markdown no-`.es`**, así que renombrar el bin sale más barato que reescribir la doc. (b) Aun con el alias, `init --name my-sat` crea un **subdirectorio**, así que el `validate` que sigue en el mismo cwd apunta al padre y dispara `GOV-000` "Missing evolith.yaml" más 41 hallazgos blocking. Dentro del satélite correcto el primer validate sigue devolviendo 46 hallazgos / 39 blocking, dominados por las reglas internas del monorepo del vendedor (`CLI-RR-01` "dist/main.js not found", `TAX-05` "Missing top-level directories: sdk, .harness") con reglas de las 8 topologías disparando sobre un repo declarado fase-0. Además el binario publicado se autoidentifica como `main` (`evolith-cli init --help` imprime `Usage: main init [options]`) porque nunca se fija el program name. **EN-PROGRESO (`44fe8dd3`):** los tres defectos del lado CLI están arreglados y probados — `evolith` publicado junto a `evolith-cli` en el bin map, el program name fijado para que `--help` ya no diga `main`, e `init` con un contrato no-interactivo definido (inicializa el directorio ACTUAL, `--name` es solo el nombre del proyecto, `evolith init ` para un subdirectorio; un stdin cerrado ya no pregunta, el fallo fija un exit code distinto de cero, y `--format json` emite un envelope parseable y nada más). **NO cerrado en dos criterios:** "un repo recién inicializado devuelve 0 hallazgos blocking" está intacto — esos 39 hallazgos vienen de las reglas `CLI-RR-*`/`TAX-*` del propio vendedor disparando sobre un satélite fase-0, y requieren gating por `audience`/topología en el camino de rulesets; y "`npx @beyondnet/evolith-cli@latest` funciona" no puede ser cierto hasta que se publique una release (`GT-570`). **+2026-07-26 (ola 3) — criterio 2 de 3 CUMPLIDO, sigue abierto por el criterio 1.** Un satélite recién inicializado devuelve ahora **0 hallazgos blocking** (eran 35: CLI-RR-01/02, TAX-05/06, DEP-01/10, CB-VAL-01 más reglas de 7 topologías que el repo nunca declaró). Dos mecanismos en la capa de rulesets: una anotación `audience: core\|satellite\|both` (por defecto `both`, así que una regla sin anotar sigue aplicando en todas partes) que marca las familias que sólo se dirigen al monorepo del vendedor, y gating por `topologies` + `appliesFromSdlcPhase` leído de la fase y topología declaradas del satélite. **La interacción con GT-569 se decidió explícitamente:** una regla dirigida a otro es un PRE-FILTRO, no un cuarto resultado — nunca llega al evaluador, así que `rulesChecked + rulesSkipped + rulesErrored === rulesTotal` se sigue cumpliendo literalmente. Llamarla `skipped` inflaría la fracción no evaluada de un repo sano y podría disparar `maxSkippedFraction`. La exclusión sigue siendo visible: `rulesNotApplicable` / `notApplicableRuleIds` sobre un nuevo `corpusTotal`, más un advisory no bloqueante `GOV-RULE-NOT-APPLICABLE` que nombra los conteos y cómo meter las reglas en alcance. Satélite nuevo antes/después: 379 totales / 108 comprobadas / 35 blocking / `failed` → 274 totales / 41 comprobadas / 233 saltadas / 105 no aplicables / corpusTotal 379 / **0 blocking** / `warning`. **Sigue abierto:** el criterio 1 (`npx @beyondnet/evolith-cli@latest` en un contenedor limpio) no puede ser cierto hasta que se publique una release — bloqueado por `GT-570`. | | | `Evolith CLI` | Cross | P0 | S | `EN-PROGRESO` | | [`GT-572`](./gap-reference-catalog.es.md#gt-572) | **Reproducido contra el tarball publicado, no contra el working tree.** `npm pack @beyondnet/evolith-mcp@1.1.0`, arrancado por stdio sin auth: 47 herramientas anunciadas, **47 de 47 devuelven FORBIDDEN (ABAC-02)**; `resources/list` (11) y `prompts/list` (8) sí funcionan. Las dos vías de escape que el código define — `--allow-no-auth` (`main.ts:62-63`) y `EVOLITH_MCP_ALLOW_NO_AUTH=true`, ambas documentadas en `mcp-server-auth.ts` — se probaron en las tres combinaciones incluida `NODE_ENV=development` y **ninguna tiene efecto sobre el transporte por defecto**, lo cual es peor que su ausencia. Causa raíz: `mcpContextStorage.run` existe en exactamente un sitio no-spec (`mcp-server.service.ts:451`) dentro del closure de dispatch HTTP, así que el camino stdio nunca establece contexto. Ninguno de los dos oráculos puede verlo: el smoke de CI asserta `success !== undefined`, y el tester exploratorio solo conduce MCP por HTTP. Fix: envolver el dispatch stdio en `mcpContextStorage` con un contexto de sesión local, y hacer que el smoke asserte una invocación real con veredicto. **EN-PROGRESO (`44fe8dd3`):** causa raíz confirmada y arreglada — el camino stdio nunca entraba en `mcpContextStorage`, así que todo `tools/call` llegaba al dispatch como el fallback anónimo `roles: []` y el ABAC nativo lo denegaba. stdio ahora establece un principal de sesión local explícito; el camino HTTP queda intacto y sigue fail-closed. **Verificado contra el servidor construido, no contra un reporte:** con `NODE_ENV` sin fijar (el default documentado, y lo que producen `npx`/Claude Desktop) `tools/call` devuelve un payload real `success: true`; 50 herramientas anunciadas. **Por qué sigue abierto:** con `NODE_ENV=production` la misma llamada sigue devolviendo FORBIDDEN, y `NODE_ENV=production` es exactamente lo que fijan las imágenes Docker — así que un despliegue stdio en contenedor sigue siendo inusable. Eso es ahora una decisión estrecha, documentada y fail-closed por diseño en vez de un fallo silencioso del 100%, pero no está cerrado. El smoke de CI en `src/sdk/cli/examples/mcp-test.js` sigue asertando `success !== undefined`, que un envelope FORBIDDEN también satisface; reforzarlo es trabajo posterior fuera de área. **+2026-07-26 (ola 3) — el modo de fallo en producción está arreglado; 2 de 3 criterios siguen sin cumplirse, y la fila NO es cerrable todavía.** stdio con `NODE_ENV=production` ya no arranca en un estado donde las 50 herramientas deniegan: el servidor ahora se niega a arrancar sin credenciales, nombrando la variable a fijar, en vez de anunciar una superficie que rechazará cada llamada; y una denegación que sí ocurra dice por qué y qué hacer, no un ABAC-02 pelado. HTTP no se tocó y sigue fail-closed; no se añadió bypass ni se ensanchó ABAC. **El agente reportó este gap como `done` con 7/7 contra su propia lista de tareas, que no es lo mismo que los criterios de esta fila — comprobado, y no lo es.** El criterio 1 pide un `tools/call` real **contra el tarball publicado**; el tarball publicado es 1.1.0, anterior a todos estos fixes, así que ese criterio está bloqueado por `GT-570` exactamente igual que el de GT-571. El criterio 3 pide que el smoke de CI asserte un veredicto; `src/sdk/cli/examples/mcp-test.js:284,375` sigue asertando `success !== undefined`, que un envelope FORBIDDEN también satisface — fuera del área del agente y sin cambiar. | | | `MCP Server` | Cross | P0 | M | `EN-PROGRESO` | | [`GT-573`](./gap-reference-catalog.es.md#gt-573) | **La integración insignia falla en silencio, con ambos CI en verde.** El branch inline devuelve el envelope legacy: `evaluation.controller.ts:186` devuelve `evaluationVerdict!.outputEnvelope`, construido en `satellite-evaluation-pipeline.service.ts:85-94` como `createSuccessEnvelope({topology, gates, summary})`. El Tracker desenvuelve `data` (`CoreEvaluationGateway.cs:433`) y liga `CoreEvaluationEnvelope`, que declara solo `overallVerdict / outcome / resolvedTopology / results.gate[] / evaluatedAt` (`CoreEvaluationDtos.cs:200-216`). `Passed` queda null (`:406`), `Gates` queda vacío (`:414`), y `ToDecision` (`:583-606`) cae a **`"SKIPPED"`**, escrito como `decision=SKIPPED, status=COMPLETED`. 0 de 12 workflows del Core construyen el Tracker; 0 tests de contrato en ninguno de los dos repos. Fix: enrutar el branch inline por `EvaluationOrchestrator` para que devuelva el `EvaluationResult` canónico, y añadir un test de contrato dirigido por el consumidor en el CI del Core que asserte el JSON exacto que liga `CoreEvaluationEnvelope`; publicar esos pares request/response como fixtures en `@beyondnet/evolith-contracts` y promover ambos schemas a `MACHINE_CONTRACT_SET`. **EN-PROGRESO (`44fe8dd3`):** el branch inline ya no construye a mano el envelope legacy `{topology, gates, summary}` — devuelve el `EvaluationResult` canónico, así que hay UNA forma para la operación en vez de dos, y un spec de contrato dirigido por el consumidor asserta las claves exactas que liga el Tracker tanto para una evaluación que pasa como para una que FALLA. Los fixtures request/response se publican dentro de `@beyondnet/evolith-contracts` y ambos schemas se añadieron a `MACHINE_CONTRACT_SET`. La compatibilidad hacia atrás se comprobó en vez de asumirse: no existe ningún llamador inline legacy en el repo (ambos consumidores ya desenvuelven `data` como `EvaluationResult`). **NO cerrado:** el Tracker vive en otro repositorio y debe re-pinnear el paquete de contratos para que el round trip quede probado de punta a punta, y el caso FAIL del spec ejercita un fallo de regla de gobernanza, no de gate OPA. Verificado: core-api 163/163, contracts 32/32. | | | `Core API` | Cross | P0 | M | `EN-PROGRESO` | @@ -638,7 +639,7 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-246`](./gap-reference-catalog.es.md#gt-246) | Implementar experimentos Chaos Mesh/Litmus | | | `QA` | Cross | P3 | L | `COMPLETADO` | -**Progreso:** 561 / 623 completados · 9 en progreso · 49 pendientes · 4 diferidos +**Progreso:** 562 / 624 completados · 9 en progreso · 49 pendientes · 4 diferidos **Oleada 2026-06-23 (auditoría profunda de Winston III):** Añadidos 14 gaps nuevos `GT-212`…`GT-225` del Winston Audit Playbook que cubren: higiene de estado ADR (GT-212), metadata + presupuestos operativos + corpus de guías por topología (GT-213, GT-217, GT-219), observabilidad + OpenAPI en controladores REST (GT-214, GT-215), paridad de input-schemas OPA + densidad de tests por topología (GT-216, GT-222), plantillas de rollback + on-call de Fase 05 (GT-218), cobertura de ramas CLI + paridad de envelope --format + limpieza de skip-list (GT-220, GT-224, GT-225), audit logging HTTP de MCP (GT-221), y tests e2e de paridad cross-surface (GT-223). diff --git a/reference/core/control-center/gaps/gap-tracking.md b/reference/core/control-center/gaps/gap-tracking.md index 32e83b8b..dee8743f 100644 --- a/reference/core/control-center/gaps/gap-tracking.md +++ b/reference/core/control-center/gaps/gap-tracking.md @@ -13,6 +13,7 @@ This board is the single source of truth for technical debt, gaps, opportunities | ID | Gap | What it means | Example | Component | Phase | Criticality | Complexity | Status | |---|---|---|---|:---:|:---:|:---:|:---:|:---:| +| [`GT-624`](./gap-reference-catalog.md#gt-624) | **Carved out of [`GT-570`](./gap-reference-catalog.md#gt-570) so its remainder is tracked rather than absorbed into a closure.** 1.2.0 shipped on 2026-07-27 with provenance and the exposure is closed for anyone installing `latest` — but two of GT-570's three original criteria are not met by that, and pretending otherwise is the pattern this board keeps catching in itself (GT-12, GT-568, GT-254, GT-424). **(a) The 1.1.0 versions are not deprecated.** `npm install @beyondnet/evolith-mcp@1.1.0` still resolves the build that predates the 2026-07-23 security wave, silently, and the public CHANGELOG names the vulnerable files. Deprecating is one command per package but requires npm credentials, so it is an owner action: `npm deprecate @beyondnet/evolith-mcp@1.1.0 "Security fixes in 1.2.0 — see CHANGELOG"`, likewise for `evolith-cli@1.1.0` and `evolith-agent-runtime@1.1.0`. **(b) No release gate fails when a security-tagged commit is absent from the published tag.** That absence is exactly what let the wave sit unpublished from 2026-07-23 to 2026-07-27 while `SECURITY.md` declared the 1.1.x line "actively patched". Nothing detected it; an audit did. Related: `GT-623` — release-please derives version bumps from commit messages, and the `security(...)` type used by 2 of the last 60 commits is not a Conventional Commits type, so it contributes nothing to a bump. Both defects let a security change fail to reach a version, by different routes. | | | `Infra` | Cross | P1 | S | `PENDING` | | [`GT-623`](./gap-reference-catalog.md#gt-623) | **The hook exists, is wired, and cannot enforce anything.** `.husky/commit-msg` runs `npx --no -- commitlint --version`; when that fails it prints `commitlint is not installed — skipping commit message lint` and exits **successfully**. Verified: `commitlint` appears in neither `dependencies` nor `devDependencies` of the root `package.json`, there is no `commitlint.config.*` or `.commitlintrc*`, and no `commitlint` key in `package.json`. So the else-branch is the only branch that ever runs, on every commit. Observed live on 2026-07-27 while merging `develop` into a feature branch. **What depends on the convention it does not enforce:** `CONTRIBUTING.md` and `.github/pull_request_template.md` mandate Conventional Commits in three places, and — the part that costs money — **release-please derives version bumps from commit messages**, wired into `sdk-cli-release.yml` and `sdk-cli-ci.yml`. **It is already drifting, with a consequence:** 2 of the last 60 non-merge commits use the type `security(...)` (`security(fase-7): add Docker/K8s hardening checklist`, `security(fase-6): add executable security rulesets`), which is not a Conventional Commits type. release-please does not recognise it, so **a commit that announces itself as a security change contributes nothing to the version bump** — which is the same failure mode as [`GT-570`](./gap-reference-catalog.md#gt-570), where a security wave sits unpublished. Fix: install and configure commitlint so the hook takes its real branch, or delete the hook and stop claiming the convention. Failing open is the worst of the three options, because it produces the appearance of enforcement. If the `security` type is wanted, declare it in the config and map it to a bump — do not leave it to a linter that never runs. | | | `Governance` | Cross | P2 | S | `PENDING` | | [`GT-622`](./gap-reference-catalog.md#gt-622) | **Every pull request carries a red `CodeQL` check reading "1 configuration not found", and it is not a security finding — it is orphaned bookkeeping.** GitHub still has **82 code-scanning analyses** on `refs/heads/main` under the analysis key `.github/workflows/ci.yml:codeql`. That configuration was real: commit `87f50ce3` added a `codeql` job to `ci.yml`, and `f50030cd` removed it on 2026-06-06 while gutting that workflow — the last analysis under the key is from that same day. Because the configuration is still *recorded* on `main` but nothing produces it, GitHub reports it missing on every PR. It has done so for 51 days. Verified: `code-scanning/analyses?ref=refs/heads/main` returns three analysis keys — `ci.yml:codeql` (82, last 2026-06-06), `sdk-cli-ci.yml:codeql-analysis` (159, current) and `sdk-cli-ci.yml:trivy-scan` (159, current). The scanning that matters is healthy; `CodeQL SAST` passes and is a required check. **The dead workflow itself is already deleted** (it ran a no-op `Disabled` job on every PR and push to `main` and `develop`); deleting the file does NOT clear the recorded configuration, which is why this row exists. **The remaining action is deliberately not automated:** removing the 82 analyses via `DELETE /repos/{owner}/{repo}/code-scanning/analyses/{id}` is irreversible and destroys code-scanning history on a protected branch. Their historical value is nil — they describe a configuration dead since June — but discarding security-scan history is an owner decision, not a tooling one. **Why it matters beyond the noise:** a permanently red check trains reviewers to discount red checks, and `CodeQL SAST` — which shares the CodeQL name and IS required — is exactly the check nobody can afford to learn to ignore. | | | `Infra` | Cross | P2 | XS | `PENDING` | | [`GT-609`](./gap-reference-catalog.md#gt-609) | **Authorization leak in the discovery surface.** `mcp-cache.service.ts:8` declares `toolsList: 'mcp:tools:list'` — a single literal key, with no principal, tenant or scope in it — and the list is cached BEFORE the scope filter runs. So the first caller to warm the cache decides the inventory every subsequent caller sees for the TTL: an admin warming it publishes the write-capable inventory to readers. **Verificado aquí contra el código.** Fix: key the cache by principal (a hash of scopes + tenant), or delete the cache — a discovery surface that answers from another principal's view is worse than an uncached one. Origin: finding 4.2 of the product diagnostic (https://github.com/beyondnetcode/why-architecture/blob/main/docs/evolith-diagnostico-es.md). | | | `MCP Server` | Cross | P0 | S | `PENDING` | @@ -43,7 +44,7 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-600`](./gap-reference-catalog.md#gt-600) | **Registered as an opportunity, not as debt** — separable from [`GT-596`](./gap-reference-catalog.md#gt-596)…[`GT-599`](./gap-reference-catalog.md#gt-599), which stand on their own as internal governance. Four of the five artifacts adopted there are implementable as Evolith rulesets: **ISO/IEC 5055:2021** structural weaknesses, **NIST SP 800-218 (SSDF) v1.1** practices, **SLSA** build levels, and the checks **OpenSSF Scorecard** scores. That converts the audit's remediation into a shippable capability, and it addresses the sharpest product finding on the board: the 2026-07-26 audit found **zero** occurrences of an ICP across 929 English documents, and 0 stars / forks / external issues in 80 public days. "I evaluate your repository against ISO 5055, SSDF and SLSA and give you your OpenSSF score" names a buyer, a budget line and a compliance trigger; "I govern your architecture" does not. It also composes with the wedge already implemented in code (`enforce edit` + the editor hook), and self-evaluating against these standards is simultaneously the fix for [`GT-597`](./gap-reference-catalog.md#gt-597) and the best available sales demo. **Decision-gated, not ready to build:** it presumes the CLI wedge is the monetisation vehicle rather than the Tracker, which is an unresolved contradiction the audit flagged and the owner has to settle first. | | | `Evolith Core` | Cross | P2 | L | `PENDING` | | [`GT-579`](./gap-reference-catalog.md#gt-579) | **Found while verifying the GT-569 remediation, not by the audit.** Node buffers writes to a piped stdout asynchronously, so the ~12 `process.exit()` calls across the command graph discarded whatever had not been flushed — truncating output at the OS pipe buffer (65,536 bytes on macOS/Linux). Measured: `validate --format json \| wc -c` returned **exactly 65536** while the same command redirected to a file produced 121,408 valid bytes. Every envelope under 64 KiB hid the bug, which is why it survived; GT-569 enlarged the envelope and exposed it. Six CLI e2e tests were failing on `JSON.parse` at position 65262 and read as a GT-569 regression — the regression was pre-existing and unconditional for any large envelope. **DONE (`44fe8dd3`):** fixed centrally in `src/sdk/cli/src/main.ts` (`makeStdioBlocking`, guarded because `_handle.setBlocking` is internal) rather than at each exit site, so a new command cannot reintroduce it by exiting the ordinary way. Verified: a 123,506-byte envelope now traverses a real pipe and parses; CLI 1305+132 green. | | | `Evolith CLI` | Cross | P0 | XS | `DONE` | | [`GT-569`](./gap-reference-catalog.md#gt-569) | **The product's headline number silently redefines its own denominator.** Of 379 rules the native engine evaluates 108 and reports `rulesChecked: 111`; the remaining 271 — **192 of them `blocking`** — return `skipped` and are filtered out at `ruleset-validator.service.ts:88` (`engineResults.filter(r => r.result !== 'skipped')`) before the count is summed. No field surfaces it: `grep -rn "rulesSkipped" src --include='*.ts'` returns 0 results and `ValidationResult` carries only `{status, rulesChecked, issues, coreRef, timestamp}`. The same field also OVER-counts: `validate --format json --core --engine opa` returns `rulesChecked: 379` having executed **zero** policies (the wasm does not resolve against the Core layout). And `native-evaluator.ts:69-72` converts any handler exception into `skipped`, so **a crashing evaluator is indistinguishable from a green rule**. Fix: add `rulesSkipped` + the skipped-id array to `ValidationResult`, introduce an `errored` state distinct from `skipped`, emit a WARNING issue per skipped MUST rule, and fail the run when `skipped/total` exceeds a configured threshold. This is a reporting change, not an engine change — closing the handler gap itself is separate and much larger. **DONE (`44fe8dd3`):** new `errored` outcome (a handler exception no longer becomes `skipped`, so a crash is distinguishable from a green rule); `rulesSkipped`/`rulesErrored`/`rulesTotal` + id arrays added ADDITIVELY with `rulesChecked` untouched; advisory issues for skipped MUST rules; opt-in `maxSkippedFraction` gate. On this repo `validate` now reports 269 skipped of 380 instead of silently recutting its denominator. Verified: core-domain 1237/1237, CLI 1305+132, contract 43/43 after adding the five `WIRE_VALIDATION_RESULT` entries. Falsified against a pristine `git archive HEAD` copy: 12 of the 17 new tests fail without the fix. **Closing the handler-coverage gap itself (≈240 unevaluable rules) is NOT part of this and stays open** — this row was about the denominator being honest, not about widening it. | | | `core-domain` | Cross | P0 | M | `DONE` | -| [`GT-570`](./gap-reference-catalog.md#gt-570) | **npm serves 1.1.0 published 2026-07-18; the security wave landed 2026-07-23.** Verified with `npm view @beyondnet/evolith-cli version time.modified`. The public `CHANGELOG.md` enumerates the corrected files by name under `[Unreleased]`, and `SECURITY.md` declares the 1.1.x line "Current stable line — actively patched". Anyone following the README installs the unpatched build from a repository that publishes where the holes are. Fix: publish **1.2.0** with the security wave, deprecate 1.1.0 on npm with a message pointing at the fixed version, move the security section from `[Unreleased]` to the published heading, and issue the advisory `SECURITY.md` already promises. Follow-on: a release gate that fails when HEAD carries security-tagged commits absent from the last published tag. | | | `Infra` | Cross | P0 | S | `PENDING` | +| [`GT-570`](./gap-reference-catalog.md#gt-570) | **npm serves 1.1.0 published 2026-07-18; the security wave landed 2026-07-23.** Verified with `npm view @beyondnet/evolith-cli version time.modified`. The public `CHANGELOG.md` enumerates the corrected files by name under `[Unreleased]`, and `SECURITY.md` declares the 1.1.x line "Current stable line — actively patched". Anyone following the README installs the unpatched build from a repository that publishes where the holes are. Fix: publish **1.2.0** with the security wave, deprecate 1.1.0 on npm with a message pointing at the fixed version, move the security section from `[Unreleased]` to the published heading, and issue the advisory `SECURITY.md` already promises. Follow-on: a release gate that fails when HEAD carries security-tagged commits absent from the last published tag. **DONE (run 30273290417, `main` @ `9a82d9f2`, 2026-07-27).** The published artifact no longer predates the security wave. Verified against the REGISTRY, not against an exit code — `@beyondnet/evolith-agent-runtime@1.2.0` (14:06:10), `@beyondnet/evolith-sdk@2.0.0` (14:06:24), `@beyondnet/evolith-mcp@1.2.0` (14:06:39), `@beyondnet/evolith-cli@1.2.0` (14:06:58) — timestamps that also confirm dependency order, since the last three consume the first. **All four carry `dist.attestations`**, which is the substance of this gap: the audit's finding was 0 of 8 packages with provenance, and these were produced by GitHub Actions with `id-token: write`, not by a developer machine. **The scope was corrected before shipping:** the plan was to bump mcp alone, which came from `git show --stat` TRUNCATING long paths — `--name-only` showed the wave touches three published packages (mcp 15 files, cli 7, agent-runtime 1: the CWE-78 shell runner). Publishing the original plan would have left two vulnerable and closed this gap against them. **Two rehearsals paid for themselves:** run 30265298045 found that none of the four tarballs carried a LICENSE on an MIT project and that `repository.url` was auto-corrected in all eight; run 30271837941 found `bin` paths auto-corrected in two. All three would have been permanent — npm forbids unpublishing after 72 hours. **RE-SCOPED, and the remainder is NOT silently dropped:** deprecating the 1.1.0 versions requires npm credentials and is an owner action, and the release gate that would fail when security-tagged commits are absent from the published tag was never built. Both moved to `GT-624`. This row closes on what it was actually about — an installable artifact older than its own published security fixes — and says plainly what it did not do. | | | `Infra` | Cross | P0 | S | `DONE` | | [`GT-571`](./gap-reference-catalog.md#gt-571) | **The highest-traffic surface of the product does not work as written.** (a) `README.md:96-98` (and `README.es.md:95-97`) instruct `evolith init` / `evolith validate`, but the published bin map declares only `evolith-cli` and `evolith-mcp` — the phantom command appears in **447 invocations across 49 non-`.es` markdown files**, so renaming the bin is cheaper than rewriting the docs. (b) Even with the alias, `init --name my-sat` creates a **subdirectory**, so the `validate` that follows in the same cwd targets the parent and raises `GOV-000` "Missing evolith.yaml" plus 41 blocking findings. Inside the correct satellite the first validate still returns 46 findings / 39 blocking, dominated by the vendor's own monorepo rules (`CLI-RR-01` "dist/main.js not found", `TAX-05` "Missing top-level directories: sdk, .harness") with rules from all 8 topologies firing on a repo declared phase-0. Additionally the published binary self-identifies as `main` (`evolith-cli init --help` prints `Usage: main init [options]`) because the program name is never set. **IN-PROGRESS (`44fe8dd3`):** the three CLI-side defects are fixed and tested — `evolith` published alongside `evolith-cli` in the bin map, the program name set so `--help` no longer says `main`, and `init` given a defined non-interactive contract (initializes the CURRENT directory, `--name` is the project name only, `evolith init ` for a subdirectory; a closed stdin no longer prompts, failure sets a non-zero exit code, and `--format json` emits a parseable envelope and nothing else). **NOT closed on two criteria:** "a freshly initialized repo returns 0 blocking findings" is untouched — those 39 findings come from the vendor's own `CLI-RR-*`/`TAX-*` rules firing on a phase-0 satellite, which needs rule `audience`/topology gating in the ruleset path; and "`npx @beyondnet/evolith-cli@latest` works" cannot be true until a release ships (`GT-570`). **+2026-07-26 (wave 3) — criterion 2 of 3 MET, still open on criterion 1.** A freshly initialized satellite now returns **0 blocking** findings (was 35: CLI-RR-01/02, TAX-05/06, DEP-01/10, CB-VAL-01 plus rules from 7 topologies the repo never declared). Two mechanisms in the ruleset layer: an `audience: core\|satellite\|both` annotation (default `both`, so an unannotated rule keeps applying everywhere) marking the families that only address the vendor monorepo, and `topologies` + `appliesFromSdlcPhase` gating read from the satellite's declared phase and topology. **The GT-569 interaction was decided explicitly:** a rule addressed to somebody else is a PRE-FILTER, not a fourth outcome — it never reaches the evaluator, so `rulesChecked + rulesSkipped + rulesErrored === rulesTotal` still holds literally. Calling it `skipped` would inflate the unevaluated fraction of a healthy repo and could trip `maxSkippedFraction`. The exclusion is still visible: `rulesNotApplicable` / `notApplicableRuleIds` over a new `corpusTotal`, plus a non-blocking `GOV-RULE-NOT-APPLICABLE` advisory naming the counts and how to bring the rules into scope. Fresh satellite before/after: 379 total / 108 checked / 35 blocking / `failed` → 274 total / 41 checked / 233 skipped / 105 not-applicable / corpusTotal 379 / **0 blocking** / `warning`. **Still open:** criterion 1 (`npx @beyondnet/evolith-cli@latest` in a clean container) cannot be true until a release ships — blocked on `GT-570`. | | | `Evolith CLI` | Cross | P0 | S | `IN-PROGRESS` | | [`GT-572`](./gap-reference-catalog.md#gt-572) | **Reproduced against the published tarball, not the working tree.** `npm pack @beyondnet/evolith-mcp@1.1.0`, started over stdio without auth: 47 tools announced, **47 of 47 return FORBIDDEN (ABAC-02)**; `resources/list` (11) and `prompts/list` (8) do work. The two escape routes the code defines — `--allow-no-auth` (`main.ts:62-63`) and `EVOLITH_MCP_ALLOW_NO_AUTH=true`, both documented in `mcp-server-auth.ts` — were tested in all three combinations including `NODE_ENV=development` and **none of them has any effect on the default transport**, which is worse than their absence. Root cause: `mcpContextStorage.run` exists in exactly one non-spec site (`mcp-server.service.ts:451`) inside the HTTP dispatch closure, so the stdio path never establishes a context. Neither oracle can see it: the CI smoke asserts `success !== undefined`, and the exploratory tester drives MCP over HTTP only. Fix: wrap the stdio dispatch in `mcpContextStorage` with a local session context, and make the smoke assert a real invocation with a verdict. **IN-PROGRESS (`44fe8dd3`):** root cause confirmed and fixed — the stdio path never entered `mcpContextStorage`, so every `tools/call` reached dispatch as the anonymous `roles: []` fallback and native ABAC denied it. stdio now establishes an explicit local-session principal; the HTTP path is untouched and stays fail-closed. **Verified against the built server, not a report:** with `NODE_ENV` unset (the documented default, and what `npx`/Claude Desktop produce) `tools/call` returns a real `success: true` payload; 50 tools announced. **Why this stays open:** under `NODE_ENV=production` the same call is still FORBIDDEN, and `NODE_ENV=production` is exactly what the Docker images set — so a containerised stdio deployment remains unusable. That is now a narrow, documented, fail-closed-by-design decision rather than a silent 100% failure, but it is not closed. The CI smoke at `src/sdk/cli/examples/mcp-test.js` still asserts `success !== undefined`, which a FORBIDDEN envelope also satisfies; strengthening it is out-of-area follow-on. **+2026-07-26 (wave 3) — the production failure mode is fixed; 2 of 3 criteria still unmet, and the row is NOT closable yet.** stdio under `NODE_ENV=production` no longer boots into a state where all 50 tools deny: the server now refuses to start when it has no credentials, naming the variable to set, instead of advertising a surface that will reject every call; a denial that does happen says why and what to do rather than a bare ABAC-02. HTTP was not touched and stays fail-closed; no bypass was added, ABAC was not widened. **The agent reported this gap `done` at 7/7 against its own task list, which is not the same as this row's criteria — checked, and it is not.** Criterion 1 asks for a real `tools/call` **against the published tarball**; the published tarball is 1.1.0, which predates every fix here, so that criterion is blocked on `GT-570` exactly like GT-571's. Criterion 3 asks the CI smoke to assert a verdict; `src/sdk/cli/examples/mcp-test.js:284,375` still asserts `success !== undefined`, which a FORBIDDEN envelope satisfies too — out of the agent's owned area and still unchanged. | | | `MCP Server` | Cross | P0 | M | `IN-PROGRESS` | | [`GT-573`](./gap-reference-catalog.md#gt-573) | **The flagship integration fails silently, with both CIs green.** The inline branch returns the legacy envelope: `evaluation.controller.ts:186` returns `evaluationVerdict!.outputEnvelope`, built at `satellite-evaluation-pipeline.service.ts:85-94` as `createSuccessEnvelope({topology, gates, summary})`. The Tracker unwraps `data` (`CoreEvaluationGateway.cs:433`) and binds `CoreEvaluationEnvelope`, which declares only `overallVerdict / outcome / resolvedTopology / results.gate[] / evaluatedAt` (`CoreEvaluationDtos.cs:200-216`). `Passed` stays null (`:406`), `Gates` stays empty (`:414`), and `ToDecision` (`:583-606`) falls through to **`"SKIPPED"`**, written as `decision=SKIPPED, status=COMPLETED`. 0 of 12 Core workflows build the Tracker; 0 contract tests exist in either repo. Fix: route the inline branch through `EvaluationOrchestrator` so it returns the canonical `EvaluationResult`, and add a consumer-driven contract test in the Core CI asserting the exact JSON `CoreEvaluationEnvelope` binds; publish those request/response pairs as fixtures inside `@beyondnet/evolith-contracts` and promote both schemas to `MACHINE_CONTRACT_SET`. **IN-PROGRESS (`44fe8dd3`):** the inline branch no longer hand-builds the legacy `{topology, gates, summary}` envelope — it returns the canonical `EvaluationResult`, so there is ONE shape for the operation rather than two, and a consumer-driven contract spec asserts the exact keys the Tracker binds for both a passing and a FAILING evaluation. Request/response fixtures are published inside `@beyondnet/evolith-contracts` and both schemas were added to `MACHINE_CONTRACT_SET`. Backwards compatibility was checked rather than assumed: no legacy in-repo inline caller exists (both consumers already unwrap `data` as an `EvaluationResult`). **NOT closed:** the Tracker lives in another repository and must re-pin the contracts package for the round trip to be proven end to end, and the contract spec's FAIL case exercises a governance-rule failure rather than an OPA-gate failure. Verified: core-api 163/163, contracts 32/32. | | | `Core API` | Cross | P0 | M | `IN-PROGRESS` | @@ -638,7 +639,7 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-246`](./gap-reference-catalog.md#gt-246) | Implement Chaos Mesh/Litmus experiments | | | `QA` | Cross | P3 | L | `DONE` | -**Progress:** 561 / 623 done · 9 in progress · 49 pending · 4 deferred +**Progress:** 562 / 624 done · 9 in progress · 49 pending · 4 deferred **Wave 2026-06-23 (Winston deep audit III):** Added 14 new gaps `GT-212`…`GT-225` from the Winston Audit Playbook covering: ADR status hygiene (GT-212), topology manifest metadata + operational budgets + guidance corpus (GT-213, GT-217, GT-219), REST controller observability + OpenAPI (GT-214, GT-215), OPA input-schema parity + per-topology test density (GT-216, GT-222), SDLC Phase 05 rollback + on-call templates (GT-218), CLI branch coverage + envelope format coverage + skip-list cleanup (GT-220, GT-224, GT-225), MCP HTTP audit logging (GT-221), and cross-surface parity e2e tests (GT-223). diff --git a/reference/core/control-center/maturity-reports/executive-summary.es.md b/reference/core/control-center/maturity-reports/executive-summary.es.md index b8bcaac7..7de80f4d 100644 --- a/reference/core/control-center/maturity-reports/executive-summary.es.md +++ b/reference/core/control-center/maturity-reports/executive-summary.es.md @@ -13,7 +13,7 @@ Instantánea estratégica generada desde el tablero canónico de gaps y la recon **Mayor problema ahora:** `Evolith Core` concentra el mayor riesgo abierto ponderado (10 pendientes, 1 P0). Ataca esa concentración antes de ampliar alcance. -**Dónde atacar primero:** [GT-570](../gaps/gap-reference-catalog.es.md#gt-570), [GT-571](../gaps/gap-reference-catalog.es.md#gt-571), [GT-575](../gaps/gap-reference-catalog.es.md#gt-575), [GT-601](../gaps/gap-reference-catalog.es.md#gt-601), [GT-609](../gaps/gap-reference-catalog.es.md#gt-609), [GT-610](../gaps/gap-reference-catalog.es.md#gt-610), [GT-572](../gaps/gap-reference-catalog.es.md#gt-572), [GT-573](../gaps/gap-reference-catalog.es.md#gt-573), [GT-602](../gaps/gap-reference-catalog.es.md#gt-602), [GT-603](../gaps/gap-reference-catalog.es.md#gt-603), +3. +**Dónde atacar primero:** [GT-571](../gaps/gap-reference-catalog.es.md#gt-571), [GT-575](../gaps/gap-reference-catalog.es.md#gt-575), [GT-601](../gaps/gap-reference-catalog.es.md#gt-601), [GT-609](../gaps/gap-reference-catalog.es.md#gt-609), [GT-610](../gaps/gap-reference-catalog.es.md#gt-610), [GT-572](../gaps/gap-reference-catalog.es.md#gt-572), [GT-573](../gaps/gap-reference-catalog.es.md#gt-573), [GT-602](../gaps/gap-reference-catalog.es.md#gt-602), [GT-603](../gaps/gap-reference-catalog.es.md#gt-603), [GT-595](../gaps/gap-reference-catalog.es.md#gt-595), +2. ## Diagnóstico Estratégico @@ -25,17 +25,16 @@ La forma correcta de usar este resumen es simple: si necesitas contexto, abre so | Orden | Foco | Motivo | IDs | |---:|---|---|---| -| 1 | Bloqueadores P0 | Impiden afirmar readiness productivo o release mayor. | [GT-570](../gaps/gap-reference-catalog.es.md#gt-570), [GT-571](../gaps/gap-reference-catalog.es.md#gt-571), [GT-575](../gaps/gap-reference-catalog.es.md#gt-575), [GT-601](../gaps/gap-reference-catalog.es.md#gt-601), [GT-609](../gaps/gap-reference-catalog.es.md#gt-609), [GT-610](../gaps/gap-reference-catalog.es.md#gt-610), [GT-572](../gaps/gap-reference-catalog.es.md#gt-572), [GT-573](../gaps/gap-reference-catalog.es.md#gt-573), [GT-602](../gaps/gap-reference-catalog.es.md#gt-602), [GT-603](../gaps/gap-reference-catalog.es.md#gt-603), +3 | +| 1 | Bloqueadores P0 | Impiden afirmar readiness productivo o release mayor. | [GT-571](../gaps/gap-reference-catalog.es.md#gt-571), [GT-575](../gaps/gap-reference-catalog.es.md#gt-575), [GT-601](../gaps/gap-reference-catalog.es.md#gt-601), [GT-609](../gaps/gap-reference-catalog.es.md#gt-609), [GT-610](../gaps/gap-reference-catalog.es.md#gt-610), [GT-572](../gaps/gap-reference-catalog.es.md#gt-572), [GT-573](../gaps/gap-reference-catalog.es.md#gt-573), [GT-602](../gaps/gap-reference-catalog.es.md#gt-602), [GT-603](../gaps/gap-reference-catalog.es.md#gt-603), [GT-595](../gaps/gap-reference-catalog.es.md#gt-595), +2 | | 2 | Área de mayor riesgo | `Evolith Core` tiene la mayor carga ponderada abierta. | [GT-601](../gaps/gap-reference-catalog.es.md#gt-601), [GT-583](../gaps/gap-reference-catalog.es.md#gt-583), [GT-589](../gaps/gap-reference-catalog.es.md#gt-589), [GT-598](../gaps/gap-reference-catalog.es.md#gt-598), [GT-614](../gaps/gap-reference-catalog.es.md#gt-614), [GT-587](../gaps/gap-reference-catalog.es.md#gt-587), +4 | -| 3 | Ganancias rápidas | Alta criticidad con complejidad XS/S. | [GT-570](../gaps/gap-reference-catalog.es.md#gt-570), [GT-571](../gaps/gap-reference-catalog.es.md#gt-571), [GT-575](../gaps/gap-reference-catalog.es.md#gt-575), [GT-601](../gaps/gap-reference-catalog.es.md#gt-601), [GT-609](../gaps/gap-reference-catalog.es.md#gt-609), [GT-610](../gaps/gap-reference-catalog.es.md#gt-610), [GT-586](../gaps/gap-reference-catalog.es.md#gt-586), [GT-597](../gaps/gap-reference-catalog.es.md#gt-597) | -| 4 | Ola P1 | Endurecimiento siguiente después de limpiar P0. | [GT-586](../gaps/gap-reference-catalog.es.md#gt-586), [GT-597](../gaps/gap-reference-catalog.es.md#gt-597), [GT-607](../gaps/gap-reference-catalog.es.md#gt-607), [GT-618](../gaps/gap-reference-catalog.es.md#gt-618), [GT-620](../gaps/gap-reference-catalog.es.md#gt-620), [GT-324](../gaps/gap-reference-catalog.es.md#gt-324), [GT-578](../gaps/gap-reference-catalog.es.md#gt-578), [GT-580](../gaps/gap-reference-catalog.es.md#gt-580), +14 | +| 3 | Ganancias rápidas | Alta criticidad con complejidad XS/S. | [GT-571](../gaps/gap-reference-catalog.es.md#gt-571), [GT-575](../gaps/gap-reference-catalog.es.md#gt-575), [GT-601](../gaps/gap-reference-catalog.es.md#gt-601), [GT-609](../gaps/gap-reference-catalog.es.md#gt-609), [GT-610](../gaps/gap-reference-catalog.es.md#gt-610), [GT-586](../gaps/gap-reference-catalog.es.md#gt-586), [GT-597](../gaps/gap-reference-catalog.es.md#gt-597), [GT-607](../gaps/gap-reference-catalog.es.md#gt-607) | +| 4 | Ola P1 | Endurecimiento siguiente después de limpiar P0. | [GT-586](../gaps/gap-reference-catalog.es.md#gt-586), [GT-597](../gaps/gap-reference-catalog.es.md#gt-597), [GT-607](../gaps/gap-reference-catalog.es.md#gt-607), [GT-618](../gaps/gap-reference-catalog.es.md#gt-618), [GT-620](../gaps/gap-reference-catalog.es.md#gt-620), [GT-624](../gaps/gap-reference-catalog.es.md#gt-624), [GT-324](../gaps/gap-reference-catalog.es.md#gt-324), [GT-578](../gaps/gap-reference-catalog.es.md#gt-578), +15 | | 5 | P2/P3 | Solo después de estabilizar seguridad, CI, reglas y contratos. | [GT-619](../gaps/gap-reference-catalog.es.md#gt-619), [GT-622](../gaps/gap-reference-catalog.es.md#gt-622), [GT-424](../gaps/gap-reference-catalog.es.md#gt-424), [GT-444](../gaps/gap-reference-catalog.es.md#gt-444), [GT-464](../gaps/gap-reference-catalog.es.md#gt-464), [GT-614](../gaps/gap-reference-catalog.es.md#gt-614), +17 | ## Bloqueadores Actuales | ID | Ataque | Componente | Esfuerzo | |---|---|---|---| -| [GT-570](../gaps/gap-reference-catalog.es.md#gt-570) | **npm sirve 1.1.0 publicada el 2026-07-18; la ola de seguridad aterrizó el 2026-07-23.** Verificado con npm view @beyondnet/evolith-cli version time.modified. El CHANGELOG.md público enumera los ficheros corregidos por nombre bajo [Unreleased], y SECURITY.md declara la línea 1.1.x "Current stable line — actively patched". Quien siga el README instala la build sin parchear desde un repositorio que publica dónde están los agujeros. Fix: publicar **1.2.0** con la ola de seguridad, deprecar 1.1.0 en npm con un mensaje que apunte a la versión corregida, mover la sección de seguridad de [Unreleased] al heading publicado, y emitir el advisory que SECURITY.md ya promete. Follow-on: un gate de release que falle cuando HEAD contenga commits etiquetados de seguridad ausentes del último tag publicado. | `Infra` | P0/S | | [GT-571](../gaps/gap-reference-catalog.es.md#gt-571) | **La superficie de mayor tráfico del producto no funciona tal como está escrita.** (a) README.md:96-98 (y README.es.md:95-97) mandan evolith init / evolith validate, pero el bin map publicado solo declara evolith-cli y evolith-mcp — el comando fantasma aparece en **447 invocaciones en 49 ficheros markdown no-.es**, así que renombrar el bin sale más barato que reescribir la doc. (b) Aun con el alias, init --name my-sat crea un **subdirectorio**, así que el validate que sigue en el mismo cwd apunta al padre y dispara GOV-000 "Missing evolith.yaml" más 41 hallazgos blocking. Dentro del satélite correcto el primer validate sigue devolviendo 46 hallazgos / 39 blocking, dominados por las reglas internas del monorepo del vendedor (CLI-RR-01 "dist/main.js not found", TAX-05 "Missing top-level directories: sdk, .harness") con reglas de las 8 topologías disparando sobre un repo declarado fase-0. Además el binario publicado se autoidentifica como main (evolith-cli init --help imprime Usage: main init [options]) porque nunca se fija el program name. **EN-PROGRESO (44fe8dd3):** los tres defectos del lado CLI están arreglados y probados — evolith publicado junto a evolith-cli en el bin map, el program name fijado para que --help ya no diga main, e init con un contrato no-interactivo definido (inicializa el directorio ACTUAL, --name es solo el nombre del proyecto, evolith init para un subdirectorio; un stdin cerrado ya no pregunta, el fallo fija un exit code distinto de cero, y --format json emite un envelope parseable y nada más). **NO cerrado en dos criterios:** "un repo recién inicializado devuelve 0 hallazgos blocking" está intacto — esos 39 hallazgos vienen de las reglas CLI-RR-*/TAX-* del propio vendedor disparando sobre un satélite fase-0, y requieren gating por audience/topología en el camino de rulesets; y "npx @beyondnet/evolith-cli@latest funciona" no puede ser cierto hasta que se publique una release (GT-570). **+2026-07-26 (ola 3) — criterio 2 de 3 CUMPLIDO, sigue abierto por el criterio 1.** Un satélite recién inicializado devuelve ahora **0 hallazgos blocking** (eran 35: CLI-RR-01/02, TAX-05/06, DEP-01/10, CB-VAL-01 más reglas de 7 topologías que el repo nunca declaró). Dos mecanismos en la capa de rulesets: una anotación audience: core|satellite|both (por defecto both, así que una regla sin anotar sigue aplicando en todas partes) que marca las familias que sólo se dirigen al monorepo del vendedor, y gating por topologies + appliesFromSdlcPhase leído de la fase y topología declaradas del satélite. **La interacción con GT-569 se decidió explícitamente:** una regla dirigida a otro es un PRE-FILTRO, no un cuarto resultado — nunca llega al evaluador, así que rulesChecked + rulesSkipped + rulesErrored === rulesTotal se sigue cumpliendo literalmente. Llamarla skipped inflaría la fracción no evaluada de un repo sano y podría disparar maxSkippedFraction. La exclusión sigue siendo visible: rulesNotApplicable / notApplicableRuleIds sobre un nuevo corpusTotal, más un advisory no bloqueante GOV-RULE-NOT-APPLICABLE que nombra los conteos y cómo meter las reglas en alcance. Satélite nuevo antes/después: 379 totales / 108 comprobadas / 35 blocking / failed → 274 totales / 41 comprobadas / 233 saltadas / 105 no aplicables / corpusTotal 379 / **0 blocking** / warning. **Sigue abierto:** el criterio 1 (npx @beyondnet/evolith-cli@latest en un contenedor limpio) no puede ser cierto hasta que se publique una release — bloqueado por GT-570. | `Evolith CLI` | P0/S | | [GT-575](../gaps/gap-reference-catalog.es.md#gt-575) | **Un producto que vende gobernanza de IA envía su único camino de egress LLM sin ninguno de los controles que vende.** GeminiProvider.ts:17 construye la URL con la API key en la query string; el fichero entero de 57 líneas no tiene AbortSignal (:31-37), ni presupuesto, ni redacción, ni log ni métrica, y su única validación de salida es JSON.parse(candidate) as T (:52). Es export público (src/packages/agent-runtime/src/index.ts:22) de @beyondnet/evolith-agent-runtime@1.1.0. Disclosure en README.md, README.es.md, SECURITY.md y los 8 READMEs de paquete: cero. Incumple al menos 4 de las 9 reglas AAI-* blocking que el propio producto vende. **La exposición es latente, no activa** — el único llamador in-tree es src/sdk/cli/src/commands/plan/index.ts:27, y PlanCommand no está registrado en app.module.ts — pero está en la superficie pública que un revisor de seguridad lee primero. La implementación correcta ya existe en casa: .harness/scripts/ci/agentic/review-provider.mjs:35-38 pone la key en un header con el comentario literal "API key in a header, not the URL query string", con topes de presupuesto y 8 patrones de redacción. Fix: portar ese control, y colapsar el puerto duplicado ILLMProvider dentro del seam gobernado IAssistantTransport/SupervisedAssistantClient. **EN-PROGRESO (44fe8dd3):** los controles que el repositorio ya ejecuta contra sí mismo en .harness/scripts/ci/agentic/ se portaron al provider que se envía en vez de reinventarse — API key movida de la query string de la URL al header x-goog-api-key, timeout con AbortController, presupuesto de bytes/tokens que falla cerrado, redacción de secretos, salida validada contra schema, y un log/métrica auditable. El egress ahora está **apagado salvo EVOLITH_LLM_EGRESS=true**. **NO cerrado:** convertir un export publicado siempre-activo en opt-in es una rotura de comportamiento para @beyondnet/evolith-agent-runtime@1.1.0 (los nombres siguen siendo aditivos, así que el congelamiento de contrato de GT-388 se sostiene, pero el comportamiento observable cambió) y necesita una decisión deliberada de versión junto a GT-570; el puerto duplicado ILLMProvider aún no está colapsado del todo dentro del seam gobernado IAssistantTransport/SupervisedAssistantClient; y los READMEs de paquete en español siguen sin disclosure. Verificado: agent-runtime 309/309. **+2026-07-26 (ola 3) — 2 de 3 criterios cumplidos.** Se colapsó el puerto LLM duplicado: GeminiProvider sólo es alcanzable ya como IAssistantTransport detrás del seam supervisado, fail-closed y apagado por defecto, así que hay un único camino gobernado a un LLM en vez de dos con posturas opuestas. La disclosure de egress en español se escribió en src/packages/agent-runtime/README.es.md, reflejando estructuralmente la inglesa. **Sigue abierto:** el criterio 3 — el repositorio no pasa sus propias 9 reglas AAI-* blocking en un check de CI, y sigue sin existir un agent.config.json raíz, así que la topología agentic-ai nunca se evalúa contra el propio Evolith. También sin resolver y dependiente del dueño: convertir un export publicado siempre-activo en opt-in es una rotura de comportamiento para @beyondnet/evolith-agent-runtime@1.1.0 y necesita una decisión de versión junto a GT-570. src/sdk/cli/README.es.md sigue sin disclosure (fuera del área del agente). | `agent-runtime` | P0/S | | [GT-601](../gaps/gap-reference-catalog.es.md#gt-601) | **canonical-result.mapper.ts escribe tres campos de trazabilidad como arrays vacíos incondicionales y fija el motor a mano — y dos consumidores los leen.** :130 rulesExecuted: [], :134 missingEvidence: [], :104/:133 risks: [] son literales en cada evaluación real, y :72 fija engine: 'opa' en toda referencia de policiesApplied corriera NativeEvaluator u OpaEvaluator. sarif-exporter.ts:256 y drift-gate.ts:203 derivan evaluatedRules de rulesExecuted. Consecuencia: **todo SARIF y todo manifiesto de evidencia del drift-gate que se emite hoy dice "0 reglas evaluadas"**, y en un producto cuya paridad dual es argumento de venta el único artefacto que la probaría atribuye mal la mitad de sus ejecuciones. El contrato de evidencia EVD-01..04 queda satisfecho en la forma y vacío en el fondo: el grafo de auditoría acumulado se escribe en blanco desde el origen. | `Evolith Core` | P0/S | @@ -54,14 +53,14 @@ La forma correcta de usar este resumen es simple: si necesitas contexto, abre so | Indicador | Valor | |---|---:| | Fecha canónica del tablero | 2026-07-26 | -| Gaps totales | 623 | -| Gaps cerrados | 561 | +| Gaps totales | 624 | +| Gaps cerrados | 562 | | Gaps pendientes | 62 | -| P0 abiertos | 13 | -| P1 abiertos | 22 | +| P0 abiertos | 12 | +| P1 abiertos | 23 | | P2 abiertos | 23 | -| Cierre total | 90% | -| Registros de evidencia de cierre | 543 | +| Cierre total | 90.1% | +| Registros de evidencia de cierre | 544 | | Readiness registrado | 4 PASS | | Área | Pendientes | P0 | P1 | Primeros IDs | @@ -70,7 +69,7 @@ La forma correcta de usar este resumen es simple: si necesitas contexto, abre so | `MCP Server` | 5 | 2 | 2 | [GT-609](../gaps/gap-reference-catalog.es.md#gt-609), [GT-572](../gaps/gap-reference-catalog.es.md#gt-572), [GT-581](../gaps/gap-reference-catalog.es.md#gt-581), [GT-582](../gaps/gap-reference-catalog.es.md#gt-582), +1 | | `agent-runtime` | 7 | 2 | 1 | [GT-575](../gaps/gap-reference-catalog.es.md#gt-575), [GT-610](../gaps/gap-reference-catalog.es.md#gt-610), [GT-612](../gaps/gap-reference-catalog.es.md#gt-612), [GT-531](../gaps/gap-reference-catalog.es.md#gt-531), +3 | | `Evolith CLI` | 5 | 1 | 3 | [GT-571](../gaps/gap-reference-catalog.es.md#gt-571), [GT-618](../gaps/gap-reference-catalog.es.md#gt-618), [GT-580](../gaps/gap-reference-catalog.es.md#gt-580), [GT-611](../gaps/gap-reference-catalog.es.md#gt-611), +1 | -| `Infra` | 5 | 1 | 2 | [GT-570](../gaps/gap-reference-catalog.es.md#gt-570), [GT-597](../gaps/gap-reference-catalog.es.md#gt-597), [GT-324](../gaps/gap-reference-catalog.es.md#gt-324), [GT-622](../gaps/gap-reference-catalog.es.md#gt-622), +1 | +| `core-domain` | 3 | 1 | 2 | [GT-595](../gaps/gap-reference-catalog.es.md#gt-595), [GT-586](../gaps/gap-reference-catalog.es.md#gt-586), [GT-584](../gaps/gap-reference-catalog.es.md#gt-584) | ## Fuente y Regla de Actualización diff --git a/reference/core/control-center/maturity-reports/executive-summary.md b/reference/core/control-center/maturity-reports/executive-summary.md index 9b8a08bd..45b0e604 100644 --- a/reference/core/control-center/maturity-reports/executive-summary.md +++ b/reference/core/control-center/maturity-reports/executive-summary.md @@ -13,7 +13,7 @@ Strategic snapshot generated from the canonical gap board and maturity reconcili **Biggest problem now:** `Evolith Core` carries the highest weighted open risk (10 open, 1 P0). Attack that concentration before expanding scope. -**Where to attack first:** [GT-570](../gaps/gap-reference-catalog.md#gt-570), [GT-571](../gaps/gap-reference-catalog.md#gt-571), [GT-575](../gaps/gap-reference-catalog.md#gt-575), [GT-601](../gaps/gap-reference-catalog.md#gt-601), [GT-609](../gaps/gap-reference-catalog.md#gt-609), [GT-610](../gaps/gap-reference-catalog.md#gt-610), [GT-572](../gaps/gap-reference-catalog.md#gt-572), [GT-573](../gaps/gap-reference-catalog.md#gt-573), [GT-602](../gaps/gap-reference-catalog.md#gt-602), [GT-603](../gaps/gap-reference-catalog.md#gt-603), +3. +**Where to attack first:** [GT-571](../gaps/gap-reference-catalog.md#gt-571), [GT-575](../gaps/gap-reference-catalog.md#gt-575), [GT-601](../gaps/gap-reference-catalog.md#gt-601), [GT-609](../gaps/gap-reference-catalog.md#gt-609), [GT-610](../gaps/gap-reference-catalog.md#gt-610), [GT-572](../gaps/gap-reference-catalog.md#gt-572), [GT-573](../gaps/gap-reference-catalog.md#gt-573), [GT-602](../gaps/gap-reference-catalog.md#gt-602), [GT-603](../gaps/gap-reference-catalog.md#gt-603), [GT-595](../gaps/gap-reference-catalog.md#gt-595), +2. ## Strategic Diagnosis @@ -25,17 +25,16 @@ Use this summary with a simple rule: if you need context, open only the linked I | Order | Focus | Reason | IDs | |---:|---|---|---| -| 1 | P0 blockers | They prevent production-readiness or major-release confidence. | [GT-570](../gaps/gap-reference-catalog.md#gt-570), [GT-571](../gaps/gap-reference-catalog.md#gt-571), [GT-575](../gaps/gap-reference-catalog.md#gt-575), [GT-601](../gaps/gap-reference-catalog.md#gt-601), [GT-609](../gaps/gap-reference-catalog.md#gt-609), [GT-610](../gaps/gap-reference-catalog.md#gt-610), [GT-572](../gaps/gap-reference-catalog.md#gt-572), [GT-573](../gaps/gap-reference-catalog.md#gt-573), [GT-602](../gaps/gap-reference-catalog.md#gt-602), [GT-603](../gaps/gap-reference-catalog.md#gt-603), +3 | +| 1 | P0 blockers | They prevent production-readiness or major-release confidence. | [GT-571](../gaps/gap-reference-catalog.md#gt-571), [GT-575](../gaps/gap-reference-catalog.md#gt-575), [GT-601](../gaps/gap-reference-catalog.md#gt-601), [GT-609](../gaps/gap-reference-catalog.md#gt-609), [GT-610](../gaps/gap-reference-catalog.md#gt-610), [GT-572](../gaps/gap-reference-catalog.md#gt-572), [GT-573](../gaps/gap-reference-catalog.md#gt-573), [GT-602](../gaps/gap-reference-catalog.md#gt-602), [GT-603](../gaps/gap-reference-catalog.md#gt-603), [GT-595](../gaps/gap-reference-catalog.md#gt-595), +2 | | 2 | Highest-risk area | `Evolith Core` has the largest weighted open load. | [GT-601](../gaps/gap-reference-catalog.md#gt-601), [GT-583](../gaps/gap-reference-catalog.md#gt-583), [GT-589](../gaps/gap-reference-catalog.md#gt-589), [GT-598](../gaps/gap-reference-catalog.md#gt-598), [GT-614](../gaps/gap-reference-catalog.md#gt-614), [GT-587](../gaps/gap-reference-catalog.md#gt-587), +4 | -| 3 | Quick wins | High criticality with XS/S complexity. | [GT-570](../gaps/gap-reference-catalog.md#gt-570), [GT-571](../gaps/gap-reference-catalog.md#gt-571), [GT-575](../gaps/gap-reference-catalog.md#gt-575), [GT-601](../gaps/gap-reference-catalog.md#gt-601), [GT-609](../gaps/gap-reference-catalog.md#gt-609), [GT-610](../gaps/gap-reference-catalog.md#gt-610), [GT-586](../gaps/gap-reference-catalog.md#gt-586), [GT-597](../gaps/gap-reference-catalog.md#gt-597) | -| 4 | P1 wave | Next hardening after P0 is cleared. | [GT-586](../gaps/gap-reference-catalog.md#gt-586), [GT-597](../gaps/gap-reference-catalog.md#gt-597), [GT-607](../gaps/gap-reference-catalog.md#gt-607), [GT-618](../gaps/gap-reference-catalog.md#gt-618), [GT-620](../gaps/gap-reference-catalog.md#gt-620), [GT-324](../gaps/gap-reference-catalog.md#gt-324), [GT-578](../gaps/gap-reference-catalog.md#gt-578), [GT-580](../gaps/gap-reference-catalog.md#gt-580), +14 | +| 3 | Quick wins | High criticality with XS/S complexity. | [GT-571](../gaps/gap-reference-catalog.md#gt-571), [GT-575](../gaps/gap-reference-catalog.md#gt-575), [GT-601](../gaps/gap-reference-catalog.md#gt-601), [GT-609](../gaps/gap-reference-catalog.md#gt-609), [GT-610](../gaps/gap-reference-catalog.md#gt-610), [GT-586](../gaps/gap-reference-catalog.md#gt-586), [GT-597](../gaps/gap-reference-catalog.md#gt-597), [GT-607](../gaps/gap-reference-catalog.md#gt-607) | +| 4 | P1 wave | Next hardening after P0 is cleared. | [GT-586](../gaps/gap-reference-catalog.md#gt-586), [GT-597](../gaps/gap-reference-catalog.md#gt-597), [GT-607](../gaps/gap-reference-catalog.md#gt-607), [GT-618](../gaps/gap-reference-catalog.md#gt-618), [GT-620](../gaps/gap-reference-catalog.md#gt-620), [GT-624](../gaps/gap-reference-catalog.md#gt-624), [GT-324](../gaps/gap-reference-catalog.md#gt-324), [GT-578](../gaps/gap-reference-catalog.md#gt-578), +15 | | 5 | P2/P3 | Only after security, CI, rules, and contracts stabilize. | [GT-619](../gaps/gap-reference-catalog.md#gt-619), [GT-622](../gaps/gap-reference-catalog.md#gt-622), [GT-424](../gaps/gap-reference-catalog.md#gt-424), [GT-444](../gaps/gap-reference-catalog.md#gt-444), [GT-464](../gaps/gap-reference-catalog.md#gt-464), [GT-614](../gaps/gap-reference-catalog.md#gt-614), +17 | ## Current Blockers | ID | Attack | Component | Effort | |---|---|---|---| -| [GT-570](../gaps/gap-reference-catalog.md#gt-570) | **npm serves 1.1.0 published 2026-07-18; the security wave landed 2026-07-23.** Verified with npm view @beyondnet/evolith-cli version time.modified. The public CHANGELOG.md enumerates the corrected files by name under [Unreleased], and SECURITY.md declares the 1.1.x line "Current stable line — actively patched". Anyone following the README installs the unpatched build from a repository that publishes where the holes are. Fix: publish **1.2.0** with the security wave, deprecate 1.1.0 on npm with a message pointing at the fixed version, move the security section from [Unreleased] to the published heading, and issue the advisory SECURITY.md already promises. Follow-on: a release gate that fails when HEAD carries security-tagged commits absent from the last published tag. | `Infra` | P0/S | | [GT-571](../gaps/gap-reference-catalog.md#gt-571) | **The highest-traffic surface of the product does not work as written.** (a) README.md:96-98 (and README.es.md:95-97) instruct evolith init / evolith validate, but the published bin map declares only evolith-cli and evolith-mcp — the phantom command appears in **447 invocations across 49 non-.es markdown files**, so renaming the bin is cheaper than rewriting the docs. (b) Even with the alias, init --name my-sat creates a **subdirectory**, so the validate that follows in the same cwd targets the parent and raises GOV-000 "Missing evolith.yaml" plus 41 blocking findings. Inside the correct satellite the first validate still returns 46 findings / 39 blocking, dominated by the vendor's own monorepo rules (CLI-RR-01 "dist/main.js not found", TAX-05 "Missing top-level directories: sdk, .harness") with rules from all 8 topologies firing on a repo declared phase-0. Additionally the published binary self-identifies as main (evolith-cli init --help prints Usage: main init [options]) because the program name is never set. **IN-PROGRESS (44fe8dd3):** the three CLI-side defects are fixed and tested — evolith published alongside evolith-cli in the bin map, the program name set so --help no longer says main, and init given a defined non-interactive contract (initializes the CURRENT directory, --name is the project name only, evolith init for a subdirectory; a closed stdin no longer prompts, failure sets a non-zero exit code, and --format json emits a parseable envelope and nothing else). **NOT closed on two criteria:** "a freshly initialized repo returns 0 blocking findings" is untouched — those 39 findings come from the vendor's own CLI-RR-*/TAX-* rules firing on a phase-0 satellite, which needs rule audience/topology gating in the ruleset path; and "npx @beyondnet/evolith-cli@latest works" cannot be true until a release ships (GT-570). **+2026-07-26 (wave 3) — criterion 2 of 3 MET, still open on criterion 1.** A freshly initialized satellite now returns **0 blocking** findings (was 35: CLI-RR-01/02, TAX-05/06, DEP-01/10, CB-VAL-01 plus rules from 7 topologies the repo never declared). Two mechanisms in the ruleset layer: an audience: core|satellite|both annotation (default both, so an unannotated rule keeps applying everywhere) marking the families that only address the vendor monorepo, and topologies + appliesFromSdlcPhase gating read from the satellite's declared phase and topology. **The GT-569 interaction was decided explicitly:** a rule addressed to somebody else is a PRE-FILTER, not a fourth outcome — it never reaches the evaluator, so rulesChecked + rulesSkipped + rulesErrored === rulesTotal still holds literally. Calling it skipped would inflate the unevaluated fraction of a healthy repo and could trip maxSkippedFraction. The exclusion is still visible: rulesNotApplicable / notApplicableRuleIds over a new corpusTotal, plus a non-blocking GOV-RULE-NOT-APPLICABLE advisory naming the counts and how to bring the rules into scope. Fresh satellite before/after: 379 total / 108 checked / 35 blocking / failed → 274 total / 41 checked / 233 skipped / 105 not-applicable / corpusTotal 379 / **0 blocking** / warning. **Still open:** criterion 1 (npx @beyondnet/evolith-cli@latest in a clean container) cannot be true until a release ships — blocked on GT-570. | `Evolith CLI` | P0/S | | [GT-575](../gaps/gap-reference-catalog.md#gt-575) | **A product that sells AI governance ships its only LLM egress path without any of the controls it sells.** GeminiProvider.ts:17 builds the URL with the API key in the query string; the whole 57-line file has no AbortSignal (:31-37), no budget, no redaction, no log or metric, and its only output validation is JSON.parse(candidate) as T (:52). It is a public export (src/packages/agent-runtime/src/index.ts:22) of @beyondnet/evolith-agent-runtime@1.1.0. Disclosure across README.md, README.es.md, SECURITY.md and the 8 package READMEs: zero. It violates at least 4 of the 9 blocking AAI-* rules the product itself sells. **Exposure is latent, not active** — the only in-tree caller is src/sdk/cli/src/commands/plan/index.ts:27, and PlanCommand is not registered in app.module.ts — but it sits on the public surface a security reviewer reads first. The correct implementation already exists in-house: .harness/scripts/ci/agentic/review-provider.mjs:35-38 puts the key in a header with the literal comment "API key in a header, not the URL query string", with budget caps and 8 redaction patterns. Fix: port that control, and collapse the duplicate ILLMProvider port into the governed IAssistantTransport/SupervisedAssistantClient seam. **IN-PROGRESS (44fe8dd3):** the controls the repository already runs against itself in .harness/scripts/ci/agentic/ were ported into the shipped provider rather than reinvented — API key moved from the URL query string to the x-goog-api-key header, AbortController timeout, byte/token budget that fails closed, secret redaction, schema-validated output, and an auditable log/metric. Egress is now **off unless EVOLITH_LLM_EGRESS=true**. **NOT closed:** turning an always-on published export into an opt-in one is a behavioural break for @beyondnet/evolith-agent-runtime@1.1.0 (names stay additive, so the GT-388 contract freeze holds, but observable behaviour changed) and needs a deliberate version decision alongside GT-570; the duplicate ILLMProvider port is not yet fully collapsed into the governed IAssistantTransport/SupervisedAssistantClient seam; and the Spanish package READMEs still carry no disclosure. Verified: agent-runtime 309/309. **+2026-07-26 (wave 3) — 2 of 3 criteria met.** The duplicate LLM port was collapsed: GeminiProvider is now reachable only as an IAssistantTransport behind the supervised, fail-closed, off-by-default seam, so there is one governed path to an LLM instead of two with opposite postures. The Spanish egress disclosure was written into src/packages/agent-runtime/README.es.md, structurally mirroring the English one. **Still open:** criterion 3 — the repository does not pass its own 9 blocking AAI-* rules in a CI check, and there is still no root agent.config.json, so the agentic-ai topology is never evaluated against Evolith itself. Also unresolved and owner-gated: turning an always-on published export into an opt-in one is a behavioural break for @beyondnet/evolith-agent-runtime@1.1.0 and needs a version decision alongside GT-570. src/sdk/cli/README.es.md still carries no disclosure (out of the agent's owned area). | `agent-runtime` | P0/S | | [GT-601](../gaps/gap-reference-catalog.md#gt-601) | **canonical-result.mapper.ts writes three traceability fields as unconditional empty arrays and hardcodes the engine — and two consumers read them.** :130 rulesExecuted: [], :134 missingEvidence: [], :104/:133 risks: [] are literals on every real evaluation, and :72 sets engine: 'opa' on every policiesApplied ref regardless of whether NativeEvaluator or OpaEvaluator ran. sarif-exporter.ts:256 and drift-gate.ts:203 both derive evaluatedRules from rulesExecuted. Consequence: every SARIF log and every PR drift-gate evidence manifest emitted today states **"0 rules evaluated"**, and in a product whose dual-engine parity is a selling point the one artifact that would prove it misattributes half its runs. The EVD-01..04 evidence contract is satisfied structurally and empty in substance, so the accumulated audit graph is being written blank at the source. | `Evolith Core` | P0/S | @@ -54,14 +53,14 @@ Use this summary with a simple rule: if you need context, open only the linked I | Indicator | Value | |---|---:| | Canonical board date | 2026-07-26 | -| Total gaps | 623 | -| Closed gaps | 561 | +| Total gaps | 624 | +| Closed gaps | 562 | | Open gaps | 62 | -| Open P0 | 13 | -| Open P1 | 22 | +| Open P0 | 12 | +| Open P1 | 23 | | Open P2 | 23 | -| Total closure | 90% | -| Closure evidence records | 543 | +| Total closure | 90.1% | +| Closure evidence records | 544 | | Recorded readiness | 4 PASS | | Area | Open | P0 | P1 | First IDs | @@ -70,7 +69,7 @@ Use this summary with a simple rule: if you need context, open only the linked I | `MCP Server` | 5 | 2 | 2 | [GT-609](../gaps/gap-reference-catalog.md#gt-609), [GT-572](../gaps/gap-reference-catalog.md#gt-572), [GT-581](../gaps/gap-reference-catalog.md#gt-581), [GT-582](../gaps/gap-reference-catalog.md#gt-582), +1 | | `agent-runtime` | 7 | 2 | 1 | [GT-575](../gaps/gap-reference-catalog.md#gt-575), [GT-610](../gaps/gap-reference-catalog.md#gt-610), [GT-612](../gaps/gap-reference-catalog.md#gt-612), [GT-531](../gaps/gap-reference-catalog.md#gt-531), +3 | | `Evolith CLI` | 5 | 1 | 3 | [GT-571](../gaps/gap-reference-catalog.md#gt-571), [GT-618](../gaps/gap-reference-catalog.md#gt-618), [GT-580](../gaps/gap-reference-catalog.md#gt-580), [GT-611](../gaps/gap-reference-catalog.md#gt-611), +1 | -| `Infra` | 5 | 1 | 2 | [GT-570](../gaps/gap-reference-catalog.md#gt-570), [GT-597](../gaps/gap-reference-catalog.md#gt-597), [GT-324](../gaps/gap-reference-catalog.md#gt-324), [GT-622](../gaps/gap-reference-catalog.md#gt-622), +1 | +| `core-domain` | 3 | 1 | 2 | [GT-595](../gaps/gap-reference-catalog.md#gt-595), [GT-586](../gaps/gap-reference-catalog.md#gt-586), [GT-584](../gaps/gap-reference-catalog.md#gt-584) | ## Source and Refresh Rule diff --git a/reference/core/control-center/maturity-reports/maturity-reconciliation.json b/reference/core/control-center/maturity-reports/maturity-reconciliation.json index f37fdf55..c53d8532 100644 --- a/reference/core/control-center/maturity-reports/maturity-reconciliation.json +++ b/reference/core/control-center/maturity-reports/maturity-reconciliation.json @@ -3,14 +3,14 @@ "scope": "evolith-core", "asOf": "2026-07-26", "gaps": { - "total": 623, - "done": 561, + "total": 624, + "done": 562, "pending": 49, "inProgress": 9, "deferred": 4 }, "evidence": { - "closureRecords": 543, + "closureRecords": 544, "cliPackage": "@beyondnet/evolith-cli@1.2.0", "adrCount": 139, "rulesetCount": 167,