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 8adb9c76..1b473743 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -9923,3 +9923,41 @@ La declaración tiene un hueco — un pack que no declara — y el directorio lo - [x] Si la respuesta es «el servidor exige un corePath explícito», se niega en vez de anunciar herramientas que no puede servir. **NO ES LA RESPUESTA ELEGIDA, y la fila deja escrito por qué.** Se decidió autosuficiente con override: el paquete lleva corpus como suelo, `EVOLITH_CORE_PATH` y un `corePath` por llamada siempre ganan, y `describeCorpusAtStartup()` dice cuál respondió. Es lo que ya hacía el CLI, así que las dos superficies dejan de discrepar. - [x] El canario de `GT-671` retira su exención de limitación conocida en el mismo cambio. **CUMPLIDO, y mejor que retirada — ahora CADUCA SOLA.** La exención se ata a la CAUSA (¿trae corpus el paquete instalado?) y no a una versión ni a una fecha, porque el canario mide el REGISTRO y `latest` sigue siendo anterior a este arreglo. En cuanto se publique un build con corpus, la exención deja de aplicar y la aserción de gate muerde, sin que nadie tenga que acordarse. Verificado en ambos sentidos: el paquete arreglado trae corpus, el publicado no. - **Estado:** `COMPLETADO` + +--- + +#### GT-706 + +**Título:** Nada asegura que los `exports` que un paquete declara resuelvan dentro de su propio tarball, así que un productor puede publicar una subruta fantasma y solo la descubre un consumidor — una publicación demasiado tarde + +- **Propósito:** Que un paquete demuestre su propio manifiesto antes de publicarse, en vez de que se entere el siguiente paquete de la release. +- **Evidencia, medida el 2026-08-16 durante la release 1.3.x.** `@beyondnet/evolith-contracts@1.1.0`, publicado el 2026-07-18, declara una subruta de export cuyo fichero no incluye. El fallo no salió en su propia publicación. Salió en `@beyondnet/evolith-infra-providers@1.2.1`, cuyo smoke de instalación en sala limpia rechazó el paquete porque `@beyondnet/evolith-contracts/ingest` era irresoluble — **después de que `@beyondnet/evolith-core-domain@1.3.1` ya hubiera llegado al registry de forma irreversible.** La release quedó a medio entregar, y npm prohíbe despublicar pasadas 72 horas, así que la única salida fue una versión nueva de `contracts`. +- **La comprobación que existe es real, y tiene la forma equivocada.** `npm-release.yml:206-220` asegura que el tarball contiene los puntos de entrada que promete el manifiesto — y calcula «prometidos» como `[pkg.main, ...Object.values(pkg.bin)]` (`:213`). **`exports` no está en esa lista.** La aserción responde una pregunta más estrecha que la que hace el manifiesto, y un paquete cuyo mapa de `exports` apunta a ficheros que nunca empaquetó la pasa. +- **Falsabilidad demostrada, OBSERVADA en verde.** Se construyó un paquete de dos ficheros que declara `"./ingest": "./dist/ingest/index.js"` con solo `dist/index.js` en disco, y se corrió la aserción de la release **literal** desde `npm-release.yml:208-220` contra él: + + ``` + 2 file(s) packed; entry points declared: dist/index.js + packlist assertion exit=0 <-- VERDE, con un export fantasma declarado + require phantom-proof/ingest -> MODULE_NOT_FOUND + ``` + +- **Por qué el smoke de sala limpia tampoco lo cubre, y no es un defecto de ese script.** `check-install-smoke.mjs` resuelve cada especificador `@beyondnet/*` que un paquete **importa** (`:74-101`), lo cual es del lado consumidor por diseño — es lo que llegó a cazar este caso. Pero un export fantasma del productor es invisible hasta que alguien lo importa, así que la comprobación dispara **en el turno del consumidor dentro del orden de publicación**, es decir, cuando el productor y todo lo anterior ya son inmutables en el registry. Tarde e irreversible es la parte que cuesta. +- **Exposición, medida en todo el workspace:** 3 de 8 paquetes publicables declaran subrutas de export — `contracts` (5), `core-domain` (16), `agent-runtime` (2) — **23 subrutas, ninguna asegurada por la release.** Dos de los tres declaran además un comodín `./*`, que promete que *cualquier* `./dist/*.js` es importable y por construcción no tiene cota. +- **Lo que esta fila NO afirma, porque se midió y es falso.** Hoy no hay ningún export fantasma en el registry. Instalando los publicados actuales `contracts@1.2.0`, `core-domain@1.3.1` y `agent-runtime@1.2.0` en un prefijo limpio y resolviendo cada subruta declarada: **22 resuelven, 0 fantasmas.** El registry está sano; lo que falta es algo que lo mantenga así. Registrar esto como «hay exports rotos» habría sido una fila que se cierra sola por accidente en la siguiente release. +- **Casos de uso:** + - Un paquete gana una subruta de export y un cambio de `files`/build deja de incluirla en silencio; la release se niega en vez de publicar un manifiesto que miente. + - Una actualización de consumidor deja de fallar en instalación por un defecto que pertenece a un paquete publicado días antes. + - Una release que no puede completarse deja de abandonar hermanos publicados de forma irreversible por detrás. +- **Impacto:** Un manifiesto publicado es un contrato. Cuando declara más de lo que lleva el tarball, todo consumidor de esa subruta se rompe en la instalación, la versión del productor no se puede retirar pasadas 72 horas, y el diagnóstico le cae a quien toque publicar después y no a quien la entregó. +- **Resultado esperado:** que la release asegure, para cada paquete que va a publicar, que todo destino de `exports` sin comodín está presente en el packlist de ese mismo paquete — fallando antes del paso irreversible, en el turno del productor. +- **Ficheros afectados:** `.github/workflows/npm-release.yml:206-220`, `src/sdk/cli/scripts/check-install-smoke.mjs`, `src/packages/contracts/package.json`, `src/packages/core-domain/package.json`, `src/packages/agent-runtime/package.json` +- **Componente:** `Infra` · **Criticidad:** P1 · **Complejidad:** S +- **Principal:** `S` · **Interés:** `HIGH` · **Base:** `estimate` +- **Procedencia:** Registrada el 2026-08-16 desde la release 1.3.x, donde la clase costó dos intentos de publicación fallidos y una release parcial irreversible. Hermana de [`GT-625`](./gap-reference-catalog.es.md#gt-625) y [`GT-671`](./gap-reference-catalog.es.md#gt-671): la misma asimetría árbol-contra-tarball, una capa antes — esas dos preguntan si el artefacto PUBLICADO funciona, esta pregunta si debió publicarse siquiera. +- **Criterios de aceptación:** + - [ ] La release resuelve cada destino de `exports` sin comodín contra el packlist del propio paquete, y tumba la publicación cuando falta uno. + - [ ] La aserción corre ANTES del paso irreversible, en el turno del productor — no en la instalación de un consumidor. + - [ ] El comodín `./*` se trata de forma explícita en vez de saltarse: o la fila registra por qué una promesa sin cota es aceptable, o el comodín se estrecha a lo que realmente se incluye. + - [ ] Falsabilidad demostrada: un paquete que declara un export que no empaqueta pone la comprobación en rojo, OBSERVADO, y la misma comprobación sigue verde sobre los tres paquetes reales cuyas 23 subrutas miden sanas hoy. + - [ ] La comprobación nombra el destino que falta y el paquete, para que el fallo sea accionable sin abrir el tarball. +- **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 339493d9..ce417dda 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -10018,3 +10018,41 @@ The declaration has one hole — a pack that does not declare — and the direct - [x] If the answer is "the server requires an explicit corePath", it refuses rather than announcing tools it cannot serve. **NOT THE ANSWER CHOSEN, and the row records why.** Self-sufficient with override was the decision: the package bundles a corpus as its floor, `EVOLITH_CORE_PATH` and a per-call `corePath` always win, and `describeCorpusAtStartup()` names which one answered. That matches what the CLI already does, so the two surfaces stop disagreeing. - [x] `GT-671`'s canary drops its known-limitation exemption in the same change. **MET, and better than dropped — it now EXPIRES BY ITSELF.** The exemption is keyed on the CAUSE (does the installed package carry a corpus?) rather than on a version or a date, because the canary measures the REGISTRY and `latest` still predates this fix. The moment a published build ships a corpus the exemption stops matching and the gate assertion bites, with nobody having to remember. Verified both ways: the fixed package ships a corpus, the published one does not. - **Status:** `DONE` + +--- + +#### GT-706 + +**Title:** Nothing asserts that a package's own declared `exports` resolve inside its own tarball, so a producer can publish a phantom subpath and only a consumer discovers it — one publish too late + +- **Purpose:** Make a package prove its own manifest before it is published, instead of letting the next package in the release find out. +- **Evidence, measured 2026-08-16 during the 1.3.x release.** `@beyondnet/evolith-contracts@1.1.0`, published 2026-07-18, declares an export subpath whose file it does not ship. The failure did not surface at its own publish. It surfaced at `@beyondnet/evolith-infra-providers@1.2.1`, whose clean-room install smoke refused the package because `@beyondnet/evolith-contracts/ingest` was unresolvable — **after `@beyondnet/evolith-core-domain@1.3.1` had already gone to the registry irreversibly.** The release stopped half-shipped, and npm forbids unpublishing after 72 hours, so the only recovery was a new `contracts` version. +- **The check that exists is real, and it is the wrong shape.** `npm-release.yml:206-220` asserts the tarball contains the entry points the manifest promises — and computes "promised" as `[pkg.main, ...Object.values(pkg.bin)]` (`:213`). **`exports` is not in that list.** So the assertion answers a narrower question than the one the manifest asks, and a package whose `exports` map points at files it never packed passes it. +- **Proven falsifiable, OBSERVED green.** A two-file package was built declaring `"./ingest": "./dist/ingest/index.js"` with only `dist/index.js` on disk, and the release's assertion was run **verbatim** from `npm-release.yml:208-220` against it: + + ``` + 2 file(s) packed; entry points declared: dist/index.js + packlist assertion exit=0 <-- GREEN, with a phantom export declared + require phantom-proof/ingest -> MODULE_NOT_FOUND + ``` + +- **Why the clean-room smoke does not cover it either, and it is not a defect of that script.** `check-install-smoke.mjs` resolves every `@beyondnet/*` specifier a package **imports** (`:74-101`), which is consumer-side by design — it is what caught this one at all. But a producer's phantom export is invisible until somebody imports it, so the check fires **at the consumer's turn in the publish order**, which is after the producer and everything before it are already immutable on the registry. Late and irreversible is the part that costs. +- **Exposure, measured across the workspace:** 3 of 8 publishable packages declare export subpaths — `contracts` (5), `core-domain` (16), `agent-runtime` (2) — **23 subpaths, none of them asserted by the release.** Two of the three also declare a `./*` wildcard, which promises that *any* `./dist/*.js` is importable and is therefore unbounded by construction. +- **What this row does NOT claim, because it was measured and is false.** There is no phantom export on the registry today. Installing the current published `contracts@1.2.0`, `core-domain@1.3.1` and `agent-runtime@1.2.0` into a clean prefix and resolving every declared subpath gives **22 resolve, 0 phantom**. The registry is healthy; what is missing is anything that keeps it that way. Registering this as "there are broken exports" would have been a row that closes itself by accident on the next release. +- **Use cases:** + - A package gains an export subpath and a `files`/build change silently stops shipping it; the release refuses instead of publishing a manifest that lies. + - A consumer upgrade stops failing at install time for a defect that belongs to a package published days earlier. + - A release that cannot complete stops leaving irreversibly-published siblings behind it. +- **Impact:** A published manifest is a contract. When it declares more than the tarball carries, every consumer of that subpath breaks at install, the producer's version cannot be withdrawn after 72 hours, and the diagnosis lands on whoever happens to be publishing next rather than on whoever shipped it. +- **Expected outcome:** the release asserts, for each package it is about to publish, that every non-wildcard `exports` target is present in that package's own packlist — failing before the irreversible step, in the producer's own turn. +- **Affected files:** `.github/workflows/npm-release.yml:206-220`, `src/sdk/cli/scripts/check-install-smoke.mjs`, `src/packages/contracts/package.json`, `src/packages/core-domain/package.json`, `src/packages/agent-runtime/package.json` +- **Component:** `Infra` · **Criticality:** P1 · **Complexity:** S +- **Principal:** `S` · **Interest:** `HIGH` · **Basis:** `estimate` +- **Provenance:** Registered 2026-08-16 from the 1.3.x release, where the class cost two failed publish attempts and one irreversible partial release. Sibling of [`GT-625`](./gap-reference-catalog.md#gt-625) and [`GT-671`](./gap-reference-catalog.md#gt-671): the same tree-versus-tarball asymmetry, one layer earlier — those two ask whether the PUBLISHED artifact works, this one asks whether the artifact should have been published at all. +- **Acceptance criteria:** + - [ ] The release resolves every non-wildcard `exports` target of a package against that package's own packlist, and fails the publish when one is absent. + - [ ] The assertion runs BEFORE the irreversible step, in the producer's own turn — not at a consumer's install. + - [ ] The `./*` wildcard is handled explicitly rather than skipped: either the row records why an unbounded promise is acceptable, or the wildcard is narrowed to what is actually shipped. + - [ ] Proven falsifiable: a package declaring an export it does not pack turns the check red, OBSERVED, and the same check stays green on the three real packages whose 23 subpaths measure healthy today. + - [ ] The check names the missing target and the package, so the failure is actionable without opening the tarball. +- **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 199c9d16..c3e2f8c3 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -722,9 +722,10 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-668`](./gap-reference-catalog.es.md#gt-668) | **La prueba de `GT-666` de que a su guarda se la había visto fallar alguna vez estaba anclada a una REFERENCIA MÓVIL, así que dejó de ser evidencia justo en el momento en que el arreglo aterrizó.** El único caso que lee el artefacto real previo al arreglo lo obtenía con `git show ${PRE_FIX_REF}:iso-5055-mapping.json`, con `PRE_FIX_REF` por defecto a `origin/develop`. Eso solo es cierto mientras el arreglo vive en una rama: **`59d62bae` se mergeó, `origin/develop` empezó a servir el artefacto CORREGIDO, y el caso que afirma 64 hallazgos encontró 0** — verde en su propia rama, rojo justo cuando importaba, bloqueando el PR de promoción `develop` → `main` **#483** en `Governance guards (GT-578)`. **Reproducido antes de actuar**, no tomado del traspaso: `node --test` sobre `develop` en `59d62bae` falla ese caso con `0 !== 64` mientras los otros 18 pasan. **La mitad peor es la que nadie habría visto:** el caso llevaba `if (before.status !== 0) return void assert.ok(true, 'SKIPPED: …')` para clones superficiales, así que en un checkout con historial truncado la misma podredumbre habría **pasado en silencio** en vez de fallar — una vía de escape de la única prueba de que la guarda estuvo roja alguna vez. **ENTREGADO 2026-08-09.** El razonamiento del comentario era correcto y se conserva: *«leído de git en vez de reconstruido … Los fixtures reconstruidos coinciden con lo que el autor creía que estaba mal; este no puede.»* Eso argumenta a favor de un artefacto REAL previo al arreglo, no de leer una rama en tiempo de test — así que el artefacto queda **congelado en el repositorio**: `.harness/fixtures/standards-rule-class/iso-5055-mapping.pre-gt-666.json`, tomado de `01308346` (`59d62bae^`, blob `6684e8a4`), **sin recortar**, y verificado byte a byte idéntico a ese blob una vez se quita su única clave añadida `_fixture` de procedencia. El `git show`, la variable de entorno `PRE_FIX_REF` y el salto por clon superficial quedan **eliminados** — un salto solo puede ocultar un fallo. **Afirmaciones sin cambios y sin debilitar: 64 hallazgos, 16 de ellos `is classified `governance``**, medidos contra el fixture congelado y los packs de hoy. Se AÑADE un caso en vez de relajar ninguno: un fixture «refrescado» desde el mapeo vivo es ROJO, y eso se **observó** — se simuló el refresco y pone en rojo 2 de 20 casos, así que el fixture negativo no puede convertirse en silencio en una copia del artefacto que existe para atrapar. Metaguardas reejecutadas: `42` 78/78 clasificadas, `43` 54/54 vistas fallar. | — | — | `Evolith Core` | Cross | P1 | XS | `COMPLETADO` | | [`GT-704`](./gap-reference-catalog.es.md#gt-704) | **Ningún job de CI corre LOS DOS motores sobre el corpus, así que una divergencia de veredicto entre ellos la descubre una persona o no la descubre nadie.** Separado de `GT-675`, cuyo AC7 pedía ese barrido y lo costeaba en ≈17 s. Lo que `GT-675` entregó es más estrecho y a propósito: un guard que ejecuta el bundle compilado y falla si no sabe declarar su propio alcance — la invariante cuya pérdida silenciosa des-arreglaría 234 reglas. No compara los dos motores. Medido el 2026-08-16 con ambos sobre el corpus completo: **16 ids reciben VEREDICTOS opuestos** (`passed` en uno, `failed` en el otro) — `TAX-01`, `TAX-05`, `INH-02`, `SVC-03`, `ACL-01` entre ellos —, que es la clase que a `ADR-0041` sí le importa, frente a las 82 diferencias de cobertura (65 decide-OPA/salta-nativo, 17 al revés) que son legítimas. También medido: `cli/exit-code-taxonomy`, que `GT-675` llamaba el pack de control donde ambos coinciden, NO coincide — nativo `passed / exit 0` contra OPA `failed / exit 2`. | Nadie corre los dos motores en paralelo, así que pueden separarse sin que nadie lo note. | Una discrepancia entre motores la caza CI el día que aparece. | `Core Domain` | Cross | P2 | M | `PENDIENTE` | | [`GT-705`](./gap-reference-catalog.es.md#gt-705) | **El servidor MCP publicado no traía corpus de rulesets Y adivinaba dónde estaba Core, así que 48 de sus 50 herramientas no podían gobernar nada desde una instalación limpia.** Encontrado por el canario de `GT-671`. **ARREGLADO el 2026-08-16 — dos causas independientes, y arreglar solo una no cambiaba nada.** (1) `files: ["dist/"…]` no llevaba corpus y ninguna dependencia lo aportaba; el paquete empaqueta ahora **los dos** árboles que el servidor necesita — corpus de rulesets y definiciones de gate SDLC. Que hacían falta ambos se OBSERVÓ, no se predijo: con solo el corpus, `evolith-validate` funcionaba y `evolith-gate-evaluate` seguía sin hacerlo. (2) `path.join(process.cwd(), '..', 'evolith')` — un directorio hermano con el nombre de este monorepo — en **9 sitios de 5 ficheros de mcp-server y 4 servicios de core-domain**, la capa que comparten las tres superficies. Ahora hay un solo resolutor: llamante → `EVOLITH_CORE_PATH` → subir desde el satélite → corpus empaquetado; `process.cwd()` no aparece. La búsqueda cualifica **por contenido**, lo que además cierra `GT-566` en esas cuatro copias — buscaban un directorio LLAMADO `rulesets` y este repo tiene un `rulesets/agents` que comparte nombre y no tiene reglas. DE PUNTA A PUNTA desde una instalación npm limpia y sin repositorio en disco: `evolith-validate` `INTERNAL_ERROR` → **veredicto `failed`**; `evolith-gate-evaluate` `RULESET_NOT_FOUND` → **veredicto `failed`, gate `business-sign-off`**. Tres specs aseguraban el contrato viejo y se reescribieron — uno se llamaba *«falls back to the sibling ../evolith convention»*. | El servidor MCP que instalas de npm anunciaba 50 herramientas y solo respondía las que no necesitan reglas. | Un agente conectado al servidor publicado puede gobernar de verdad. | `MCP Server` | Cross | P1 | M | `COMPLETADO` | +| [`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. **Lo que esta fila NO afirma, medido:** hoy no hay ningún fantasma en el registry — los tres paquetes publicados resuelven **22 de 22**. El registry está sano; nada lo mantiene así. | 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 | `PENDIENTE` | -**Progreso:** 672 / 703 completados · 2 en progreso · 2 pendientes · 27 diferidos +**Progreso:** 672 / 704 completados · 2 en progreso · 3 pendientes · 27 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 08dde4fd..d86f6f26 100644 --- a/reference/core/control-center/gaps/gap-tracking.md +++ b/reference/core/control-center/gaps/gap-tracking.md @@ -722,9 +722,10 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-668`](./gap-reference-catalog.md#gt-668) | **`GT-666`'s proof that its guard had ever been observed failing was anchored to a MOVING REF, so it stopped being evidence at the exact moment the fix landed.** The one case that reads the real pre-fix artifact obtained it with `git show ${PRE_FIX_REF}:iso-5055-mapping.json`, `PRE_FIX_REF` defaulting to `origin/develop`. That is true only while the fix lives on a branch: **`59d62bae` merged, `origin/develop` began serving the CORRECTED artifact, and the case asserting 64 findings found 0** — green on its own branch, red the moment it mattered, blocking the `develop` → `main` promotion PR **#483** in `Governance guards (GT-578)`. **Reproduced before acting**, not taken from the handover: `node --test` on `develop` at `59d62bae` fails that one case with `0 !== 64` while the other 18 pass. **The worse half is the one nobody would have seen:** the case carried `if (before.status !== 0) return void assert.ok(true, 'SKIPPED: …')` for shallow clones, so in a checkout with a truncated history the same rot would have **passed in silence** rather than failing — an escape hatch out of the only case that proves the guard was ever red. **DELIVERED 2026-08-09.** The comment's reasoning was right and is kept: *«read out of git rather than reconstructed … Reconstructed fixtures agree with whatever the author believed was wrong; this one cannot.»* That argues for a REAL pre-fix artifact, not for reading a branch at test time — so the artifact is **frozen in the repository**: `.harness/fixtures/standards-rule-class/iso-5055-mapping.pre-gt-666.json`, taken from `01308346` (`59d62bae^`, blob `6684e8a4`), **not trimmed**, and verified byte-identical to that blob once its one added `_fixture` provenance key is dropped. The `git show`, the `PRE_FIX_REF` env var and the shallow-clone skip are **deleted** — a skip can only ever hide a failure. **Assertions unchanged and unweakened: 64 findings, 16 of them `is classified `governance``**, measured against the frozen fixture and today's packs. One case is ADDED rather than any relaxed: a fixture «refreshed» from the live mapping is RED, and that was **observed** — the refresh was simulated and turns 2 of 20 cases red, so the negative fixture cannot silently become a copy of the artifact it exists to catch. Meta-guards re-run: `42` 78/78 classified, `43` 54/54 observed failing. | — | — | `Evolith Core` | Cross | P1 | XS | `DONE` | | [`GT-704`](./gap-reference-catalog.md#gt-704) | **No CI job runs BOTH engines over the corpus, so a verdict divergence between them is discovered by a human or not at all.** Split out of `GT-675`, whose AC7 asked for the sweep and costed it at ≈17 s. What `GT-675` shipped is narrower and deliberately so: a guard that executes the compiled bundle and fails if it cannot state its own scope — the invariant whose quiet loss would silently un-fix 234 rules. It does not compare the two engines. Measured 2026-08-16 with both engines on the whole corpus: **16 rule ids get opposite VERDICTS** (`passed` on one, `failed` on the other) — `TAX-01`, `TAX-05`, `INH-02`, `SVC-03`, `ACL-01` among them — which is the class `ADR-0041` does care about, as opposed to the 82 coverage differences (65 OPA-decides/native-skips, 17 the reverse) that are legitimate. Also measured: `cli/exit-code-taxonomy`, which `GT-675` called the control pack where both engines agree, does NOT agree — native `passed / exit 0` against OPA `failed / exit 2`. | Nobody runs the two engines side by side, so they can drift apart unnoticed. | A disagreement between the engines is caught by CI the day it appears. | `Core Domain` | Cross | P2 | M | `PENDING` | | [`GT-705`](./gap-reference-catalog.md#gt-705) | **The published MCP server shipped no ruleset corpus AND guessed where Core was, so 48 of its 50 tools could not govern anything from a clean install.** Found by `GT-671`'s canary. **FIXED 2026-08-16 — two independent causes, and fixing either alone changed nothing.** (1) `files: ["dist/"…]` carried no corpus and no dependency supplied one; the package now bundles **both** trees the server needs — the ruleset corpus and the SDLC gate definitions. That both were required was OBSERVED, not predicted: with only the corpus, `evolith-validate` worked and `evolith-gate-evaluate` still did not. (2) `path.join(process.cwd(), '..', 'evolith')` — a sibling directory named after this monorepo — in **9 places across 5 files of mcp-server and 4 services of core-domain**, the layer all three surfaces share. One resolver now: caller → `EVOLITH_CORE_PATH` → walk up from the satellite → bundled corpus; `process.cwd()` is absent. The walk qualifies **by content**, which also closes `GT-566` in those four copies — they probed for a directory NAMED `rulesets` and this repo has a `rulesets/agents` that shares the name and holds no rules. END TO END from a clean npm install with no repository on disk: `evolith-validate` `INTERNAL_ERROR` → **verdict `failed`**; `evolith-gate-evaluate` `RULESET_NOT_FOUND` → **verdict `failed`, gate `business-sign-off`**. Three specs asserted the old contract and were rewritten — one was named *"falls back to the sibling ../evolith convention"*. | The MCP server you install from npm announced 50 tools and could only answer the ones needing no rules. | An agent connecting to the published server can actually govern something. | `MCP Server` | Cross | P1 | M | `DONE` | +| [`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 `./*`. **What this row does NOT claim, measured:** there is no phantom on the registry today — the three published packages resolve **22 of 22**. The registry is healthy; nothing keeps it that way. | 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 | `PENDING` | -**Progress:** 672 / 703 done · 2 in progress · 2 pending · 27 deferred +**Progress:** 672 / 704 done · 2 in progress · 3 pending · 27 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 734d432e..512213f6 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-706](../gaps/gap-reference-catalog.es.md#gt-706) | +| 4 | Ola P1 | Endurecimiento siguiente después de limpiar P0. | [GT-684](../gaps/gap-reference-catalog.es.md#gt-684), [GT-706](../gaps/gap-reference-catalog.es.md#gt-706), [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), +12 | ## Bloqueadores Actuales @@ -42,13 +42,13 @@ 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 | 703 | +| Gaps totales | 704 | | Gaps cerrados | 672 | -| Gaps pendientes | 31 | +| Gaps pendientes | 32 | | P0 abiertos | 1 | -| P1 abiertos | 8 | +| P1 abiertos | 9 | | P2 abiertos | 18 | -| Cierre total | 95.6% | +| Cierre total | 95.5% | | Registros de evidencia de cierre | 654 | | Readiness registrado | 4 PASS | @@ -56,8 +56,8 @@ La forma correcta de usar este resumen es simple: si necesitas contexto, abre so |---|---:|---:|---:|---| | `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` | 6 | 0 | 2 | [GT-706](../gaps/gap-reference-catalog.es.md#gt-706), [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), +2 | | `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` | 5 | 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-691](../gaps/gap-reference-catalog.es.md#gt-691), +1 | | `Core Domain` | 4 | 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), [GT-704](../gaps/gap-reference-catalog.es.md#gt-704) | ## 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 0d7cf194..c56ab276 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-706](../gaps/gap-reference-catalog.md#gt-706) | +| 4 | P1 wave | Next hardening after P0 is cleared. | [GT-684](../gaps/gap-reference-catalog.md#gt-684), [GT-706](../gaps/gap-reference-catalog.md#gt-706), [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), +12 | ## Current Blockers @@ -42,13 +42,13 @@ 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 | 703 | +| Total gaps | 704 | | Closed gaps | 672 | -| Open gaps | 31 | +| Open gaps | 32 | | Open P0 | 1 | -| Open P1 | 8 | +| Open P1 | 9 | | Open P2 | 18 | -| Total closure | 95.6% | +| Total closure | 95.5% | | Closure evidence records | 654 | | Recorded readiness | 4 PASS | @@ -56,8 +56,8 @@ Use this summary with a simple rule: if you need context, open only the linked I |---|---:|---:|---:|---| | `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` | 6 | 0 | 2 | [GT-706](../gaps/gap-reference-catalog.md#gt-706), [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), +2 | | `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` | 5 | 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-691](../gaps/gap-reference-catalog.md#gt-691), +1 | | `Core Domain` | 4 | 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), [GT-704](../gaps/gap-reference-catalog.md#gt-704) | ## Source and Refresh Rule diff --git a/reference/core/control-center/maturity-reports/maturity-reconciliation.json b/reference/core/control-center/maturity-reports/maturity-reconciliation.json index b9f19e16..53925296 100644 --- a/reference/core/control-center/maturity-reports/maturity-reconciliation.json +++ b/reference/core/control-center/maturity-reports/maturity-reconciliation.json @@ -3,9 +3,9 @@ "scope": "evolith-core", "asOf": "2026-08-08", "gaps": { - "total": 703, + "total": 704, "done": 672, - "pending": 2, + "pending": 3, "inProgress": 2, "deferred": 27 },