Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions reference/core/control-center/gaps/gap-reference-catalog.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
38 changes: 38 additions & 0 deletions reference/core/control-center/gaps/gap-reference-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Loading
Loading