From 10c415f2fc5c4114cf7fcbdae9c20757e0ac8c76 Mon Sep 17 00:00:00 2001 From: Alberto Arroyo Raygada Date: Sat, 8 Aug 2026 13:01:58 -0500 Subject: [PATCH] fix(harness): GT-658 ask whether the renderer works before accusing 371 diagrams, and pin it (#444) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `01-validate-docs --render-mermaid` spawned `npx -y @mermaid-js/mermaid-cli` with NO version, once per diagram. On 2026-08-08 that install came out INCOMPLETE on a runner — `Cannot find package 'import-meta-resolve'`, which the package DOES declare, so the tree was half-written rather than the publish being bad. Every diagram then failed as "mermaid render failed", blamed on the document containing it; 23m21s to reach hundreds of wrong accusations. Two defects in one invocation. The pin: `npx -y` with no version resolves @latest when CI runs, and the tool is in package-lock.json ZERO times, so npm ci, npm audit, Dependabot and GT-657's audit gate are all blind to it. The blame: the corpus and the renderer are different things and only one can be broken by a commit, but the guard asked about 371 diagrams before asking whether the renderer worked, so the loudest signal pointed at the only innocent party. A preflight now renders one trivial diagram first. A failure there is ONE error naming the renderer, stating no diagram is implicated. Exit 0 is not proof — a renderer that writes no SVG has not rendered. Pinned to 11.16.0 in one constant both paths read. Both halves measured on the runner before merging: broken renderer (non-existent pin) -> exit 1, ZERO "mermaid render failed" healthy renderer (dispatched full corpus) -> 405 of 405 rendered, success The PR's own green was NOT accepted as verification: it rendered 0 of 405, because pull_request builds scope rendering to changed files and this change touches no diagram-bearing markdown. A workflow_dispatch, where GITHUB_BASE_REF is unset and the whole corpus renders, is what proved it. The first version of this preflight carried the defect it removes: on a machine with no usable Chromium mermaid-cli exits 1 silently, and the message read "exited 1 on a trivial diagram: ". It now says the process was silent, names the likely cause and prints the command to reproduce. NOT done, deliberately: declaring the tool a pinned devDependency is the fuller fix but drags Puppeteer and Chromium into every npm ci. Recorded as a separate decision. Board: 643 / 656 done, 3 in progress, 3 pending, 7 deferred. --- .harness/scripts/ci/01-validate-docs.mjs | 106 +++++++++++++++++- .../evidence/gap-closure-evidence.json | 14 +++ .../gaps/gap-reference-catalog.es.md | 21 ++++ .../gaps/gap-reference-catalog.md | 21 ++++ .../control-center/gaps/gap-tracking.es.md | 3 +- .../core/control-center/gaps/gap-tracking.md | 3 +- .../maturity-reports/executive-summary.es.md | 6 +- .../maturity-reports/executive-summary.md | 6 +- .../maturity-reconciliation.json | 6 +- 9 files changed, 170 insertions(+), 16 deletions(-) diff --git a/.harness/scripts/ci/01-validate-docs.mjs b/.harness/scripts/ci/01-validate-docs.mjs index 46a2fe71..24885126 100644 --- a/.harness/scripts/ci/01-validate-docs.mjs +++ b/.harness/scripts/ci/01-validate-docs.mjs @@ -377,6 +377,90 @@ function getChangedMarkdownFilesForRendering() { return null; } +// GT-658: pinned on purpose. `npx -y @mermaid-js/mermaid-cli` without a version +// resolves `@latest` at the instant CI runs, so what executes is decided by +// whoever published most recently and by nothing in this repository. Bump this +// deliberately, in a commit, the way every other pinned tool here is bumped. +const MERMAID_CLI = "@mermaid-js/mermaid-cli@11.16.0"; + +/** The npx invocation, in one place so the preflight and the render cannot drift apart. */ +function mermaidArgs(input, output) { + return ["-y", MERMAID_CLI, "-i", input, "-o", output, "-b", "transparent", + "-p", path.join(root, ".harness/scripts/puppeteer-config.json")]; +} + +/** + * GT-658: render one trivial diagram before rendering the corpus. + * + * On 2026-08-08 the npx install of mermaid-cli came out INCOMPLETE on a runner — + * `Cannot find package 'import-meta-resolve'`, which the package does declare, so + * the tree was half-written rather than the publish being bad. Every one of the + * 371 diagrams then failed, each reported as "mermaid render failed" against a + * document whose diagram was perfectly fine, and the job burned 23 minutes + * arriving at 371 wrong accusations. + * + * The diagram corpus and the renderer are two different things and only one of + * them can be broken by a commit. This asks which one FIRST, so a broken renderer + * costs one honest failure instead of 371 misleading ones. + * + * @returns {Promise} an error message when the renderer cannot run + */ +function preflightMermaidRenderer(outputDirectory) { + return new Promise((resolve) => { + const input = path.join(outputDirectory, "000-preflight.mmd"); + const output = path.join(outputDirectory, "000-preflight.svg"); + // The smallest diagram that still exercises parse -> layout -> SVG. + fs.writeFileSync(input, "graph TD\n A[preflight] --> B[ok]\n", "utf8"); + + const child = spawn("npx", mermaidArgs(input, output), { encoding: "utf8" }); + let stderr = ""; + let stdout = ""; + let settled = false; + + const done = (message) => { + if (settled) return; + settled = true; + clearTimeout(timer); + resolve(message); + }; + + // Generous: this is the invocation that pays for the install and, on a cold + // runner, for the Chromium download too. + const timer = setTimeout(() => { + child.kill("SIGKILL"); + done(`the renderer did not finish a trivial diagram within 180s (${MERMAID_CLI})`); + }, 180000); + + child.stdout?.on("data", (c) => { stdout += c.toString(); }); + child.stderr?.on("data", (c) => { stderr += c.toString(); }); + child.on("error", (error) => done(`the renderer could not be started: ${error.message}`)); + child.on("close", (status) => { + if (status !== 0) { + // mermaid-cli can exit non-zero having written NOTHING to either stream — + // observed on a machine without a usable Chromium. The first version of + // this preflight interpolated an empty string and produced "exited 1 on a + // trivial diagram: ", which is the same uninformative-message defect this + // whole guard change exists to remove. Say that it was silent, and give + // the command back so the reader can run it themselves. + const detail = (stderr || stdout).trim(); + done( + `the renderer exited ${status} on a trivial diagram` + + (detail ? `: ${detail.slice(0, 600)}` : ' and printed NOTHING to stdout or stderr' + + ' (a browser it cannot launch usually fails this way). Reproduce with:\n' + + ` npx -y ${MERMAID_CLI} -i .mmd -o out.svg -b transparent -p .harness/scripts/puppeteer-config.json`), + ); + return; + } + // Exit 0 is not proof: a renderer that writes nothing has not rendered. + if (!fs.existsSync(output) || fs.statSync(output).size === 0) { + done(`the renderer exited 0 but produced no SVG for a trivial diagram (${MERMAID_CLI})`); + return; + } + done(null); + }); + }); +} + function renderMermaidBlock(block, outputDirectory, index) { return new Promise((resolve) => { const basename = `${String(index + 1).padStart(3, "0")}-${path @@ -387,11 +471,7 @@ function renderMermaidBlock(block, outputDirectory, index) { fs.writeFileSync(input, `${block.body}\n`, "utf8"); - const child = spawn( - "npx", - ["-y", "@mermaid-js/mermaid-cli", "-i", input, "-o", output, "-b", "transparent", "-p", path.join(root, ".harness/scripts/puppeteer-config.json")], - { encoding: "utf8" }, - ); + const child = spawn("npx", mermaidArgs(input, output), { encoding: "utf8" }); let stdout = ""; let stderr = ""; @@ -450,6 +530,22 @@ async function renderMermaidBlocks() { const blocksToRender = changedMarkdownFiles ? mermaidBlocks.filter((block) => changedMarkdownFiles.has(path.resolve(block.file))) : mermaidBlocks; + if (blocksToRender.length === 0) { + return; + } + + // GT-658: ask whether the RENDERER works before asking whether 371 diagrams do. + const rendererError = await preflightMermaidRenderer(outputDirectory); + if (rendererError) { + console.error( + `\n❌ Mermaid rendering did not run: ${rendererError}\n` + + ` ${blocksToRender.length} diagram(s) were NOT checked, and none of them is implicated —\n` + + ` this is the renderer, not the corpus. Nothing in this repository can fix it by\n` + + ` editing a diagram; re-run, or bump the pin in ${path.relative(root, ".harness/scripts/ci/01-validate-docs.mjs")}.\n`, + ); + process.exit(1); + } + const concurrency = Math.max(4, Math.min(os.cpus().length, 16)); const workers = []; diff --git a/reference/core/control-center/evidence/gap-closure-evidence.json b/reference/core/control-center/evidence/gap-closure-evidence.json index ffb3ac26..1561ef15 100644 --- a/reference/core/control-center/evidence/gap-closure-evidence.json +++ b/reference/core/control-center/evidence/gap-closure-evidence.json @@ -9796,6 +9796,20 @@ ], "dependencyDisposition": "accepted-scope", "dependencyRationale": "GHSA-pm4m-ph32-ghv5 in js-yaml, reached through @nestjs/swagger, is accepted rather than fixed because no fix exists to apply: all three published swagger releases pin js-yaml exactly and all three pin a vulnerable version (11.4.4 -> 4.1.1, 11.4.5 -> 4.3.0, 11.4.6 -> 5.2.1), and npm overrides do not rewrite that nested exact spec — measured through a top-level override, a scoped override, both with the unrelated nested override objects removed, and through --package-lock-only as well as a real npm install. The acceptance is recorded in .harness/config/npm-audit-exceptions.json against the advisory AND its path, and 63-validate-npm-audit-gate fails the day it stops matching a real advisory, so it cannot outlive the hole it excuses. The three moderate advisories stay below the unchanged HIGH threshold and are untouched." + }, + { + "id": "GT-658", + "closedAt": "2026-08-08", + "closureCommit": "8511e17c", + "evidence": [ + ".harness/scripts/ci/01-validate-docs.mjs" + ], + "validationCommands": [ + "node --check .harness/scripts/ci/01-validate-docs.mjs", + "node .harness/scripts/ci/01-validate-docs.mjs" + ], + "dependencyDisposition": "accepted-scope", + "dependencyRationale": "The renderer stays an npx invocation rather than a lockfile dependency, pinned but not declared. Declaring @mermaid-js/mermaid-cli as a devDependency is the fuller fix -- it would put the renderer under npm audit and Dependabot and make npm ci fail loudly on the partial install that caused this -- but it drags Puppeteer and a Chromium download into every npm ci, for every developer, to render diagrams only CI renders. That trade is recorded as a separate decision rather than taken as a side effect of a bug fix. The happy path was NOT verified locally and this is stated rather than implied: mermaid-cli exits 1 with empty stdout and stderr on this machine, which has no usable Chromium; the corpus render is exercised by the Evolith Core Validation job on the runner, where it has been green on PRs 440, 441 and 442." } ] } 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 4d63891c..76ede5f6 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -8450,3 +8450,24 @@ La lección es la del propio tablero y esta vez la pagó quien medía: un `conta - **El check `Trivy` reporta esto como una alerta HIGH nueva, y no es una regresión — se registra porque la reversión se intentó y hubo que deshacerla.** Trivy compara alertas sobre las líneas cambiadas, así que sustituir un id de advisory por otro en la misma posición del lockfile se lee como "1 new alert including 1 high severity security vulnerability". Medido en ambos sentidos en vez de argumentado: `/swagger.4.4` fija `js-yaml 4.1.1`, que arrastra **tres** advisories —`GHSA-52cp-r559-cp3m` (HIGH), `GHSA-5p4m-2wfm-xmqj` (HIGH) y `GHSA-h67p-54hq-rp68` (moderate)—, mientras que `11.4.6` fija `5.2.1`, que arrastra **uno**. El bump quita dos advisories high y deja uno, así que el árbol que produce es estrictamente mejor y la "alerta nueva" es el id cambiando, no un agujero abriéndose. **El casi-error es la lección:** en una primera lectura de Trivy se revirtió el bump como si él hubiera causado la alerta, y solo enumerar los advisories en AMBAS versiones mostró que la reversión empeoraba las cosas. El conteo de filas de npm dice lo contrario que el conteo de advisories: 11.4.6 produce 2 filas high (js-yaml y su padre) frente a 1 de 11.4.4, llevando un tercio de los advisories. Un campo `doNotRevertTheSwaggerBump` en la entrada de excepciones lo traslada al siguiente lector. - **Lo que deliberadamente NO se cubre:** tres advisories `moderate` (`hono`, `@hono/node-server`, `@modelcontextprotocol/sdk`) quedan por debajo del umbral HIGH y siguen intactos, como estaban. Bajar el umbral es otra decisión y esta fila no la toma. - **Estado:** `COMPLETADO` (2026-08-08) + +#### GT-658 + +**Título:** Un renderizador roto acusó a cientos de diagramas inocentes, y nada del repositorio elegía qué renderizador corría + +- **Propósito / Problema:** Que un guard de documentación culpe a lo que de verdad está roto, y que una herramienta sin fijar deje de decidir qué ejecuta CI. +- **Evidencia:** `01-validate-docs --render-mermaid` lanzaba `npx -y @mermaid-js/mermaid-cli` **sin versión**, una vez por diagrama. El 2026-08-08 esa instalación salió INCOMPLETA en un runner: `Cannot find package 'import-meta-resolve'` —una dependencia que `@mermaid-js/mermaid-cli@11.16.0` **sí** declara, verificado con `npm view`—, así que el fallo fue una instalación a medio escribir y no una publicación mala. Entonces falló cada diagrama como `mermaid render failed`, atribuido al documento que lo contenía. El job tardó **23m21s** en producir esos fallos, en la promoción `develop` → `main`, y el mismo comando había salido bien veinte minutos antes en una ejecución concurrente — intermitente, del entorno, e imposible de arreglar con ningún cambio en los documentos que acusaba. +- **Dos defectos en una sola invocación.** + - **El pin.** `npx -y` sin versión resuelve `@latest` cuando corre CI, así que lo que se ejecuta lo elige quien haya publicado más recientemente. Peor: la herramienta aparece **cero** veces en `package-lock.json`: no la instala `npm ci`, no la ve `npm audit`, no la sube Dependabot, y el gate de auditoría de [`GT-657`](./gap-reference-catalog.es.md#gt-657) —construido el mismo día— no puede razonar sobre ella. Un binario que descarga Chromium y corre en CI estaba fuera de todos los controles de cadena de suministro del repositorio. + - **La culpa.** El corpus de diagramas y el renderizador son cosas distintas, y solo una puede romperla un commit. El guard preguntaba "¿es válido cada uno de estos 371 diagramas?" antes de preguntar "¿funciona el renderizador?", así que un renderizador roto produjo cientos de acusaciones seguras y equivocadas, cada una nombrando un fichero y una línea cuyo contenido estaba bien. +- **Qué significa:** la señal más ruidosa apuntaba a la única parte inocente. Quien siguiera esos fallos editaría diagramas correctos buscando un defecto que nunca estuvo ahí, que es peor que no tener señal. +- **Componente:** `.harness` · **Criticidad:** P2 · **Complejidad:** S +- **Principal:** `S` · **Interés:** `MED` · **Base:** `estimate` +- **Procedencia:** Encontrado el 2026-08-08 en el CI del PR #443, la promoción que llevaba GT-622/656/657. Diagnosticado en vez de despachado como flake: la ejecución que falló era la de `push`, la del propio PR pasó, y la herramienta declara el paquete que no encontraba — tres hechos que juntos dicen "instalación incompleta", no "diagramas rotos". +- **Criterios de aceptación:** + - [x] Un renderizador que no puede correr produce UN error que lo nombra y dice explícitamente que ningún diagrama está implicado — observado fijando una versión inexistente: exit 1, **0** líneas `mermaid render failed`, y "405 diagram(s) were NOT checked, and none of them is implicated". + - [x] El exit 0 no se acepta como prueba de renderizado: un preflight que sale 0 sin escribir SVG, o con uno vacío, falla tan alto como un crash. Un renderizador que no produjo nada no ha renderizado. + - [x] La versión que ejecuta CI la elige un commit, no la publicación más reciente del registro — fijada en `11.16.0`, en una única constante que leen tanto el preflight como el render por diagrama, para que no puedan divergir. + - [x] El camino feliz sigue renderizando el corpus y el guard sigue fallando ante un diagrama de verdad malformado — la comprobación del corpus no cambia, solo el orden de las preguntas. +- **Deliberadamente NO hecho, y por qué es una decisión aparte:** declarar la herramienta como devDependency fijada es el arreglo completo — mete el renderizador en el lockfile, bajo `npm audit`, bajo Dependabot, y hace que `npm ci` falle ruidosamente ante la instalación parcial que causó esto. También arrastra Puppeteer y una descarga de Chromium a cada `npm ci`, de cada desarrollador, para renderizar diagramas que solo renderiza CI. Ese intercambio merece hacerse a propósito, no como efecto colateral de arreglar un bug. +- **Estado:** `COMPLETADO` (2026-08-08) diff --git a/reference/core/control-center/gaps/gap-reference-catalog.md b/reference/core/control-center/gaps/gap-reference-catalog.md index f728f2d2..768bd8fa 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -8545,3 +8545,24 @@ The lesson is the board's own, and this time the measurer paid it: a `contains` - **The `Trivy` check reports this as a NEW high alert, and that is not a regression — recorded because the revert was attempted and had to be undone.** Trivy compares alerts on the changed lines, so replacing one advisory id with another at the same lockfile position reads as "1 new alert including 1 high severity security vulnerability". Measured both ways rather than argued: `/swagger.4.4` pins `js-yaml 4.1.1`, which carries **three** advisories — `GHSA-52cp-r559-cp3m` (HIGH), `GHSA-5p4m-2wfm-xmqj` (HIGH) and `GHSA-h67p-54hq-rp68` (moderate) — while `11.4.6` pins `5.2.1`, which carries **one**. The bump removes two high advisories and leaves one, so the tree it produces is strictly better and the "new alert" is the id changing, not a hole opening. **The near-miss is the lesson:** on a first reading of Trivy the bump was reverted as if it had caused the alert, and only enumerating the advisories at BOTH versions showed the revert made things worse. npm's row count says the opposite of the advisory count here — 11.4.6 produces 2 high ROWS (js-yaml and its parent) against 11.4.4's 1, while carrying a third of the advisories. A `doNotRevertTheSwaggerBump` field in the exceptions entry carries this to the next reader. - **What is deliberately NOT covered:** three `moderate` advisories (`hono`, `@hono/node-server`, `@modelcontextprotocol/sdk`) stay below the HIGH threshold and are untouched, as they were before. Raising the threshold is a separate decision and this row does not take it. - **Status:** `DONE` (2026-08-08) + +#### GT-658 + +**Title:** A broken renderer accused hundreds of innocent diagrams, and nothing in the repository chose which renderer ran + +- **Purpose / Problem:** Make a documentation guard blame the thing that is actually broken, and stop an unpinned tool from deciding what CI executes. +- **Evidence:** `01-validate-docs --render-mermaid` spawned `npx -y @mermaid-js/mermaid-cli` with **no version**, once per diagram. On 2026-08-08 that install came out INCOMPLETE on a runner: `Cannot find package 'import-meta-resolve'` — a dependency `@mermaid-js/mermaid-cli@11.16.0` **does** declare, verified with `npm view`, so the failure was a half-written install and not a bad publish. Every diagram then failed as `mermaid render failed`, attributed to the document containing it. The job took **23m21s** to produce those failures, on the `develop` → `main` promotion, and the same command had succeeded twenty minutes earlier in a concurrent run — intermittent, environmental, and unfixable by any change to the documents it accused. +- **Two defects in one invocation.** + - **The pin.** `npx -y` with no version resolves `@latest` when CI runs, so what executes is chosen by whoever published most recently. Worse, the tool appears in `package-lock.json` **zero** times: `npm ci` does not install it, `npm audit` cannot see it, Dependabot cannot bump it, and [`GT-657`](./gap-reference-catalog.md#gt-657)'s audit gate — built the same day — cannot reason about it. A binary that downloads Chromium and runs in CI sat outside every supply-chain control this repository has. + - **The blame.** The diagram corpus and the renderer are different things, and only one of them can be broken by a commit. The guard asked "is each of these 371 diagrams valid?" before asking "does the renderer work?", so a broken renderer produced hundreds of confident, wrong accusations — each naming a file and a line whose content was fine. +- **What it means:** the loudest signal pointed at the only party that was innocent. A reader following those failures would edit correct diagrams looking for a defect that was never there, which is worse than no signal. +- **Component:** `.harness` · **Criticality:** P2 · **Complexity:** S +- **Principal:** `S` · **Interest:** `MED` · **Basis:** `estimate` +- **Provenance:** Found on 2026-08-08 in the CI of PR #443, the promotion carrying GT-622/656/657. Diagnosed rather than waved through as flake: the run that failed was the `push` build, the PR's own build passed, and the tool declares the package it could not find — three facts that together say "incomplete install", not "broken diagrams". +- **Acceptance criteria:** + - [x] A renderer that cannot run produces ONE error naming the renderer, and states explicitly that no diagram is implicated — observed by pinning a non-existent version: exit 1, **0** `mermaid render failed` lines, and "405 diagram(s) were NOT checked, and none of them is implicated". + - [x] Exit 0 is not accepted as proof of rendering: a preflight that exits 0 while writing no SVG, or an empty one, fails as loudly as a crash. A renderer that produced nothing has not rendered. + - [x] The version CI executes is chosen by a commit, not by the registry's newest publish — pinned to `11.16.0`, in one constant that both the preflight and the per-diagram render read, so the two cannot drift apart. + - [x] The happy path still renders the corpus and the guard still fails on a genuinely malformed diagram — the corpus check is unchanged, only the order of questions is. +- **Deliberately NOT done, and why it is a separate decision:** declaring the tool as a pinned devDependency is the fuller fix — it puts the renderer in the lockfile, under `npm audit`, under Dependabot, and makes `npm ci` fail loudly on the very partial install that caused this. It also drags Puppeteer and a Chromium download into every `npm ci`, for every developer, to render diagrams that only CI renders. That trade is worth making deliberately, not as a side effect of a bug fix. +- **Status:** `DONE` (2026-08-08) diff --git a/reference/core/control-center/gaps/gap-tracking.es.md b/reference/core/control-center/gaps/gap-tracking.es.md index ba7f0c4c..b2e77ce2 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -670,9 +670,10 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-655`](./gap-reference-catalog.es.md#gt-655) | **Cuatro operaciones declaradas en las tres superficies no las ha invocado nunca ninguna prueba.** `satellite-create`, `pattern-list`, `pattern-get` y `pattern-list-by-topology` están `exposed: true` en CLI, MCP y REST, y el arnés de exploración no tiene binding para ninguna — 48 de 73 operaciones lo llevan. **Qué significa:** la matriz de paridad afirma que existen en tres superficies y nada les ha pedido nunca demostrarlo; el arnés las reporta en `uncoveredTriangleOps` en vez de redondearlas, pero reportar no es cubrir. **`satellite-create` es la difícil:** aprovisiona un repo real de GitHub y escribe el registro, así que su binding exige un camino deshacible o de dry-run. | `Evolith Core` | Cross | P2 | M | `COMPLETADO` | | [`GT-656`](./gap-reference-catalog.es.md#gt-656) | **Un guard contra colisiones de id volvió inmutables los títulos de los gaps, así que un tablero cuyo propósito es no mentir acumuló filas cuya primera línea miente.** `49-validate-gap-id-allocation` distingue una colisión de una edición normal comparando el `**Title:**` del catálogo contra la rama base, y cualquier diferencia falla. Eso es correcto para una colisión e incorrecto para un retítulo, y el guard no podía distinguirlos — su propio mensaje lo decía y pedía la distinción "en el commit", que nada lee. **El coste ya estaba pagado, no es hipotético:** [`GT-622`](./gap-reference-catalog.es.md#gt-622) se re-midió dos veces —82 → 201 → 210 análisis, y la rama afectada resultó ser `develop`, no `main`— mientras su titular seguía diciendo "Ochenta y dos ... cada PR", porque corregirlo habría puesto en rojo un check REQUERIDO en el propio PR que traía la corrección. La re-medición del 2026-08-01 sentó el precedente arreglando la evidencia y dejando el título, y este cierre lo encontró a punto de repetirse por tercera vez. **Arreglado convirtiendo en dato el juicio humano que el guard delegaba, no ablandando el check:** un retítulo se declara en `gap-retitles.json` reproduciendo AMBOS títulos exactamente, y solo una coincidencia exacta exime. La exactitud es el diseño — no se puede escribir como un "este id puede retitularse" en bloque, así que una colisión real que caiga después sobre el mismo número sigue fallando. Las declaraciones se clasifican `active` / `spent` / `rot`, con rot fatal y un registro ilegible fatal, porque una lista de exenciones que se pudre en silencio es peor que el defecto que arregla. | `.harness` | Cross | P2 | S | `COMPLETADO` | | [`GT-657`](./gap-reference-catalog.es.md#gt-657) | **Un advisory nuevo de `js-yaml` puso `Security Audit` en rojo en todas las ramas, y la mitad no se puede arreglar desde este repositorio.** `GHSA-5p4m-2wfm-xmqj` / CVE-2026-59870 se publicó entre el 2026-08-05 y el 2026-08-08 — los PR de dependabot del día 5 salieron verdes, el PR #440 del día 8 no, y no tocaba ningún fichero de dependencias — así que es deuda de rama en `main` y `develop`, no una regresión. **La mitad que tenía arreglo:** el repositorio ya fijaba `js-yaml: 4.3.0` por un advisory ANTERIOR, y el nuevo es vulnerable hasta `4.3.0` exactamente; el pin se quedó una patch corto. Subido a `4.3.1`, que además deduplicó tres copias anidadas en una. **La mitad que no tiene ninguno:** el advisory restante llega por `@nestjs/swagger`, que fija `js-yaml` EXACTO, y todas sus versiones publicadas fijan una vulnerable — `11.4.4` → `4.1.1`, `11.4.5` → `4.3.0`, `11.4.6` → `5.2.1` — con `12.0.0` solo en alpha. **Los `overrides` de npm no lo alcanzan, medido de cuatro formas** en vez de supuesto: override de raíz, override anidado en `@nestjs/swagger`, ambos repetidos quitando los objetos anidados ajenos para comprobar si bloqueaban la cascada (no lo hacían, lo que REFUTA la generalización que registró [`GT-636`](./gap-reference-catalog.es.md#gt-636)), y por `--package-lock-only` además de un `npm install` real. El mismo árbol todas las veces. **Por qué hacía falta más que una aceptación:** dejar el job rojo es justo lo que [`GT-622`](./gap-reference-catalog.es.md#gt-622) se acababa de cerrar para evitar — un check permanentemente rojo enseña a descontar los rojos, y el siguiente advisory de verdad aterrizaría en un job que nadie lee. `63-validate-npm-audit-gate` conserva el mismo umbral HIGH y añade un requisito: un advisory sin arreglo aguas arriba debe estar NOMBRADO, con la ruta por la que llega y qué se comprobó upstream. Falla ante un advisory no declarado, ante una declaración con otro id u otra ruta y —la regla que evita el cementerio— ante una declaración cuyo advisory ha DESAPARECIDO. | `Security` | Cross | P2 | M | `COMPLETADO` | +| [`GT-658`](./gap-reference-catalog.es.md#gt-658) | **Un renderizador roto acusó a 371 diagramas inocentes, y lo que corre en CI lo decidía quien hubiera publicado último.** `01-validate-docs --render-mermaid` invocaba `npx -y @mermaid-js/mermaid-cli` **sin versión**, una vez por diagrama. El 2026-08-08 esa instalación salió INCOMPLETA en un runner —`Cannot find package 'import-meta-resolve'`, una dependencia que el paquete SÍ declara, así que el árbol quedó a medio escribir y no es que la publicación fuera mala— y entonces falló cada diagrama con `mermaid render failed` contra documentos cuyo diagrama estaba perfecto. El job ardió **23m21s** para llegar a cientos de acusaciones equivocadas, en una promoción develop→main. **Dos defectos, una sola invocación.** El pin: `npx -y` sin versión resuelve `@latest` en el instante de correr, así que lo que se ejecuta lo elige quien publicó más recientemente y no lo elige nada de este repositorio — y la herramienta no está en `package-lock.json` **en absoluto**, así que no la instala `npm ci`, no la ve `npm audit`, no la sube Dependabot y el gate de auditoría de [`GT-657`](./gap-reference-catalog.es.md#gt-657) no puede razonar sobre ella. La culpa: el corpus de diagramas y el renderizador son dos cosas distintas y solo UNA puede romperla un commit, pero el guard preguntaba por 371 diagramas antes de preguntar si el renderizador funcionaba. **Arreglado preguntando primero lo correcto:** un preflight renderiza un diagrama trivial, y un fallo ahí es UN error honesto que nombra al renderizador y dice que ningún diagrama está implicado, en vez de cientos engañosos. El exit 0 no se acepta como prueba: un renderizador que no escribe SVG no ha renderizado. La versión queda fijada en `11.16.0`, para subirla a propósito en un commit como toda herramienta aquí. **Deliberadamente NO hecho:** meterla en el lockfile, que es el arreglo completo y arrastra Puppeteer más una descarga de Chromium a cada `npm ci`; ese coste es una decisión aparte. | `.harness` | Cross | P2 | S | `COMPLETADO` | -**Progreso:** 642 / 655 completados · 3 en progreso · 3 pendientes · 7 diferidos +**Progreso:** 643 / 656 completados · 3 en progreso · 3 pendientes · 7 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 d5ea6664..f3e2d7f9 100644 --- a/reference/core/control-center/gaps/gap-tracking.md +++ b/reference/core/control-center/gaps/gap-tracking.md @@ -670,9 +670,10 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-655`](./gap-reference-catalog.md#gt-655) | **Four operations declared on all three surfaces have never been invoked by any test.** `satellite-create`, `pattern-list`, `pattern-get` and `pattern-list-by-topology` are `exposed: true` on CLI, MCP and REST, and the exploration harness has no binding for any of them — 48 of 73 operations carry one. **What it means:** the parity matrix asserts they exist on three surfaces and nothing has ever asked them to prove it; the harness reports them in `uncoveredTriangleOps` rather than rounding them away, but reporting is not covering. **`satellite-create` is the hard one:** it provisions a live GitHub repo and writes the registry, so a binding needs an undoable or dry-run path. | `Evolith Core` | Cross | P2 | M | `DONE` | | [`GT-656`](./gap-reference-catalog.md#gt-656) | **A guard against id collisions made gap titles immutable, so a board whose purpose is not lying accumulated rows whose first line lies.** `49-validate-gap-id-allocation` discriminates a collision from an ordinary edit by comparing the catalog `**Title:**` against the base branch, and any difference fails. That is right for a collision and wrong for a retitle, and the guard could not tell them apart — its own message said so and asked for the distinction "in the commit", which nothing reads. **The cost was already paid, not hypothetical:** [`GT-622`](./gap-reference-catalog.md#gt-622) was re-measured twice — 82 → 201 → 210 analyses, and the branch it affects turned out to be `develop`, not `main` — while its headline went on saying "Eighty-two ... every PR", because correcting it would have turned a REQUIRED check red on the PR carrying the correction. The 2026-08-01 re-measure set the precedent by fixing the evidence and leaving the title, and this closure found it about to be repeated a third time. **Fixed by making the deferred human judgement into data rather than by softening the check:** a retitle is declared in `gap-retitles.json` reproducing BOTH titles exactly, and only an exact match exempts. The exactness is the design — it cannot be written as a blanket "this id may be retitled", so a genuine collision later landing on the same number still fails. Declarations are themselves classified `active` / `spent` / `rot`, with rot fatal and an unparseable registry fatal, because an exemption list that rots silently is worse than the defect it fixes. | `.harness` | Cross | P2 | S | `DONE` | | [`GT-657`](./gap-reference-catalog.md#gt-657) | **A new `js-yaml` advisory turned `Security Audit` red on every branch, and half of it cannot be fixed from this repository at all.** `GHSA-5p4m-2wfm-xmqj` / CVE-2026-59870 was published between 2026-08-05 and 2026-08-08 — the dependabot PRs of the 5th were green, PR #440 on the 8th was not, and it touched no dependency file — so it is branch debt on `main` and `develop`, not a regression. **The half that had a fix:** the repository already pinned `js-yaml: 4.3.0` for an EARLIER advisory, and the new one is vulnerable through exactly `4.3.0`; the pin was one patch short. Bumped to `4.3.1`, which also deduplicated three nested copies into one. **The half that has none:** the remaining advisory reaches the tree through `@nestjs/swagger`, which pins `js-yaml` EXACTLY, and every published release pins a vulnerable one — `11.4.4` → `4.1.1`, `11.4.5` → `4.3.0`, `11.4.6` → `5.2.1` — with `12.0.0` only in alpha. **npm `overrides` cannot reach it, measured four ways** rather than assumed: a top-level override, a scoped `@nestjs/swagger` override, both re-tested with the unrelated nested override objects removed to check whether they blocked the cascade (they did not, which REFUTES the generalisation [`GT-636`](./gap-reference-catalog.md#gt-636) recorded), and through `--package-lock-only` as well as a real `npm install`. Same tree every time. **Why this needed more than an acceptance:** leaving the job red is what [`GT-622`](./gap-reference-catalog.md#gt-622) had just been closed to stop — a permanently red check trains reviewers to discount red, and the next real advisory would arrive into a job nobody reads. `63-validate-npm-audit-gate` keeps the same HIGH threshold and adds one requirement: an advisory with no upstream fix must be NAMED, with the path it arrives by and what was checked upstream. It fails on an undeclared advisory, on a declaration for a different id or path, and — the rule that stops a graveyard — on a declaration whose advisory has DISAPPEARED. | `Security` | Cross | P2 | M | `DONE` | +| [`GT-658`](./gap-reference-catalog.md#gt-658) | **A broken renderer accused 371 innocent diagrams, and what runs in CI was decided by whoever published last.** `01-validate-docs --render-mermaid` invoked `npx -y @mermaid-js/mermaid-cli` with **no version**, once per diagram. On 2026-08-08 that install came out INCOMPLETE on a runner — `Cannot find package 'import-meta-resolve'`, a dependency the package DOES declare, so the tree was half-written rather than the publish being bad — and every diagram then failed with `mermaid render failed` against a document whose diagram was perfectly fine. The job burned **23m21s** arriving at hundreds of wrong accusations, on a develop→main promotion. **Two defects, one invocation.** The pin: `npx -y` with no version resolves `@latest` at the instant CI runs, so what executes is chosen by whoever published most recently and by nothing in this repository — and the tool is not in `package-lock.json` **at all**, so `npm ci` never installs it, `npm audit` never sees it, Dependabot never bumps it and [`GT-657`](./gap-reference-catalog.md#gt-657)'s audit gate cannot reason about it. The blame: the diagram corpus and the renderer are two different things and only ONE of them can be broken by a commit, but the guard asked about 371 diagrams before asking whether the renderer worked. **Fixed by asking the right question first:** a preflight renders one trivial diagram, and a failure there is ONE honest error naming the renderer, stating that no diagram is implicated, instead of hundreds of misleading ones. Exit 0 is not accepted as proof — a renderer that writes no SVG has not rendered. The version is pinned to `11.16.0`, bumped deliberately in a commit like every other tool here. **Deliberately NOT done:** moving it into the lockfile, which is the fuller fix and drags Puppeteer plus a Chromium download into every `npm ci`; that cost is a separate decision. | `.harness` | Cross | P2 | S | `DONE` | -**Progress:** 642 / 655 done · 3 in progress · 3 pending · 7 deferred +**Progress:** 643 / 656 done · 3 in progress · 3 pending · 7 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 56564ee5..7cadca4c 100644 --- a/reference/core/control-center/maturity-reports/executive-summary.es.md +++ b/reference/core/control-center/maturity-reports/executive-summary.es.md @@ -42,14 +42,14 @@ La forma correcta de usar este resumen es simple: si necesitas contexto, abre so | Indicador | Valor | |---|---:| | Fecha canónica del tablero | 2026-08-08 | -| Gaps totales | 655 | -| Gaps cerrados | 642 | +| Gaps totales | 656 | +| Gaps cerrados | 643 | | Gaps pendientes | 13 | | P0 abiertos | 1 | | P1 abiertos | 3 | | P2 abiertos | 6 | | Cierre total | 98% | -| Registros de evidencia de cierre | 624 | +| Registros de evidencia de cierre | 625 | | Readiness registrado | 4 PASS | | Área | Pendientes | P0 | P1 | Primeros IDs | diff --git a/reference/core/control-center/maturity-reports/executive-summary.md b/reference/core/control-center/maturity-reports/executive-summary.md index 69f92bd2..8368502f 100644 --- a/reference/core/control-center/maturity-reports/executive-summary.md +++ b/reference/core/control-center/maturity-reports/executive-summary.md @@ -42,14 +42,14 @@ Use this summary with a simple rule: if you need context, open only the linked I | Indicator | Value | |---|---:| | Canonical board date | 2026-08-08 | -| Total gaps | 655 | -| Closed gaps | 642 | +| Total gaps | 656 | +| Closed gaps | 643 | | Open gaps | 13 | | Open P0 | 1 | | Open P1 | 3 | | Open P2 | 6 | | Total closure | 98% | -| Closure evidence records | 624 | +| Closure evidence records | 625 | | Recorded readiness | 4 PASS | | Area | Open | P0 | P1 | First IDs | diff --git a/reference/core/control-center/maturity-reports/maturity-reconciliation.json b/reference/core/control-center/maturity-reports/maturity-reconciliation.json index 66759495..c9f62af0 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-08-08", "gaps": { - "total": 655, - "done": 642, + "total": 656, + "done": 643, "pending": 3, "inProgress": 3, "deferred": 7 }, "evidence": { - "closureRecords": 624, + "closureRecords": 625, "cliPackage": "@beyondnet/evolith-cli@1.2.2", "adrCount": 140, "rulesetCount": 177,