diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f7b09007..562bc8ca 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -907,7 +907,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Login to GHCR if: env.PUBLISH == 'true' @@ -918,7 +918,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build ${{ matrix.service }} (push only from main/tags) - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: ${{ matrix.context }} file: ${{ matrix.dockerfile }} diff --git a/.github/workflows/sdk-cli-release.yml b/.github/workflows/sdk-cli-release.yml index 70399b34..b64ba2f6 100644 --- a/.github/workflows/sdk-cli-release.yml +++ b/.github/workflows/sdk-cli-release.yml @@ -10,12 +10,24 @@ on: # NOTHING but core-validation, and every downstream job skipped with it. The # validation half only ever executed on a v* tag, which is how five consecutive # tags each discovered a different long-standing defect. + # + # NO `paths:` FILTER ON `pull_request` — deliberately, and it must stay that way, + # for the same reason `sdk-cli-ci.yml` carries the identical note. `build-and-test` + # is a job in this workflow and is intended to become a REQUIRED status check + # (GT-710). A required check behind a path filter never reports on a PR that misses + # those paths, and GitHub reads "never reported" as "not satisfied" -- so such a PR + # is unmergeable forever, with every visible check green and nothing to point at. + # `sdk-cli-ci.yml` records the repository living through exactly that with + # `CodeQL SAST` on PR #218; this filter would have reproduced it on the next + # docs-only change. Measured before removing it, not argued: the filter listed + # `src/sdk/cli/**`, `src/packages/**`, this file and `.harness/**`, and PR #690 + # touched only `reference/` -- so it would have been the first casualty. + # + # The `push` trigger below keeps its filter on purpose: pushes are not gated by + # required checks, so no deadlock is possible there and the CI minutes are worth + # saving. That asymmetry is the whole design, not an oversight. pull_request: - paths: - - 'src/sdk/cli/**' - - 'src/packages/**' - - '.github/workflows/sdk-cli-release.yml' - - '.harness/**' + branches: [main, develop] push: branches: - main diff --git a/reference/core/control-center/evidence/gap-closure-evidence.json b/reference/core/control-center/evidence/gap-closure-evidence.json index 83c449f6..06c133a5 100644 --- a/reference/core/control-center/evidence/gap-closure-evidence.json +++ b/reference/core/control-center/evidence/gap-closure-evidence.json @@ -10635,6 +10635,23 @@ "BOTH HALVES: evolith_arch32#623 (144fce23) and evolith_tracker#153 (97e1bc8e).", "node .harness/scripts/ci/08-validate-tracking.mjs && node --test src/rulesets/standards/iso-5055-mapping.test.mjs" ] + }, + { + "id": "GT-709", + "closedAt": "2026-09-05", + "closureCommit": "eb458372", + "dependencyDisposition": "none", + "evidence": [ + "package.json", + "package-lock.json" + ], + "validationCommands": [ + "BASELINE MEASURED ON THE TREE WITHOUT THE CHANGE, with the same guard CI runs, rather than inherited from the CI log: `63-validate-npm-audit-gate.mjs` on `origin/main` (11562cce) reports 11 blocking rows / 7 high, naming fast-uri's four advisories (GHSA-jqff-g426-hqxp, GHSA-5jgf-p345-68v8, GHSA-f65p-4m7j-42xc, GHSA-fph4-wmhf-6fwf), the five ajv/commitlint chain rows it reports as `via:fast-uri`, and browserslist's two (GHSA-73wf-gq98-2v4g, GHSA-c83g-rgw3-j3cx).", + "THE CAUSE WAS A PIN OF OUR OWN, NOT A MISSING UPSTREAM FIX. `overrides.fast-uri` was `3.1.5` — exactly the last vulnerable release of the 3.x line. The advisory patches in `3.1.6`, inside the `^3.0.1` that `ajv@8.20.0` declares, so the fix fitted in the pin that already existed.", + "AFTER `fast-uri` 3.1.7: 2 blocking rows / 2 high, both browserslist — so 9 of the 11 rows are attributable to the single pin, measured and not inferred. AFTER `browserslist` 4.28.9: `0 undeclared high/critical advisories; 0 accepted with a recorded reason, 0 stale`.", + "NO EXCEPTION WAS DECLARED. `.harness/config/npm-audit-exceptions.json` still holds zero entries; that file is for advisories with no upstream fix, and both of these had one. The moderate `qs` advisory survives on purpose — the gate does not block below HIGH.", + "WHY THE GATE STOPPED NOTHING is registered as its own row rather than as prose here: `Security Audit` is not among the nine required contexts, and eight PRs merged into `main` while it was red. See GT-710." + ] } ] } 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 8eebe3aa..c5e26a3f 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -10050,3 +10050,67 @@ Los dos se arreglaron de forma estructural y no como correcciones: el rethrow no - [x] **FALSABILIDAD:** ningún enlace de ninguno de los dos repositorios resuelve a un fichero KDD borrado, comprobado tras el barrido y no supuesto desde la lista de borrados. **CUMPLIDO para el Core** — buscar los ocho nombres borrados en todos los markdown no devuelve nada fuera del `ADR-0127` y del aviso de corrección del documento de rediseño, que los nombran como retirados en vez de enlazarlos. - **Estado:** `COMPLETADO` +#### GT-709 + +**Título:** Un `overrides` puesto para cerrar un advisory se vuelve el techo que impide cerrarlo la vez siguiente + +- **Propósito:** Que el bloque de `overrides` deje de ser el sitio donde una CVE se queda a vivir, y que `Security Audit` vuelva a significar algo. +- **Evidencia, medida el 2026-09-05 con `63-validate-npm-audit-gate.mjs` —el guard que corre CI— sobre el mismo árbol:** + + | árbol | filas bloqueantes | altas | + |---|---:|---:| + | `origin/main` (`11562cce`) | 11 | 7 | + | + `fast-uri` `3.1.7` | 2 | 2 | + | + `browserslist` `4.28.9` | **0** | **0** | + +- **La causa no era una dependencia sin arreglo, sino dos pins propios.** `overrides.fast-uri` estaba fijado en `3.1.5` — exactamente la última versión vulnerable de la rama 3.x. `GHSA-jqff-g426-hqxp` parchea en `3.1.6`, dentro del `^3.0.1` que declara `ajv@8.20.0`, así que el arreglo cabía en el pin que ya existía. Las nueve filas que se van con ese cambio son las cuatro advisories de `fast-uri` (`GHSA-jqff-g426-hqxp`, `GHSA-5jgf-p345-68v8`, `GHSA-f65p-4m7j-42xc`, `GHSA-fph4-wmhf-6fwf`) más las cinco filas de la cadena `ajv` / `@commitlint/config-validator` / `@commitlint/resolve-extends` / `@commitlint/load` / `@commitlint/cli`, que el gate reportaba `via:fast-uri`. +- **Las dos restantes eran el mismo patrón otra vez.** `browserslist` `4.28.4`, transitivo solo-dev por `ts-jest`→`@babel/core`→`@babel/helper-compilation-targets` y por `@nestjs/cli`→`webpack`, con `GHSA-73wf-gq98-2v4g` y `GHSA-c83g-rgw3-j3cx` parcheados en `4.28.7`. Se pinea `4.28.9` con la misma forma de override que el comentario del propio `sdk-cli-ci.yml` prescribe para este caso. +- **Lo que NO se hizo, y por qué:** no se declaró ninguna excepción en `.harness/config/npm-audit-exceptions.json`. Ese fichero es para advisories sin arreglo upstream, y aquí había arreglo en las dos; declararlas habría convertido un pin caduco en una excepción permanente. +- **Casos de uso:** + - Un revisor ve `Security Audit` en rojo, comprueba que la advisory es de un transitivo y la descarta como ajena, sin mirar que el techo lo pone un `overrides` propio. + - Alguien añade un `overrides` para cerrar una CVE y con ello fija la versión que impedirá cerrar la siguiente de ese mismo paquete. +- **Impacto:** Una CVE ALTA viva en `main` durante tres días, con el gate que la detecta en rojo y ocho merges pasando por encima. +- **Resultado esperado:** `63-validate-npm-audit-gate.mjs` en verde sobre `main` sin excepciones declaradas. +- **Ficheros afectados:** `package.json`, `package-lock.json` +- **Componente:** `Infra` · **Criticidad:** P2 · **Complejidad:** S +- **Principal:** `XS` · **Interest:** `HIGH` · **Basis:** `estimate` +- **Procedencia:** Registrado el 2026-09-05, encontrado barriendo los pull requests de dependabot: los cinco de npm salían con `Security Audit` en rojo, y contrastarlo contra `main` mostró que el rojo era anterior a los cinco. +- **Criterios de aceptación:** + - [x] El gate pasa de 11 filas bloqueantes a 0, medido con el guard real sobre el árbol y no deducido del advisory. **CUMPLIDO** — `0 undeclared high/critical advisories; 0 accepted with a recorded reason, 0 stale`. + - [x] El arreglo es un cambio de versión, no una excepción declarada. **CUMPLIDO** — `npm-audit-exceptions.json` sigue con cero entradas. + - [x] La versión elegida respeta el rango que declara el consumidor, en vez de forzarlo. **CUMPLIDO** — `3.1.7` cae dentro del `^3.0.1` de `ajv@8.20.0`. + - [x] **FALSABILIDAD:** el baseline se midió sobre el árbol SIN el cambio y con el mismo guard, no se heredó del log de CI. **CUMPLIDO** — 11 filas / 7 altas sobre `origin/main`, con las cuatro filas de `fast-uri` y las dos de `browserslist` nombradas una a una antes de tocar nada. +- **Estado:** `COMPLETADO` + +#### GT-710 + +**Título:** El gate que mide las CVE no es un check requerido, así que ocho merges pasaron por encima de él en rojo + +- **Propósito:** Que un advisory ALTA sin declarar bloquee el merge en lugar de limitarse a informarlo. +- **Evidencia, medida el 2026-09-05:** + + | dato | valor | + |---|---| + | contextos requeridos en `main` y `develop` | 9 | + | ¿incluyen `Security Audit`? | **no** cuando se registró; **sí desde el 2026-09-05** (10 contextos en ambas ramas, verificado) | + | ¿incluyen `Trivy Container Scan` o `build-and-test`? | **no** — decididos requeridos, pendiente de aplicar; `build-and-test` con una precondición (ver criterio 3) | + | `Security Audit` en rojo desde | `b84523b4`, 2026-09-02 | + | merges a `main` en ese intervalo | **8**, cuatro de ellos de dependencias npm | + | cómo lo presenta GitHub | `UNSTABLE`, no `BLOCKED` | + +- **Los nueve requeridos son** `CodeQL SAST`, `Secret Detection (gitleaks)`, `Services build (GHCR)`, `Test`, `Test core`, `Test core-api`, `Test core-domain`, `Test mcp-server` y `Validate documentation`. +- **El workflow nombra el modo de fallo y luego lo construye.** El comentario de `sdk-cli-ci.yml` advierte de que un check permanentemente rojo enseña a los revisores a descontar el rojo — y el check queda fuera del conjunto requerido, que es la vía más directa a ese resultado. Los cuatro PR de dependencias npm mergeados en el intervalo ([#664](https://github.com/beyondnetcode/evolith_arch32/pull/664), [#665](https://github.com/beyondnetcode/evolith_arch32/pull/665), [#666](https://github.com/beyondnetcode/evolith_arch32/pull/666), [#667](https://github.com/beyondnetcode/evolith_arch32/pull/667)) son exactamente la clase de cambio que el gate existe para juzgar. +- **Casos de uso:** + - Un bump de dependencias introduce una CVE ALTA y se mergea igual, porque el único check que la ve no es requerido. + - Un revisor aprende que `Security Audit` rojo es normal y deja de leerlo. +- **Impacto:** El gate de seguridad de dependencias es informativo y no impide ningún merge, incluidos los de dependencias. +- **Resultado esperado:** `Security Audit` entre los contextos requeridos de `main` y `develop`, con un PR que lleve una alta sin declarar quedando `BLOCKED`. +- **Ficheros afectados:** protección de rama de `main` y `develop`, `.github/workflows/sdk-cli-ci.yml` +- **Componente:** `Infra` · **Criticidad:** P1 · **Complejidad:** S +- **Principal:** `XS` · **Interest:** `HIGH` · **Basis:** `estimate` +- **Procedencia:** Registrado el 2026-09-05 al cerrar [`GT-709`](#gt-709): explicar por qué una CVE ALTA había sobrevivido tres días exigía mirar qué checks bloquean de verdad, y `Security Audit` no estaba entre ellos. +- **Criterios de aceptación:** + - [ ] `Security Audit` figura en los contextos requeridos de `main` y de `develop`. + - [ ] **FALSABILIDAD:** un PR con un advisory ALTA sin declarar sale `BLOCKED` y no `UNSTABLE`, observado y no supuesto. + - [~] La decisión sobre `Trivy` y `build-and-test` queda escrita — requeridos también, o registrado por qué no lo son. **DECIDIDO por el dueño el 2026-09-05: requeridos los dos.** Ejecutable solo la mitad, y la otra mitad no es pereza sino un deadlock medido: **`Trivy Container Scan` es seguro de requerir** porque vive en `sdk-cli-ci.yml`, que **no lleva filtro `paths`** —y su comentario explica que no debe llevarlo nunca, por el bloqueo que `CodeQL SAST` causó en el PR #218 al volverse requerido—, así que reporta en todo PR. **`build-and-test` NO puede requerirse tal como está:** vive en `sdk-cli-release.yml`, cuyo disparador `pull_request` sí filtra por `src/sdk/cli/**`, `src/packages/**`, `.github/workflows/sdk-cli-release.yml` y `.harness/**`. Un check requerido detrás de un filtro de rutas **nunca reporta** en un PR que no las toca, y GitHub lee «no reportó» como «no satisfecho»: el PR queda inmergeable para siempre con todo en verde. Es exactamente lo que le habría pasado a [#690](https://github.com/beyondnetcode/evolith_arch32/pull/690), que solo tocó `reference/`. **Precondición, no alternativa:** quitar el filtro `paths` del `pull_request` de `sdk-cli-release.yml` —el mismo arreglo que ya se aplicó a `sdk-cli-ci.yml`— y solo entonces añadirlo a los requeridos. **Nota sobre el nombre:** el check a requerir es `Trivy Container Scan`, el nombre del job; el check `Trivy` a secas que publica `aquasecurity/trivy-action` aparece en `main` pero no en la cabeza de `develop`, así que requerir ese nombre reintroduciría el mismo deadlock por otra vía. +- **Estado:** `PENDIENTE` diff --git a/reference/core/control-center/gaps/gap-reference-catalog.md b/reference/core/control-center/gaps/gap-reference-catalog.md index d554cebc..02b21a69 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -10143,3 +10143,67 @@ Both were fixed structurally rather than corrected: the rethrow now names BOTH f - [x] **FALSIFIABILITY:** no link in either repository resolves to a deleted KDD file, checked after the sweep rather than assumed from the delete list. **MET for the Core** — searching the eight deleted filenames across every markdown file returns nothing outside `ADR-0127` and the redesign doc's correction notice, both of which name them as retired rather than link to them. - **Status:** `DONE` +#### GT-709 + +**Title:** An `overrides` pin added to close an advisory becomes the ceiling that prevents closing it the next time + +- **Purpose:** Stop the `overrides` block being the place a CVE settles in, and make `Security Audit` mean something again. +- **Evidence, measured 2026-09-05 with `63-validate-npm-audit-gate.mjs` — the guard CI runs — against the same tree:** + + | tree | blocking rows | high | + |---|---:|---:| + | `origin/main` (`11562cce`) | 11 | 7 | + | + `fast-uri` `3.1.7` | 2 | 2 | + | + `browserslist` `4.28.9` | **0** | **0** | + +- **The cause was not a dependency without a fix, but two pins of our own.** `overrides.fast-uri` was set to `3.1.5` — exactly the last vulnerable release of the 3.x line. `GHSA-jqff-g426-hqxp` patches in `3.1.6`, inside the `^3.0.1` that `ajv@8.20.0` declares, so the fix fitted in the pin that was already there. The nine rows that go with that change are `fast-uri`'s own four advisories (`GHSA-jqff-g426-hqxp`, `GHSA-5jgf-p345-68v8`, `GHSA-f65p-4m7j-42xc`, `GHSA-fph4-wmhf-6fwf`) plus the five rows of the `ajv` / `@commitlint/config-validator` / `@commitlint/resolve-extends` / `@commitlint/load` / `@commitlint/cli` chain, which the gate reported as `via:fast-uri`. +- **The remaining two were the same pattern again.** `browserslist` `4.28.4`, a dev-only transitive through `ts-jest`→`@babel/core`→`@babel/helper-compilation-targets` and `@nestjs/cli`→`webpack`, with `GHSA-73wf-gq98-2v4g` and `GHSA-c83g-rgw3-j3cx` patched in `4.28.7`. Pinned to `4.28.9` with the same override shape `sdk-cli-ci.yml`'s own comment prescribes for this case. +- **What was NOT done, and why:** no exception was declared in `.harness/config/npm-audit-exceptions.json`. That file is for advisories with no upstream fix, and both of these had one; declaring them would have turned a stale pin into a permanent exception. +- **Use cases:** + - A reviewer sees `Security Audit` red, checks that the advisory is transitive and dismisses it as somebody else's, without noticing that the ceiling is set by an `overrides` entry of our own. + - Someone adds an `overrides` entry to close a CVE and thereby pins the version that will prevent closing the next one in that same package. +- **Impact:** A HIGH CVE live on `main` for three days, with the gate that detects it red and eight merges going past it. +- **Expected outcome:** `63-validate-npm-audit-gate.mjs` green on `main` with no declared exceptions. +- **Files affected:** `package.json`, `package-lock.json` +- **Component:** `Infra` · **Criticality:** P2 · **Complexity:** S +- **Principal:** `XS` · **Interest:** `HIGH` · **Basis:** `estimate` +- **Provenance:** Registered 2026-09-05, found while sweeping the dependabot pull requests: all five npm ones came out with `Security Audit` red, and contrasting that against `main` showed the red predated all five. +- **Acceptance criteria:** + - [x] The gate goes from 11 blocking rows to 0, measured with the real guard against the tree rather than deduced from the advisory. **MET** — `0 undeclared high/critical advisories; 0 accepted with a recorded reason, 0 stale`. + - [x] The fix is a version change, not a declared exception. **MET** — `npm-audit-exceptions.json` still holds zero entries. + - [x] The chosen version respects the range the consumer declares rather than forcing it. **MET** — `3.1.7` falls inside `ajv@8.20.0`'s `^3.0.1`. + - [x] **FALSIFIABILITY:** the baseline was measured on the tree WITHOUT the change and with the same guard, not inherited from the CI log. **MET** — 11 rows / 7 high on `origin/main`, with `fast-uri`'s four rows and `browserslist`'s two named one by one before anything was touched. +- **Status:** `DONE` + +#### GT-710 + +**Title:** The gate that measures CVEs is not a required check, so eight merges went past it while it was red + +- **Purpose:** Make an undeclared HIGH advisory block the merge instead of merely reporting it. +- **Evidence, measured 2026-09-05:** + + | fact | value | + |---|---| + | required contexts on `main` and `develop` | 9 | + | do they include `Security Audit`? | **no** when registered; **yes since 2026-09-05** (10 contexts on both branches, verified) | + | do they include `Trivy Container Scan` or `build-and-test`? | **no** — decided required, not yet applied; `build-and-test` carries a precondition (see criterion 3) | + | `Security Audit` red since | `b84523b4`, 2026-09-02 | + | merges into `main` in that window | **8**, four of them npm dependency changes | + | how GitHub renders them | `UNSTABLE`, not `BLOCKED` | + +- **The nine required are** `CodeQL SAST`, `Secret Detection (gitleaks)`, `Services build (GHCR)`, `Test`, `Test core`, `Test core-api`, `Test core-domain`, `Test mcp-server` and `Validate documentation`. +- **The workflow names the failure mode and then builds it.** The comment in `sdk-cli-ci.yml` warns that a permanently red check trains reviewers to discount red — and the check is left out of the required set, which is the most direct route to that outcome. The four npm dependency PRs merged in the window ([#664](https://github.com/beyondnetcode/evolith_arch32/pull/664), [#665](https://github.com/beyondnetcode/evolith_arch32/pull/665), [#666](https://github.com/beyondnetcode/evolith_arch32/pull/666), [#667](https://github.com/beyondnetcode/evolith_arch32/pull/667)) are exactly the class of change that gate exists to judge. +- **Use cases:** + - A dependency bump introduces a HIGH CVE and merges anyway, because the only check that sees it is not required. + - A reviewer learns that a red `Security Audit` is normal and stops reading it. +- **Impact:** The dependency security gate is advisory and blocks no merge, including dependency merges. +- **Expected outcome:** `Security Audit` among the required contexts on `main` and `develop`, with a PR carrying an undeclared high coming out `BLOCKED`. +- **Files affected:** branch protection on `main` and `develop`, `.github/workflows/sdk-cli-ci.yml` +- **Component:** `Infra` · **Criticality:** P1 · **Complexity:** S +- **Principal:** `XS` · **Interest:** `HIGH` · **Basis:** `estimate` +- **Provenance:** Registered 2026-09-05 while closing [`GT-709`](#gt-709): explaining why a HIGH CVE had survived three days required looking at which checks actually block, and `Security Audit` was not among them. +- **Acceptance criteria:** + - [ ] `Security Audit` appears in the required contexts of both `main` and `develop`. + - [ ] **FALSIFIABILITY:** a PR carrying an undeclared HIGH advisory comes out `BLOCKED` rather than `UNSTABLE`, observed and not assumed. + - [~] The decision on `Trivy` and `build-and-test` is written down — required too, or a recorded reason why not. **DECIDED by the owner 2026-09-05: both required.** Only half is executable, and the other half is not laziness but a measured deadlock: **`Trivy Container Scan` is safe to require** because it lives in `sdk-cli-ci.yml`, which carries **no `paths` filter** — and whose comment says it must never carry one, because of the block `CodeQL SAST` caused on PR #218 once it became required — so it reports on every PR. **`build-and-test` CANNOT be required as it stands:** it lives in `sdk-cli-release.yml`, whose `pull_request` trigger does filter on `src/sdk/cli/**`, `src/packages/**`, `.github/workflows/sdk-cli-release.yml` and `.harness/**`. A required check behind a path filter **never reports** on a PR that misses those paths, and GitHub reads "never reported" as "not satisfied": the PR is unmergeable forever with everything green. That is exactly what would have happened to [#690](https://github.com/beyondnetcode/evolith_arch32/pull/690), which touched only `reference/`. **A precondition, not an alternative:** drop the `paths` filter from `sdk-cli-release.yml`'s `pull_request` — the same fix already applied to `sdk-cli-ci.yml` — and only then add it to the required set. **On the name:** the check to require is `Trivy Container Scan`, the job name; the bare `Trivy` check published by `aquasecurity/trivy-action` shows on `main` but not on `develop`'s head, so requiring that name would reintroduce the same deadlock by another route. +- **Status:** `PENDING` diff --git a/reference/core/control-center/gaps/gap-tracking.es.md b/reference/core/control-center/gaps/gap-tracking.es.md index 654a1927..4f19ed3f 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -4,6 +4,7 @@ **Estado:** Seguimiento Activo **Responsable:** Evolith Architecture Board +**Última Actualización:** 2026-09-05 (**Dos filas nuevas de un mismo hilo: una CVE ALTA que llevaba tres días viva en `main` y la razón por la que no detuvo nada.** `GT-709` → COMPLETADO: `Security Audit` estaba rojo desde `b84523b4` no por una dependencia sin arreglo, sino porque `overrides.fast-uri` estaba fijado en `3.1.5`, **exactamente la última versión vulnerable de la rama 3.x**, con el parche en `3.1.6` y dentro del rango que `ajv` declara. Medido con el guard real, `63-validate-npm-audit-gate.mjs`: de **11 filas bloqueantes / 7 altas a 0 y 0**, sin declarar ninguna excepción. `GT-710` → PENDIENTE: ese gate **no es un check requerido**, y en el intervalo en que estuvo rojo se mergearon **ocho** PR a `main`, cuatro de ellos de dependencias npm. **Lo que merece llevarse:** el mecanismo que se usa para cerrar un advisory —el `overrides`— es el mismo que después impide cerrarlo, y el único check que lo ve no bloquea nada.) **Última Actualización:** 2026-08-18 (**Un gap cerrado por el disparador que él mismo había escrito — que se activó dos días después de escribirlo, y nombraba la release equivocada.** `GT-691` → COMPLETADO. La CVE ALTA de `js-yaml` que bloqueaba toda promoción a `main` salió del árbol por una actualización, no por un descarte: `@nestjs/swagger@11.4.7` —un PARCHE sobre la línea `11.4.x` que la fila había dado por agotada, no la 12 estable que decía esperar— declara `"js-yaml": "5.3.0"`, y `package-lock.json` resuelve ahora `node_modules/@nestjs/swagger/node_modules/js-yaml` hacia ella. `npm audit` pasa de 1 alta a 0 altas / 0 críticas; el criterio de falsabilidad de la fila se volvió a medir contra el árbol nuevo en vez de heredarlo, y ninguno de los dos falsadores se disparó. **Lo que merece llevarse es el elemento a vigilar, no la CVE:** la fila identificó bien que había que vigilar `@nestjs/swagger` y no `js-yaml`, y luego ató esa vigilancia a un major que no había salido.) **Última Actualización:** 2026-08-08 (**Un gap cerrado haciendo lo irreversible que el board había diferido a propósito — y lo que vale registrar es la medición que se tomó antes.** `GT-622` → COMPLETADO, y lo que lo cerró es la mitad irreversible que el board había diferido: los 210 análisis huérfanos de `.github/workflows/ci.yml:codeql` están borrados de `refs/heads/main` (201) y `refs/heads/develop` (9), la clave muerta no aparece en ninguna de las dos, y el corpus de alerts es idéntico a través del borrado — 242 abiertos / 82 descartados / 60 corregidos, CodeQL 75 / Scorecard 158 / Trivy 9. **Tres afirmaciones de esa misma fila no sobrevivieron a la re-medición, y la fila ya se había re-medido dos veces sin que ninguna saliera, porque cada pasada comprobó el RECUENTO y ninguna comprobó la AFIRMACIÓN:** el check era `neutral`, no rojo, desde el PR #250; la configuración que nombra el aviso está en `refs/heads/develop`, no en `main`, así que solo los PR contra `develop` seguían arrastrándolo mientras `main` salía limpio por su cuenta desde el PR #420 con los 201 huérfanos todavía puestos; y los 9 análisis de `develop` —los que importaban— nunca se contaron. **El campo que convirtió un juicio del dueño en una decisión fácil no se había leído nunca:** los 210 análisis tienen `results_count: 0`, así que lo descartado son 210 registros de “escaneé y no encontré nada” de una configuración muerta desde junio. La irreversibilidad era real; la pérdida no. Quedan a propósito 22 análisis de la clave muerta en `refs/pull/{4..17}/merge`, refs efímeras por PR que jamás pueden ser base de un PR. **Observado también al medir, y fuera de este cierre:** el conjunto de contextos requeridos en `main` y `develop` es ahora de **8**, tras ganar `Secret Detection (gitleaks)` — la promoción que `GT-653` registraba como su único pendiente. El criterio de cierre que solo podía observarse en un PR contra `develop` se observó en el PR #440, el que trae este mismo cierre, 105s después de abrirlo — escrito tras leer el check, no antes. Contadores recalculados desde las filas: **640 / 653 completados · 3 en progreso · 3 pendientes · 7 diferidos**.) **Última Actualización (ola):** 2026-08-14 · segunda pasada (**Trece gaps más registrados desde una ola de benchmarking de 30 agentes, cuatro filas `DONE` corregidas, y una de las filas de esta misma mañana acotada.** La ola produjo 40 candidatos desde tres lentes, 16 pasaron por verificación adversarial con REFUTADO por defecto, **15 sobrevivieron y 1 murió formalmente** — «una regla de tenant nunca puede ejecutarse» es FALSO: una regla con id nuevo se EJECUTÓ hasta un veredicto `failed` real por dos caminos cableados en producción, lo que acota [`GT-669`](./gap-reference-catalog.es.md#gt-669) a extensibilidad de ejecutores y documentación. **Las cuatro mediciones portantes se repitieron a mano antes de escribir nada**, y la más aguda se reprodujo al primer intento: el mismo pack devuelve **exit 0 `passed`** con `--engine opa` y **exit 2 `failed` con 2 issues bloqueantes** con `--engine native` ([`GT-675`](./gap-reference-catalog.es.md#gt-675)). **Cuatro filas `DONE` llevan criterios que el código no satisface** — `GT-321` (ledger con cero instanciaciones), `GT-266` (aprovisionamiento con cero llamantes), `GT-221` (loggers de recursos y prompts sin llamantes de producción), `GT-158` (nada emite un token de aprobación) — cada una anotada in situ con su medición y re-registrada como fila viva en vez de reabierta, porque un registro de cierre sobre una fila no-DONE es error duro en `08-validate-tracking`. `GT-518` queda anotada igual por [`GT-677`](./gap-reference-catalog.es.md#gt-677). **Diez de las trece filas son MÁS ESTRECHAS de lo propuesto**, y cada una registra la subafirmación que su verificación mató. Ids nuevos `GT-675`…`GT-687`. Contadores recalculados desde las filas: **654 / 685 completados · 2 en progreso · 19 pendientes · 10 diferidos**.) @@ -20,6 +21,8 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | ID | Gap | En simple | Qué resuelve | Componente | Fase | Criticidad | Complejidad | Estado | |---|---|---|---|:---:|:---:|:---:|:---:|:---:| +| [`GT-710`](./gap-reference-catalog.es.md#gt-710) | **El gate que mide las CVE no es un check requerido, así que ocho merges pasaron por encima de él estando rojo.** Los nueve contextos requeridos de `main` y `develop` son `CodeQL SAST`, `Secret Detection (gitleaks)`, `Services build (GHCR)`, `Test`, `Test core`, `Test core-api`, `Test core-domain`, `Test mcp-server` y `Validate documentation`. **`Security Audit` no está entre ellos**, y tampoco lo están `Trivy` ni `build-and-test`. Medido el 2026-09-05: entre `b84523b4` (02-sep), el commit donde `Security Audit` se puso rojo, y su arreglo en [`GT-709`](./gap-reference-catalog.es.md#gt-709), se mergearon a `main` **ocho pull requests con el gate en rojo** — y cuatro de ellos ([#664](https://github.com/beyondnetcode/evolith_arch32/pull/664), [#665](https://github.com/beyondnetcode/evolith_arch32/pull/665), [#666](https://github.com/beyondnetcode/evolith_arch32/pull/666), [#667](https://github.com/beyondnetcode/evolith_arch32/pull/667)) eran cambios de dependencias npm, exactamente la clase de cambio que ese gate existe para juzgar. GitHub los presenta como `UNSTABLE` y no como `BLOCKED`, así que el flujo normal de revisión los mergea sin fricción. **El propio workflow nombra este modo de fallo por escrito:** el comentario de `sdk-cli-ci.yml` dice que un check siempre rojo enseña a los revisores a descontar el rojo, y luego deja el check fuera de los requeridos, que es la manera más directa de garantizar que eso ocurra. **Aplicado en parte el 2026-09-05:** `Security Audit` ya es requerido en `main` y `develop` (10 contextos, verificado), y el dueño decidió que `Trivy` y `build-and-test` lo sean también. Sigue abierta porque **el falsador de esta fila aún no se ha disparado** —no hay ninguna alta viva que observar quedando `BLOCKED` en vez de `UNSTABLE`— y porque `build-and-test` **no puede requerirse tal como está**: vive en `sdk-cli-release.yml`, cuyo `pull_request` filtra por rutas, y un check requerido detrás de un filtro nunca reporta y deja el PR inmergeable con todo en verde. Se registra aparte de [`GT-709`](./gap-reference-catalog.es.md#gt-709) a propósito: aquella era una CVE con arreglo de dos líneas, esta es la razón por la que la CVE pudo vivir tres días sin detener nada. | Tenemos un chequeo de seguridad que mide bien y no impide nada; ocho cambios entraron con él en rojo. | Que un advisory ALTA sin declarar bloquee el merge en lugar de limitarse a informarlo. | `Infra` | Cross | P1 | S | `PENDIENTE` | +| [`GT-709`](./gap-reference-catalog.es.md#gt-709) | **Un `overrides` puesto para cerrar un advisory se convierte en el techo que impide cerrarlo la vez siguiente.** `Security Audit` llevaba rojo en `main` desde `b84523b4` (2026-09-02) por `GHSA-jqff-g426-hqxp`, una CVE ALTA en `fast-uri` — y la causa no era una dependencia sin arreglo publicado, sino **dos pins propios que se quedaron por debajo de la versión parcheada**, que el gate reporta con la misma forma que una advisory ajena. `overrides.fast-uri` estaba fijado en `3.1.5`, **exactamente la última versión vulnerable de la rama 3.x**; el parche es `3.1.6`, dentro del `^3.0.1` que declara `ajv@8.20.0`, así que el arreglo cabía en el pin que ya existía y nadie lo miró porque parecía configuración resuelta. **Medido con `63-validate-npm-audit-gate.mjs`, el mismo guard que corre CI, y no inferido de changelogs:** `origin/main` daba **11 filas bloqueantes / 7 altas**; con `fast-uri` a `3.1.7` caen 9 de las 11 — las cuatro advisories suyas más las cinco filas de la cadena `ajv`/`commitlint` que llegaban *via* `fast-uri`. Las dos restantes eran `browserslist` `4.28.4`, transitivo solo-dev (`ts-jest`→`@babel/core`, `@nestjs/cli`→`webpack`) con arreglo en `4.28.7`: mismo patrón, mismo tipo de override, pineado a `4.28.9`. **Resultado: 0 filas bloqueantes, 0 altas.** La moderada de `qs` sobrevive a propósito — el gate no bloquea por debajo de HIGH. **CERRADA el 2026-09-05** por [#689](https://github.com/beyondnetcode/evolith_arch32/pull/689), merge `eb458372` en `main`. **Lo que merece llevarse no es la CVE sino el modo de fallo:** el mecanismo que se usa para cerrar un advisory es el mismo que después lo mantiene abierto, y no hay nada que vigile los pins. Mismo patrón que [`GT-691`](./gap-reference-catalog.es.md#gt-691), donde la vigilancia quedó atada a un major que no había salido; allí el elemento mal vigilado fue `@nestjs/swagger`, aquí es el propio bloque `overrides`. El defecto de que este gate no bloquee ningún merge queda registrado aparte, en [`GT-710`](./gap-reference-catalog.es.md#gt-710). | El chequeo de vulnerabilidades llevaba tres días en rojo por dos versiones que nosotros mismos habíamos fijado una por debajo del arreglo. | Que el bloque de `overrides` deje de ser el sitio donde una CVE se queda a vivir, y que el chequeo de seguridad vuelva a significar algo. | `Infra` | Cross | P2 | S | `COMPLETADO` | | [`GT-708`](./gap-reference-catalog.es.md#gt-708) | **Un concepto de gobierno que existía solo en prosa, en dos repositorios, y que una compuerta real citaba como precondición.** «KDD» nombraba dos cosas distintas y ninguna llegó a construirse: la **Fase 1.1 — Knowledge-First Discovery**, subfase opcional con su propia compuerta de preparación y siete plantillas de artefacto; y **KDD — Knowledge-Driven Development**, lectura posterior de la sesión con el dueño del 2026-07-04 (`L-009`, `D-004`) que lo convertía en sección opcional *dentro del PRD*, activable por tenant. **Medido en todas las superficies ejecutables, y ausente en todas:** `phase-gates.rules.json` tiene cinco gates para las fases 1..5; ninguno de los siete artefactos KDD está entre los 33 de `artifact-registry.json`; cero ficheros TypeScript con `KDD`/`knowledge-first`/`knowledgeBrief`/`discoveryReadiness`/`storySeed`/`epicCandidate`; el CLI tiene 31 comandos y cero menciones, y su `--phase discovery` mapea a la **fase 1 entera** (`phase-id.ts`: `f1: 'discovery'`), no a la 1.1; el servidor MCP, cero; el Tracker no tiene ni pantalla ni entidad; y `prd.schema.json` no lleva sección KDD, así que `D-004` tampoco llegó nunca a schema. **Aun así la prosa tenía dientes:** `phase-1-business-signoff.es.md` convertía *«el nivel de adopción de la Fase 1.1 ha sido declarado»* en **precondición para abrir el Gate 1**, y tres filas de su tabla de evidencia llevaban cláusulas condicionadas a niveles de KDD — una compuerta que nadie implementa bloqueando una que implementa todo el mundo. **CERRADA el 2026-08-18 por eliminación, por decisión del dueño de que Evolith Core y Tracker dejan de manejar el concepto en cualquier forma.** 16 ficheros borrados (el playbook de la Fase 1.1 y las siete plantillas, EN y ES); eliminadas la precondición del Gate 1 y sus tres cláusulas de evidencia condicionadas a KDD; eliminadas la tabla `Subfase 01.1`, la fila del índice de playbooks y las referencias a Story Seeds / Epic Candidates en el playbook de Fase 2 y en el índice de plantillas; `D-004`/`L-009` reescritas a lo que las sobrevive — el PRD es el piso canónico y el Gate 1 lo exige siempre. **La retirada es el [`ADR-0127`](../../architecture/adrs/core/0127-retire-knowledge-first-discovery.es.md), y el `ADR-0103` queda ENMENDADO por él en vez de editado:** una decisión aceptada del Architecture Board se supersede, no se reescribe, así que su razonamiento se mantiene y lo único que desapareció es su vecino. `CHANGELOG.md` y el `ADR-0103` conservan su texto sobre KDD a propósito — registran lo que era cierto cuando se escribieron, y editarlos falsificaría la historia que este repositorio guarda deliberadamente. **Falsabilidad, comprobada tras el barrido y no inferida de la lista de borrados:** toda referencia a los ocho ficheros borrados no devuelve nada fuera del ADR y del aviso de corrección, y `KDD`/`knowledge-first` solo sobreviven en los seis ficheros citados. **Lo que deja esta fila es la lección, no el barrido:** un concepto puede ser citado como precondición dura por una compuerta que todo el mundo implementa mientras no lo implementa nadie, y seguir así meses, porque nadie contrasta la prosa contra los datos. **CERRADA el 2026-08-18 — aterrizaron las dos mitades.** La del Tracker es `evolith_tracker#153` (`97e1bc8e`): `REQ-DIS-12` y `REQ-DIS-13` eliminados junto con la viñeta de gobierno de la subfase 01.1, la sección del catálogo de artefactos, la viñeta del blueprint, las cláusulas del índice de Discovery y los bloques de `.bmad-core`. **Los dos repositorios tenían la misma forma de dientes con distintas palabras:** el Core convertía *«el nivel de adopción de la Fase 1.1 ha sido declarado»* en precondición para abrir el Gate 1, y el Tracker daba al `REQ-DIS-13` el criterio de aceptación *«un resultado FAIL bloquea la apertura de la compuerta de Business Sign-Off»* — dos documentos, una compuerta inexistente, ambos bloqueando la única compuerta por la que pasa toda iniciativa. Lo que sobrevive es deliberado: `CHANGELOG.md` y el `ADR-0103` en el Core, y las dos filas del board de auditoría del Tracker, todos ellos registros de lo que era cierto cuando se escribieron. | Un concepto que describimos por todas partes y no construimos en ninguna, del que depende una de nuestras compuertas reales. | Que el modelo de cinco fases se lea igual en los documentos que en los datos, y que el Gate 1 deje de depender de una subfase que nadie puede ejecutar. | `Governance` | Cross | P2 | M | `COMPLETADO` | | [`GT-707`](./gap-reference-catalog.es.md#gt-707) | **Todo binario autónomo que publica este repositorio falla en `--help`, y ninguna release ha llevado nunca uno.** Medido el 2026-08-18 en los cuatro pull requests abiertos y, antes de ellos, en el push del tag `v1.3.6` y en el pull request que hizo por primera vez que el release pipeline corriera en pull requests: `smoke-test` y `smoke-test-functional` fallan con `ERR_REQUIRE_ESM: require() of ES Module /snapshot/…/@clack/prompts/dist/index.mjs`, lanzado desde `prompt.service.js` — así que el binario muere antes de parsear un argumento, en las tres plataformas. `gh release view` sobre `v1.3.0` y `v1.1.0` devuelve **cero assets**: `upload-assets` depende de `smoke-test`, así que el canal nunca ha entregado nada, y la propia puerta del pipeline es lo que lo detuvo. **Medido además, para que el próximo intento arranque aquí:** `@clack/prompts@1.5.1` es la ÚNICA dependencia solo-ESM del CLI (`chalk` 4.1.2, `ora` 5.4.1, `inquirer` 8.2.7 y `cli-table3` son todas CommonJS); `esbuild` la empaqueta en un CJS de 107 kB que carga limpio; y empaquetar con el fork mantenido `@yao-pkg/pkg@6` ELIMINA el `ERR_REQUIRE_ESM` y falla distinto — `MODULE_NOT_FOUND` por el mismo `.mjs`, porque el fichero no está en el snapshot — lo que significa que el empaquetador y el conjunto de assets son dos defectos distintos, no uno. **El arreglo que parece barato no lo es:** redirigir el import toca 6 ficheros de producción y ~24 specs que hacen `jest.mock('@clack/prompts')` con ese especificador exacto. **Deliberadamente NO arreglado dentro de los cuatro pull requests que lo encontraron:** están verdes en los 8 checks requeridos y esto es un fallo previo en un workflow no requerido; meter un rediseño de empaquetado ahí sería el cambio-ajeno-dentro-de-una-promoción que este tablero no deja de rechazar. **CERRADA el 2026-08-18 vendorizando a CommonJS las dependencias solo-ESM — y la primera evidencia de esta misma fila estaba mal dos veces, que es la parte que merece conservarse.** El binario empaquetado ya arranca: construido desde este árbol, `--help` sale **0**, `--version` imprime `1.3.2`, e `init --runtime nodejs --monorepo none --arch clean` sale **0** y escribe un satélite. **MAL #1 — «la única dependencia solo-ESM».** Esta fila lo midió sondeando `require('/package.json')`, que ocho de las 25 dependencias directas rechazan con `ERR_PACKAGE_PATH_NOT_EXPORTED` — un error que se lee como «bien». Leyendo los manifiestos DESDE DISCO aparecen **tres**: `@clack/prompts@1.5.1`, `conf@15.1.0` y `@modelcontextprotocol/sdk@1.29.0`. Dos se cargan en runtime y ambas quedan vendorizadas; el sdk de MCP es solo un `.d.ts` en este paquete, así que nunca entra en el snapshot. `pkg` llevaba avisando de `conf` todo el tiempo —muere nombrando `conf/package.json` y `config.service.js`— y nadie leyó más allá del primer error. **MAL #2 — el fallback apuntaba a nada.** `clack.ts` compila a `dist/infrastructure/prompts/` mientras el bundle se escribe en `dist/vendor/`, así que `require('./vendor/clack.cjs')` resolvía a una ruta inexistente. Nada falló en build; falló el BINARIO en ejecución, con el error del propio paquete, porque el `MODULE_NOT_FOUND` del fallback quedaba tragado por un rethrow del original. Ambos son ahora arreglos estructurales y no correcciones: el rethrow nombra LOS DOS fallos, y `vendor-esm-deps.mjs` lee la ruta relativa del shim COMPILADO y la resuelve — observado en rojo contra la ruta rota, con la ubicación resuelta en el mensaje. **Lo que se entrega:** `scripts/vendor-esm-deps.mjs` empaqueta cada dependencia solo-ESM con esbuild (clack 107 kB, conf 410 kB) y luego la carga de vuelta en un proceso hijo con `--no-experimental-require-module` —lo más cerca que un proceso Node normal está del contrato sin-ESM del snapshot— y compara su superficie de exports con la del paquete real. Dos shims (`prompts/clack.ts`, `config/conf-module.ts`) prueban PRIMERO EL PAQUETE y caen al bundle ante CUALQUIER fallo de carga: el orden es lo que mantiene las ~24 specs que hacen `jest.mock('@clack/prompts')` interceptando el mismo especificador de siempre, y «cualquier fallo» es porque la misma causa aflora como `ERR_REQUIRE_ESM` con un empaquetador y como `MODULE_NOT_FOUND` con otro. 106 suites / 1485 tests en verde, `tsc -b` limpio. El release pipeline no cambia: el arreglo es agnóstico del empaquetador, y su `smoke-test` es la falsabilidad que esta fila pedía. | El programa descargable que publicamos se cae al instante y, de hecho, nunca hemos publicado ninguno. | O un binario autónomo que arranca, o una retirada honesta del canal — no un check rojo que todo el mundo aprende a ignorar. | `Infra` | Cross | P2 | M | `COMPLETADO` | | [`GT-688`](./gap-reference-catalog.es.md#gt-688) | **Una composición de topologías confirmada se trunca a un solo id antes de la compuerta, así que un sistema mixto obtiene un veredicto verde por la única topología que sobrevivió.** Medido en vivo el 2026-08-14 contra el dist compilado: `manifestFromWorkspace` con `design.topologyConfirmedRefs: [modular-monolith, agentic-ai, event-driven]` devuelve `{"topology":"modular-monolith","facts":{"context":{"topologyRef":"modular-monolith"}}}` — **las otras dos no aparecen por ninguna parte**. Causa: `evaluation-context.builder.ts:26` `topology: ctx.topologyRef`, y `grep -n "design"` sobre ese fichero entero devuelve un hit, un comentario en `:149`. **Peor sin el escalar:** el manifiesto sale sin clave `topology`, el kind SALTA (`kind-evaluators.ts:363`), y el pipeline se reinventa una **con un regex sobre el YAML en disco**, ganando la primera coincidencia (`satellite-evaluation-pipeline.service.ts:354`). El contrato de resultado tampoco puede llevar dos (`TopologyEvaluationResult.topologyRef`, escalar obligatorio). Así que el kind devuelve `PASS, conformant: true` por el id que conservó — **un veredicto verde sobre un sistema del que la mitad nunca se comprobó**. **La observación del dueño que originó esta fila quedó REFUTADA a medias, y la mitad refutada importa:** el modelo NO es singular — `ADR-0079:44` rechaza por escrito el diseño excluyente, la transversalidad es declaración formal (`maturityLevel: "cross"` en las cinco no progresivas), y el corpus, el contrato de satélite y el evaluador de diseño son plurales; `evolith topology phase-artifacts -t agentic-ai,event-driven` sí une ambas. Lo que sobrevivió es el cable de APLICACIÓN. No lo cubre `MT-A*`: sus 26 filas están DONE y todas son corpus, esquema o documentación. | — | — | `Core Domain` | Cross | P1 | L | `COMPLETADO` | @@ -728,7 +731,7 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-706`](./gap-reference-catalog.es.md#gt-706) | **Nada asegura que los `exports` que un paquete declara resuelvan dentro de su propio tarball, así que un productor publica una subruta fantasma y solo la descubre un consumidor — una publicación demasiado tarde.** `contracts@1.1.0` declaró una subruta de export que no incluía; el fallo salió en el smoke de sala limpia de `infra-providers@1.2.1`, **después de que `core-domain@1.3.1` ya estuviera irreversiblemente en el registry**, dejando la release a medio entregar y sin despublicar posible pasadas 72 horas. La comprobación que existe es real y tiene la forma equivocada: `npm-release.yml:213` calcula «prometidos» como `[pkg.main, ...bin]`, y **`exports` no está en esa lista**. FALSABILIDAD DEMOSTRADA, OBSERVADA EN VERDE: un paquete de dos ficheros que declara `"./ingest"` con solo `dist/index.js` en disco pasa esa aserción corrida literal — `exit=0`, mientras `require pkg/ingest` responde `MODULE_NOT_FOUND`. El smoke de sala limpia tampoco lo cubre, y no es defecto suyo: resuelve lo que un paquete IMPORTA, así que el fantasma del productor es invisible hasta el turno de un consumidor, que es después del paso irreversible. Exposición: 3 de 8 paquetes publicables declaran **23 subrutas de export**, ninguna asegurada, y dos declaran además un `./*` sin cota. **ARREGLADO 2026-08-16 — `.harness/scripts/ci/67-validate-declared-exports.mjs`, corriendo en tiempo de PR sobre todos los workspaces publicables Y por paquete dentro del bucle de release, justo antes de `npm publish`.** Recoge cada hoja de texto del árbol de condiciones, así que `types` cuenta tanto como `default`, e incluye `main`/`bin`, siendo un superconjunto de la aserción que sustituye. **La propia afirmación de esta fila sobre el registry la refutó el guard en su primera corrida:** «22 de 22 resuelven, 0 fantasmas» excluía las claves con comodín por su propio filtro, y una está MUERTA — `core-domain` declara `./infrastructure/adapters/*` **sin ningún directorio `adapters`**, 0 coincidencias en un packlist de 796 ficheros, `MODULE_NOT_FOUND` en el 1.3.1 publicado, y **ningún commit de este repositorio llevó jamás ese path**. Borrada, no ampliada: nunca hubo nada detrás. Falsabilidad observada por los dos lados — rojo con la fixture `./ingest`, con `core-domain` de verdad, y con un fichero presente en disco pero excluido por `files`; verde con la misma fixture en cuanto se incluye y con el árbol entero, **68 destinos declarados en 9 paquetes**. | Un paquete puede prometer una ruta de import que nunca incluyó, y quien se entera es el siguiente paquete en publicarse. | La release se niega a publicar un manifiesto que miente, antes de que nada sea irreversible. | `Infra` | Cross | P1 | S | `COMPLETADO` | -**Progreso:** 677 / 706 completados · 3 en progreso · 0 pendientes · 26 diferidos +**Progreso:** 678 / 708 completados · 3 en progreso · 1 pendiente · 26 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 004f9be0..6c18efc5 100644 --- a/reference/core/control-center/gaps/gap-tracking.md +++ b/reference/core/control-center/gaps/gap-tracking.md @@ -4,6 +4,7 @@ **Status:** Active Tracking **Owner:** Evolith Architecture Board +**Last Updated:** 2026-09-05 (**Two new rows off one thread: a HIGH CVE that had been live on `main` for three days, and the reason it stopped nothing.** `GT-709` → DONE: `Security Audit` had been red since `b84523b4` not because of a dependency without a fix, but because `overrides.fast-uri` was pinned at `3.1.5`, **exactly the last vulnerable release of the 3.x line**, with the patch in `3.1.6` and inside the range `ajv` declares. Measured with the real guard, `63-validate-npm-audit-gate.mjs`: from **11 blocking rows / 7 high to 0 and 0**, with no exception declared. `GT-710` → PENDING: that gate **is not a required check**, and in the window it was red **eight** PRs were merged into `main`, four of them npm dependency changes. **What is worth keeping:** the mechanism used to close an advisory — the `overrides` entry — is the same one that later prevents closing it, and the only check that sees it blocks nothing.) **Last Updated:** 2026-08-18 (**One gap closed by the trigger it had written down for itself — which fired two days after it was written, and named the wrong release.** `GT-691` → DONE. The HIGH `js-yaml` CVE that blocked every promotion to `main` left the tree by an upgrade, not a dismissal: `@nestjs/swagger@11.4.7` — a PATCH on the `11.4.x` line the row had declared exhausted, not the stable 12 it said to wait for — declares `"js-yaml": "5.3.0"`, and `package-lock.json` now resolves `node_modules/@nestjs/swagger/node_modules/js-yaml` to it. `npm audit` goes from 1 high to 0 high / 0 critical; the row's falsifiability criterion was re-measured against the new tree rather than inherited, and neither falsifier fired. **What is worth carrying forward is the watch item, not the CVE:** the row correctly identified that the thing to watch was `@nestjs/swagger` rather than `js-yaml`, then tied that watch to a major release that had not shipped.) **Last Updated:** 2026-08-08 (**One gap closed by doing the irreversible thing the board had deliberately deferred — and the measurement taken first is what makes it worth recording.** `GT-622` → DONE, and the irreversible half the board had deferred is what closed it: the 210 orphaned `.github/workflows/ci.yml:codeql` code-scanning analyses are deleted from `refs/heads/main` (201) and `refs/heads/develop` (9), the dead key is absent from both, and the alert corpus is byte-identical across the deletion — 242 open / 82 dismissed / 60 fixed, CodeQL 75 / Scorecard 158 / Trivy 9. **Three of that row’s own claims did not survive re-measurement, and the row had already been re-measured twice without any of them surfacing, because each pass checked the COUNT and none checked the CLAIM:** the check was `neutral`, not red, from PR #250 onward; the configuration the warning names is on `refs/heads/develop`, not `main`, so only PRs into `develop` still carried it while `main` came back clean on its own from PR #420 with all 201 orphans still in place; and the 9 analyses on `develop` — the ones that mattered — were never counted. **The field that turned an owner judgement call into an easy one had never been read:** all 210 analyses carry `results_count: 0`, so what was discarded is 210 records of “scanned, found nothing” from a configuration dead since June. The irreversibility was real; the loss was not. 22 analyses under the dead key are deliberately left on `refs/pull/{4..17}/merge`, ephemeral per-PR refs that can never be the base of a PR. **Also observed while measuring, and not part of this closure:** the required-context set on `main` and `develop` is now **8**, having gained `Secret Detection (gitleaks)` — the promotion `GT-653` recorded as its one remaining item. The closure criterion that could only be observed on a PR into `develop` was observed on PR #440, the PR carrying this very closure, 105s after it opened — written after reading the check, not before. Counters recomputed from the rows: **640 / 653 done · 3 in progress · 3 pending · 7 deferred**.) **Last Updated (wave):** 2026-08-14 · second pass (**Thirteen more gaps registered from a 30-agent benchmark wave, four `DONE` rows corrected, and one of this morning's own rows narrowed.** The wave produced 40 candidates across three lenses, 16 went through adversarial verification with REFUTED as the default, **15 survived and 1 was formally killed** — "a tenant rule can never execute" is FALSE: a novel-id rule was RUN to a real `failed` verdict on two production-wired paths, which narrows [`GT-669`](./gap-reference-catalog.md#gt-669) to executor extensibility and documentation. **The four load-bearing measurements were re-run by hand before anything was written**, and the sharpest reproduced first try: the same pack returns **exit 0 `passed`** on `--engine opa` and **exit 2 `failed` with 2 blocking issues** on `--engine native` ([`GT-675`](./gap-reference-catalog.md#gt-675)). **Four `DONE` rows carry criteria the code does not satisfy** — `GT-321` (ledger with zero instantiations), `GT-266` (provisioning with zero call sites), `GT-221` (resource/prompt loggers with zero production callers), `GT-158` (nothing issues an approval token) — each annotated in place with its measurement and re-registered as a live row rather than reopened, because a closure record on a non-DONE row is a hard error in `08-validate-tracking`. `GT-518` is annotated the same way by [`GT-677`](./gap-reference-catalog.md#gt-677). **Ten of the thirteen rows are NARROWER than proposed**, and each records the sub-claim its verification killed. New ids `GT-675`…`GT-687`. Counters recomputed from the rows: **654 / 685 done · 2 in progress · 19 pending · 10 deferred**.) @@ -20,6 +21,8 @@ This board is the single source of truth for technical debt, gaps, opportunities | ID | Gap | In plain terms | What it fixes | Component | Phase | Criticality | Complexity | Status | |---|---|---|---|:---:|:---:|:---:|:---:|:---:| +| [`GT-710`](./gap-reference-catalog.md#gt-710) | **The gate that measures CVEs is not a required check, so eight merges went past it while it was red.** The nine required contexts on `main` and `develop` are `CodeQL SAST`, `Secret Detection (gitleaks)`, `Services build (GHCR)`, `Test`, `Test core`, `Test core-api`, `Test core-domain`, `Test mcp-server` and `Validate documentation`. **`Security Audit` is not among them**, and neither are `Trivy` or `build-and-test`. Measured 2026-09-05: between `b84523b4` (Sep 2), the commit where `Security Audit` turned red, and its fix in [`GT-709`](./gap-reference-catalog.md#gt-709), **eight pull requests were merged into `main` with the gate red** — four of them ([#664](https://github.com/beyondnetcode/evolith_arch32/pull/664), [#665](https://github.com/beyondnetcode/evolith_arch32/pull/665), [#666](https://github.com/beyondnetcode/evolith_arch32/pull/666), [#667](https://github.com/beyondnetcode/evolith_arch32/pull/667)) npm dependency changes, precisely the class of change that gate exists to judge. GitHub renders them `UNSTABLE` rather than `BLOCKED`, so ordinary review merges them without friction. **The workflow names this failure mode in its own words:** the comment in `sdk-cli-ci.yml` says a permanently red check trains reviewers to discount red, and then leaves the check out of the required set, which is the most direct way to guarantee exactly that. **Partly applied 2026-09-05:** `Security Audit` is now required on `main` and `develop` (10 contexts, verified), and the owner decided `Trivy` and `build-and-test` should be too. It stays open because **this row's falsifier has not fired yet** — there is no live high advisory to observe coming out `BLOCKED` rather than `UNSTABLE` — and because `build-and-test` **cannot be required as it stands**: it lives in `sdk-cli-release.yml`, whose `pull_request` trigger is path-filtered, and a required check behind a filter never reports, leaving the PR unmergeable with everything green. Registered separately from [`GT-709`](./gap-reference-catalog.md#gt-709) on purpose: that one was a CVE with a two-line fix, this is why the CVE could live for three days without stopping anything. | We have a security check that measures correctly and prevents nothing; eight changes landed while it was red. | Make an undeclared HIGH advisory block the merge instead of merely reporting it. | `Infra` | Cross | P1 | S | `PENDING` | +| [`GT-709`](./gap-reference-catalog.md#gt-709) | **An `overrides` pin added to close an advisory becomes the ceiling that prevents closing it the next time.** `Security Audit` had been red on `main` since `b84523b4` (2026-09-02) over `GHSA-jqff-g426-hqxp`, a HIGH CVE in `fast-uri` — and the cause was not a dependency without an upstream fix, but **two pins of our own left below the patched version**, which the gate reports in the same shape as somebody else's advisory. `overrides.fast-uri` was pinned at `3.1.5`, **exactly the last vulnerable release of the 3.x line**; the patch is `3.1.6`, inside the `^3.0.1` that `ajv@8.20.0` declares, so the fix fitted in the pin that was already there and nobody looked because it read as settled configuration. **Measured with `63-validate-npm-audit-gate.mjs`, the same guard CI runs, not inferred from changelogs:** `origin/main` reported **11 blocking rows / 7 high**; with `fast-uri` at `3.1.7`, 9 of the 11 go — its own four advisories plus the five `ajv`/`commitlint` chain rows that arrived *via* `fast-uri`. The remaining two were `browserslist` `4.28.4`, a dev-only transitive (`ts-jest`→`@babel/core`, `@nestjs/cli`→`webpack`) fixed in `4.28.7`: same pattern, same kind of override, pinned to `4.28.9`. **Result: 0 blocking rows, 0 high.** The moderate `qs` advisory survives on purpose — the gate does not block below HIGH. **CLOSED 2026-09-05** by [#689](https://github.com/beyondnetcode/evolith_arch32/pull/689), merge `eb458372` on `main`. **What is worth keeping is not the CVE but the failure mode:** the mechanism used to close an advisory is the same one that later holds it open, and nothing watches the pins. Same pattern as [`GT-691`](./gap-reference-catalog.md#gt-691), where the watch was tied to a major that had not shipped; there the mis-watched item was `@nestjs/swagger`, here it is the `overrides` block itself. That this gate blocks no merge at all is registered separately, as [`GT-710`](./gap-reference-catalog.md#gt-710). | The vulnerability check sat red for three days because of two versions we had ourselves pinned one release below the fix. | Stop the `overrides` block being the place a CVE settles in, and make the security check mean something again. | `Infra` | Cross | P2 | S | `DONE` | | [`GT-708`](./gap-reference-catalog.md#gt-708) | **A governance concept that existed only in prose, in two repositories, and was cited as a precondition by a gate that does exist.** «KDD» named two different things and neither was ever built: **Phase 1.1 — Knowledge-First Discovery**, an optional subphase with its own readiness gate and seven artifact templates; and **KDD — Knowledge-Driven Development**, a later reading from the 2026-07-04 owner session (`L-009`, `D-004`) that made it an optional section *inside the PRD*, activated per tenant. **Measured across every executable surface, and absent from all of them:** `phase-gates.rules.json` has five gates for phases 1..5; none of the seven KDD artifacts is among the 33 in `artifact-registry.json`; zero TypeScript files match `KDD`/`knowledge-first`/`knowledgeBrief`/`discoveryReadiness`/`storySeed`/`epicCandidate`; the CLI has 31 commands and zero mentions, and its `--phase discovery` maps to **phase 1 entire** (`phase-id.ts`: `f1: 'discovery'`), not to 1.1; the MCP server has zero; the Tracker has no screen and no entity; and `prd.schema.json` carries no KDD section, so `D-004` never reached a schema either. **The prose had teeth anyway:** `phase-1-business-signoff.md` made *"Phase 1.1 adoption level has been declared"* a **precondition for opening Gate 1**, and three rows of its evidence table carried clauses keyed to KDD levels — a gate nothing implements blocking a gate everything implements. **CLOSED 2026-08-18 by removal, on the owner's decision that Evolith Core and Tracker no longer carry the concept in any form.** 16 files deleted (the Phase 1.1 playbook and the seven artifact templates, EN and ES); Gate 1's precondition and its three KDD-keyed evidence clauses removed; the `Subphase 01.1` table, the playbook index row and the Story-Seed/Epic-Candidate references in the Phase 2 playbook and template index removed; `D-004`/`L-009` rewritten to what survives them — the PRD is the canonical floor and Gate 1 always requires it. **The retirement is [`ADR-0127`](../../architecture/adrs/core/0127-retire-knowledge-first-discovery.md), and `ADR-0103` is AMENDED by it rather than edited:** an accepted Architecture Board decision is superseded, not rewritten, so its reasoning stands and only its neighbour is gone. `CHANGELOG.md` and `ADR-0103` keep their KDD text on purpose — they record what was true when written, and editing them would falsify the history this repository keeps deliberately. **Falsifiability, checked after the sweep rather than inferred from the delete list:** every reference to the eight deleted filenames returns nothing outside the ADR and the correction notice, and `KDD`/`knowledge-first` survive only in the six files named above. **What this row leaves behind is the lesson, not the sweep:** a concept can be cited as a hard precondition by a gate that everything implements while being implemented by nothing, and stay that way for months, because nobody diffs the prose against the data. **CLOSED 2026-08-18 — both halves landed.** The Tracker half is `evolith_tracker#153` (`97e1bc8e`): `REQ-DIS-12` and `REQ-DIS-13` removed along with the subphase-01.1 governance bullet, the artifact-catalogue section, the blueprint bullet, the Discovery index clauses and the `.bmad-core` blocks. **Both repositories had the same shape of teeth in different words:** the Core made *"Phase 1.1 adoption level has been declared"* a precondition for opening Gate 1, and the Tracker gave `REQ-DIS-13` the acceptance criterion *"a FAIL result blocks opening the Business Sign-Off gate"* — two documents, one non-existent gate, both blocking the only gate every initiative must pass. What survives is deliberate: `CHANGELOG.md` and `ADR-0103` in the Core, and the Tracker's two audit-board rows, all of them records of what was true when written. | A concept we describe everywhere and have built nowhere, which one of our real gates depends on. | The five-phase model reads the same in the docs as in the data, and Gate 1 stops depending on a subphase nobody can execute. | `Governance` | Cross | P2 | M | `DONE` | | [`GT-707`](./gap-reference-catalog.md#gt-707) | **Every standalone binary this repository publishes fails on `--help`, and no release has ever carried one.** Measured 2026-08-18 across all four open pull requests and, before them, on the `v1.3.6` tag push and on the pull request that first made the release pipeline run on pull requests: `smoke-test` and `smoke-test-functional` fail with `ERR_REQUIRE_ESM: require() of ES Module /snapshot/…/@clack/prompts/dist/index.mjs`, raised from `prompt.service.js` — so the binary dies before parsing an argument, on all three platforms. `gh release view` on `v1.3.0` and `v1.1.0` returns **zero assets**: `upload-assets` depends on `smoke-test`, so the channel has never delivered anything, and the pipeline's own gate is what stopped it. **Measured further, so the next attempt starts here:** `@clack/prompts@1.5.1` is the ONLY ESM-only dependency the CLI has (`chalk` 4.1.2, `ora` 5.4.1, `inquirer` 8.2.7 and `cli-table3` are all CommonJS); `esbuild` bundles it to a 107 kB CJS file that loads clean; and packaging with the maintained fork `@yao-pkg/pkg@6` REMOVES the `ERR_REQUIRE_ESM` and then fails differently — `MODULE_NOT_FOUND` for the same `.mjs`, because the file is not in the snapshot — which means the packager and the asset set are two separate defects, not one. **The cheap-looking fix is not cheap:** redirecting the import touches 6 production files and ~24 spec files that `jest.mock('@clack/prompts')` by that exact specifier. **Deliberately NOT fixed inside the four pull requests that found it:** they are green on all 8 required checks and this is a pre-existing failure in a non-required workflow; smuggling a packaging redesign into them would be the unrelated-change-inside-a-promotion this board keeps refusing. **CLOSED 2026-08-18 by vendoring the ESM-only dependencies to CommonJS — and this row's own first evidence was wrong twice, which is the part worth keeping.** The packaged binary now runs: built from this tree, `--help` exits **0**, `--version` prints `1.3.2`, and `init --runtime nodejs --monorepo none --arch clean` exits **0** and writes a satellite. **WRONG #1 — "the only ESM-only dependency".** This row measured that by probing `require('/package.json')`, which eight of the 25 direct dependencies refuse with `ERR_PACKAGE_PATH_NOT_EXPORTED` — an error that reads like "fine". Reading the manifests FROM DISK instead found **three**: `@clack/prompts@1.5.1`, `conf@15.1.0` and `@modelcontextprotocol/sdk@1.29.0`. Two of them are loaded at runtime and both are vendored; the MCP sdk is only a `.d.ts` in this package, so it never enters the snapshot. `pkg` had been telling us about `conf` all along — it dies naming `conf/package.json` and `config.service.js` — and nobody read past the first error. **WRONG #2 — the fallback pointed at nothing.** `clack.ts` compiles to `dist/infrastructure/prompts/` while the bundle is written to `dist/vendor/`, so `require('./vendor/clack.cjs')` resolved to a path that does not exist. Nothing failed at build time; the BINARY failed at run time with the package's own error, because the fallback's `MODULE_NOT_FOUND` was swallowed by a rethrow of the original. Both are now structural fixes rather than corrections: the rethrow names BOTH failures, and `vendor-esm-deps.mjs` reads the relative path out of each COMPILED shim and resolves it — observed red against the broken path, with the resolved location in the message. **What ships:** `scripts/vendor-esm-deps.mjs` bundles each ESM-only dependency with esbuild (clack 107 kB, conf 410 kB), then loads each one back in a child process with `--no-experimental-require-module` — the closest an ordinary Node process gets to the snapshot's no-ESM contract — and compares its export surface against the real package. Two shims (`prompts/clack.ts`, `config/conf-module.ts`) try the PACKAGE FIRST and fall back to the bundle on ANY load failure: the order is what keeps the ~24 specs that `jest.mock('@clack/prompts')` intercepting the same specifier they always did, and "any failure" is because the same cause surfaces as `ERR_REQUIRE_ESM` under one packager and `MODULE_NOT_FOUND` under another. 106 suites / 1485 tests green, `tsc -b` clean. The release pipeline is unchanged: the fix is packager-agnostic, and its `smoke-test` is the falsifiability this row asked for. | The downloadable program we publish crashes instantly, and in fact we have never published one. | Either a standalone binary that runs, or an honest retirement of the channel — not a red check everybody learns to ignore. | `Infra` | Cross | P2 | M | `DONE` | | [`GT-688`](./gap-reference-catalog.md#gt-688) | **A confirmed topology composition is truncated to one id before the gate, so a mixed system gets a green topology verdict for the one topology that survived.** Measured live 2026-08-14 against the built dist: `manifestFromWorkspace` with `design.topologyConfirmedRefs: [modular-monolith, agentic-ai, event-driven]` returns `{"topology":"modular-monolith","facts":{"context":{"topologyRef":"modular-monolith"}}}` — **the other two appear nowhere**. Cause: `evaluation-context.builder.ts:26` `topology: ctx.topologyRef`, and `grep -n "design"` over that whole file returns one hit, a comment at `:149`. **Worse without the scalar:** the manifest carries no `topology` key at all, the kind SKIPs (`kind-evaluators.ts:363`), and the pipeline re-derives one **by regex over the YAML on disk**, first match wins (`satellite-evaluation-pipeline.service.ts:354`). The result contract cannot carry two either (`TopologyEvaluationResult.topologyRef`, required scalar). So the kind returns `PASS, conformant: true` for the id it kept — **a green verdict over a system half of which was never checked**. **The owner observation that produced this row was half REFUTED, and the refuted half matters:** the model is NOT singular — `ADR-0079:44` rejects the exclusive design in writing, transversality is a formal declaration (`maturityLevel: "cross"` on all five non-progressive manifests), and the corpus, satellite contract and design evaluator are plural; `evolith topology phase-artifacts -t agentic-ai,event-driven` really does union both. What survived is the ENFORCEMENT wire. Not covered by `MT-A*`: all 26 rows are DONE and every one is corpus/schema/docs work. | — | — | `Core Domain` | Cross | P1 | L | `DONE` | @@ -728,7 +731,7 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-706`](./gap-reference-catalog.md#gt-706) | **Nothing asserts that a package's own declared `exports` resolve inside its own tarball, so a producer publishes a phantom subpath and only a consumer discovers it — one publish too late.** `contracts@1.1.0` declared an export subpath it did not ship; the failure surfaced at `infra-providers@1.2.1`'s clean-room smoke, **after `core-domain@1.3.1` was already irreversibly on the registry**, leaving the release half-shipped with no unpublish available after 72 hours. The check that exists is real and the wrong shape: `npm-release.yml:213` computes "promised" as `[pkg.main, ...bin]`, and **`exports` is not in that list**. PROVEN FALSIFIABLE, OBSERVED GREEN: a two-file package declaring `"./ingest"` with only `dist/index.js` on disk passes that assertion run verbatim — `exit=0`, while `require pkg/ingest` answers `MODULE_NOT_FOUND`. The clean-room smoke does not cover it either, and that is not its defect: it resolves what a package IMPORTS, so a producer's phantom is invisible until a consumer's turn, which is after the irreversible step. Exposure: 3 of 8 publishable packages declare **23 export subpaths**, none asserted, two of them also declaring an unbounded `./*`. **FIXED 2026-08-16 — `.harness/scripts/ci/67-validate-declared-exports.mjs`, run at PR time over every publishable workspace AND per package inside the release loop, immediately before `npm publish`.** It collects every string leaf of the condition tree, so `types` counts as much as `default`, and folds in `main`/`bin`, making it a superset of the assertion it replaces. **The row's own claim about the registry was refuted by the guard on its first run:** "22 of 22 resolve, 0 phantom" excluded wildcard keys by its own filter, and one is DEAD — `core-domain` declares `./infrastructure/adapters/*` with **no `adapters` directory at all**, 0 matches in a 796-file packlist, `MODULE_NOT_FOUND` on the published 1.3.1, and **no commit in this repository ever carried that path**. Deleted, not widened: there was never anything behind it. Falsifiability observed on both sides — red on the `./ingest` fixture, on `core-domain` for real, and on a file present on disk but excluded by `files`; green on the same fixture once it ships and on the whole tree, **68 declared targets across 9 packages**. | A package can promise an import path it never shipped, and the next package to publish is the one that finds out. | The release refuses to publish a manifest that lies, before anything becomes irreversible. | `Infra` | Cross | P1 | S | `DONE` | -**Progress:** 677 / 706 done · 3 in progress · 0 pending · 26 deferred +**Progress:** 678 / 708 done · 3 in progress · 1 pending · 26 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 bca7817a..744f3ed6 100644 --- a/reference/core/control-center/maturity-reports/executive-summary.es.md +++ b/reference/core/control-center/maturity-reports/executive-summary.es.md @@ -27,8 +27,8 @@ La forma correcta de usar este resumen es simple: si necesitas contexto, abre so |---:|---|---|---| | 1 | Bloqueadores P0 | Impiden afirmar readiness productivo o release mayor. | [GT-435](../gaps/gap-reference-catalog.es.md#gt-435) | | 2 | Área de mayor riesgo | `Governance` tiene la mayor carga ponderada abierta. | [GT-670](../gaps/gap-reference-catalog.es.md#gt-670), [GT-585](../gaps/gap-reference-catalog.es.md#gt-585), [GT-669](../gaps/gap-reference-catalog.es.md#gt-669), [GT-672](../gaps/gap-reference-catalog.es.md#gt-672), [GT-689](../gaps/gap-reference-catalog.es.md#gt-689), [GT-588](../gaps/gap-reference-catalog.es.md#gt-588), +2 | -| 3 | Ganancias rápidas | Alta criticidad con complejidad XS/S. | [GT-684](../gaps/gap-reference-catalog.es.md#gt-684) | -| 4 | Ola P1 | Endurecimiento siguiente después de limpiar P0. | [GT-684](../gaps/gap-reference-catalog.es.md#gt-684), [GT-324](../gaps/gap-reference-catalog.es.md#gt-324), [GT-670](../gaps/gap-reference-catalog.es.md#gt-670), [GT-680](../gaps/gap-reference-catalog.es.md#gt-680), [GT-681](../gaps/gap-reference-catalog.es.md#gt-681), [GT-585](../gaps/gap-reference-catalog.es.md#gt-585), [GT-669](../gaps/gap-reference-catalog.es.md#gt-669), [GT-448](../gaps/gap-reference-catalog.es.md#gt-448) | +| 3 | Ganancias rápidas | Alta criticidad con complejidad XS/S. | [GT-684](../gaps/gap-reference-catalog.es.md#gt-684), [GT-710](../gaps/gap-reference-catalog.es.md#gt-710) | +| 4 | Ola P1 | Endurecimiento siguiente después de limpiar P0. | [GT-684](../gaps/gap-reference-catalog.es.md#gt-684), [GT-710](../gaps/gap-reference-catalog.es.md#gt-710), [GT-324](../gaps/gap-reference-catalog.es.md#gt-324), [GT-670](../gaps/gap-reference-catalog.es.md#gt-670), [GT-680](../gaps/gap-reference-catalog.es.md#gt-680), [GT-681](../gaps/gap-reference-catalog.es.md#gt-681), [GT-585](../gaps/gap-reference-catalog.es.md#gt-585), [GT-669](../gaps/gap-reference-catalog.es.md#gt-669), +1 | | 5 | P2/P3 | Solo después de estabilizar seguridad, CI, reglas y contratos. | [GT-444](../gaps/gap-reference-catalog.es.md#gt-444), [GT-464](../gaps/gap-reference-catalog.es.md#gt-464), [GT-674](../gaps/gap-reference-catalog.es.md#gt-674), [GT-685](../gaps/gap-reference-catalog.es.md#gt-685), [GT-686](../gaps/gap-reference-catalog.es.md#gt-686), [GT-687](../gaps/gap-reference-catalog.es.md#gt-687), +10 | ## Bloqueadores Actuales @@ -41,23 +41,23 @@ La forma correcta de usar este resumen es simple: si necesitas contexto, abre so | Indicador | Valor | |---|---:| -| Fecha canónica del tablero | 2026-08-18 | -| Gaps totales | 706 | -| Gaps cerrados | 677 | -| Gaps pendientes | 29 | +| Fecha canónica del tablero | 2026-09-05 | +| Gaps totales | 708 | +| Gaps cerrados | 678 | +| Gaps pendientes | 30 | | P0 abiertos | 1 | -| P1 abiertos | 8 | +| P1 abiertos | 9 | | P2 abiertos | 16 | -| Cierre total | 95.9% | -| Registros de evidencia de cierre | 659 | +| Cierre total | 95.8% | +| Registros de evidencia de cierre | 660 | | Readiness registrado | 4 PASS | | Área | Pendientes | P0 | P1 | Primeros IDs | |---|---:|---:|---:|---| | `Governance` | 8 | 0 | 3 | [GT-670](../gaps/gap-reference-catalog.es.md#gt-670), [GT-585](../gaps/gap-reference-catalog.es.md#gt-585), [GT-669](../gaps/gap-reference-catalog.es.md#gt-669), [GT-672](../gaps/gap-reference-catalog.es.md#gt-672), +4 | | `Cross` | 3 | 1 | 1 | [GT-435](../gaps/gap-reference-catalog.es.md#gt-435), [GT-448](../gaps/gap-reference-catalog.es.md#gt-448), [GT-651](../gaps/gap-reference-catalog.es.md#gt-651) | +| `Infra` | 5 | 0 | 2 | [GT-710](../gaps/gap-reference-catalog.es.md#gt-710), [GT-324](../gaps/gap-reference-catalog.es.md#gt-324), [GT-464](../gaps/gap-reference-catalog.es.md#gt-464), [GT-685](../gaps/gap-reference-catalog.es.md#gt-685), +1 | | `MCP Server` | 3 | 0 | 3 | [GT-684](../gaps/gap-reference-catalog.es.md#gt-684), [GT-680](../gaps/gap-reference-catalog.es.md#gt-680), [GT-681](../gaps/gap-reference-catalog.es.md#gt-681) | -| `Infra` | 4 | 0 | 1 | [GT-324](../gaps/gap-reference-catalog.es.md#gt-324), [GT-464](../gaps/gap-reference-catalog.es.md#gt-464), [GT-685](../gaps/gap-reference-catalog.es.md#gt-685), [GT-692](../gaps/gap-reference-catalog.es.md#gt-692) | | `Core Domain` | 3 | 0 | 0 | [GT-674](../gaps/gap-reference-catalog.es.md#gt-674), [GT-687](../gaps/gap-reference-catalog.es.md#gt-687), [GT-678](../gaps/gap-reference-catalog.es.md#gt-678) | ## 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 9ec9256a..a68804bb 100644 --- a/reference/core/control-center/maturity-reports/executive-summary.md +++ b/reference/core/control-center/maturity-reports/executive-summary.md @@ -27,8 +27,8 @@ Use this summary with a simple rule: if you need context, open only the linked I |---:|---|---|---| | 1 | P0 blockers | They prevent production-readiness or major-release confidence. | [GT-435](../gaps/gap-reference-catalog.md#gt-435) | | 2 | Highest-risk area | `Governance` has the largest weighted open load. | [GT-670](../gaps/gap-reference-catalog.md#gt-670), [GT-585](../gaps/gap-reference-catalog.md#gt-585), [GT-669](../gaps/gap-reference-catalog.md#gt-669), [GT-672](../gaps/gap-reference-catalog.md#gt-672), [GT-689](../gaps/gap-reference-catalog.md#gt-689), [GT-588](../gaps/gap-reference-catalog.md#gt-588), +2 | -| 3 | Quick wins | High criticality with XS/S complexity. | [GT-684](../gaps/gap-reference-catalog.md#gt-684) | -| 4 | P1 wave | Next hardening after P0 is cleared. | [GT-684](../gaps/gap-reference-catalog.md#gt-684), [GT-324](../gaps/gap-reference-catalog.md#gt-324), [GT-670](../gaps/gap-reference-catalog.md#gt-670), [GT-680](../gaps/gap-reference-catalog.md#gt-680), [GT-681](../gaps/gap-reference-catalog.md#gt-681), [GT-585](../gaps/gap-reference-catalog.md#gt-585), [GT-669](../gaps/gap-reference-catalog.md#gt-669), [GT-448](../gaps/gap-reference-catalog.md#gt-448) | +| 3 | Quick wins | High criticality with XS/S complexity. | [GT-684](../gaps/gap-reference-catalog.md#gt-684), [GT-710](../gaps/gap-reference-catalog.md#gt-710) | +| 4 | P1 wave | Next hardening after P0 is cleared. | [GT-684](../gaps/gap-reference-catalog.md#gt-684), [GT-710](../gaps/gap-reference-catalog.md#gt-710), [GT-324](../gaps/gap-reference-catalog.md#gt-324), [GT-670](../gaps/gap-reference-catalog.md#gt-670), [GT-680](../gaps/gap-reference-catalog.md#gt-680), [GT-681](../gaps/gap-reference-catalog.md#gt-681), [GT-585](../gaps/gap-reference-catalog.md#gt-585), [GT-669](../gaps/gap-reference-catalog.md#gt-669), +1 | | 5 | P2/P3 | Only after security, CI, rules, and contracts stabilize. | [GT-444](../gaps/gap-reference-catalog.md#gt-444), [GT-464](../gaps/gap-reference-catalog.md#gt-464), [GT-674](../gaps/gap-reference-catalog.md#gt-674), [GT-685](../gaps/gap-reference-catalog.md#gt-685), [GT-686](../gaps/gap-reference-catalog.md#gt-686), [GT-687](../gaps/gap-reference-catalog.md#gt-687), +10 | ## Current Blockers @@ -41,23 +41,23 @@ Use this summary with a simple rule: if you need context, open only the linked I | Indicator | Value | |---|---:| -| Canonical board date | 2026-08-18 | -| Total gaps | 706 | -| Closed gaps | 677 | -| Open gaps | 29 | +| Canonical board date | 2026-09-05 | +| Total gaps | 708 | +| Closed gaps | 678 | +| Open gaps | 30 | | Open P0 | 1 | -| Open P1 | 8 | +| Open P1 | 9 | | Open P2 | 16 | -| Total closure | 95.9% | -| Closure evidence records | 659 | +| Total closure | 95.8% | +| Closure evidence records | 660 | | Recorded readiness | 4 PASS | | Area | Open | P0 | P1 | First IDs | |---|---:|---:|---:|---| | `Governance` | 8 | 0 | 3 | [GT-670](../gaps/gap-reference-catalog.md#gt-670), [GT-585](../gaps/gap-reference-catalog.md#gt-585), [GT-669](../gaps/gap-reference-catalog.md#gt-669), [GT-672](../gaps/gap-reference-catalog.md#gt-672), +4 | | `Cross` | 3 | 1 | 1 | [GT-435](../gaps/gap-reference-catalog.md#gt-435), [GT-448](../gaps/gap-reference-catalog.md#gt-448), [GT-651](../gaps/gap-reference-catalog.md#gt-651) | +| `Infra` | 5 | 0 | 2 | [GT-710](../gaps/gap-reference-catalog.md#gt-710), [GT-324](../gaps/gap-reference-catalog.md#gt-324), [GT-464](../gaps/gap-reference-catalog.md#gt-464), [GT-685](../gaps/gap-reference-catalog.md#gt-685), +1 | | `MCP Server` | 3 | 0 | 3 | [GT-684](../gaps/gap-reference-catalog.md#gt-684), [GT-680](../gaps/gap-reference-catalog.md#gt-680), [GT-681](../gaps/gap-reference-catalog.md#gt-681) | -| `Infra` | 4 | 0 | 1 | [GT-324](../gaps/gap-reference-catalog.md#gt-324), [GT-464](../gaps/gap-reference-catalog.md#gt-464), [GT-685](../gaps/gap-reference-catalog.md#gt-685), [GT-692](../gaps/gap-reference-catalog.md#gt-692) | | `Core Domain` | 3 | 0 | 0 | [GT-674](../gaps/gap-reference-catalog.md#gt-674), [GT-687](../gaps/gap-reference-catalog.md#gt-687), [GT-678](../gaps/gap-reference-catalog.md#gt-678) | ## Source and Refresh Rule diff --git a/reference/core/control-center/maturity-reports/maturity-evidence.json b/reference/core/control-center/maturity-reports/maturity-evidence.json index 6e56b236..3b9e25b5 100644 --- a/reference/core/control-center/maturity-reports/maturity-evidence.json +++ b/reference/core/control-center/maturity-reports/maturity-evidence.json @@ -1,6 +1,6 @@ { "schemaVersion": "1.0.0", - "asOf": "2026-08-18", + "asOf": "2026-09-05", "checks": [ { "id": "cli-baseline", diff --git a/reference/core/control-center/maturity-reports/maturity-reconciliation.json b/reference/core/control-center/maturity-reports/maturity-reconciliation.json index b53c8fd8..1bdc8ede 100644 --- a/reference/core/control-center/maturity-reports/maturity-reconciliation.json +++ b/reference/core/control-center/maturity-reports/maturity-reconciliation.json @@ -1,16 +1,16 @@ { "schemaVersion": "1.0.0", "scope": "evolith-core", - "asOf": "2026-08-18", + "asOf": "2026-09-05", "gaps": { - "total": 706, - "done": 677, - "pending": 0, + "total": 708, + "done": 678, + "pending": 1, "inProgress": 3, "deferred": 26 }, "evidence": { - "closureRecords": 659, + "closureRecords": 660, "cliPackage": "@beyondnet/evolith-cli@1.3.2", "adrCount": 144, "rulesetCount": 184,