From 2d41c2b223ba6ace1c0c196510b61ccce23e2f8a Mon Sep 17 00:00:00 2001 From: aarroyo Date: Thu, 30 Jul 2026 11:05:50 -0500 Subject: [PATCH] docs(gaps): renumber GT-633 -> GT-640, the collision the new guard found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `49-validate-gap-id-allocation` reported on its first real run that `GT-633` names two different gaps: on develop it is "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29), on main it is the tautological-guard row (`c3221276`, 2026-07-30). Per the guard's own advice the NEWER row moves, and that is mine. THIS COMMIT IS THE COST GT-638 DESCRIBES, paid once so it is on the record. The id had to be changed in: - the board row and its anchor, EN and ES - the catalog heading, EN and ES - the closure-evidence record - cross-references from GT-638 and GT-639, EN and ES, including the narrative that describes this very collision — updated to say how it was resolved rather than left describing a state that no longer exists - three source comments (`capture-native-evaluability-snapshot.mjs`, `test/rule-corpus-triage.ts`, `rule-corpus-triage.spec.ts`) and it CANNOT be changed in the commit messages and PR bodies that already name GT-633 — #294 through #300 among them. That is the lossy half, and it is why the row asks for allocation to be right the first time rather than repairable. Historical mentions are kept and marked ("registered as GT-633 at the time") rather than rewritten, so the record still reads correctly against those commits. VERIFIED THE COLLISION IS GONE, not assumed: `49-validate-gap-id-allocation --base origin/develop` now reports 0 collisions and lists GT-640 among the newly allocated ids, where before it named GT-633 on both sides. Also 08-validate- tracking, 01-validate-docs (1491 files), 04 bilingual parity, core-domain rule-corpus-triage 22/22, iso-5055-mapping guard 9/9, 46 chain at a fixed point. NOT DONE HERE: develop carries the same three source comments, and after this lands they will name GT-640 on main and GT-633 on develop — where GT-633 is the scaffolding gap. Whoever resolves the develop -> main merge should take main's version of those files; noted on #284. Co-Authored-By: Claude Opus 5 --- .../core/control-center/evidence/gap-closure-evidence.json | 4 ++-- .../core/control-center/gaps/gap-reference-catalog.es.md | 6 +++--- reference/core/control-center/gaps/gap-reference-catalog.md | 6 +++--- reference/core/control-center/gaps/gap-tracking.es.md | 6 +++--- reference/core/control-center/gaps/gap-tracking.md | 6 +++--- .../src/application/validators/rule-corpus-triage.spec.ts | 6 +++--- src/packages/core-domain/test/rule-corpus-triage.ts | 6 +++--- .../standards/capture-native-evaluability-snapshot.mjs | 4 ++-- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/reference/core/control-center/evidence/gap-closure-evidence.json b/reference/core/control-center/evidence/gap-closure-evidence.json index 2638ccfa..a0d7e0a8 100644 --- a/reference/core/control-center/evidence/gap-closure-evidence.json +++ b/reference/core/control-center/evidence/gap-closure-evidence.json @@ -8855,7 +8855,7 @@ "dependencyRationale": "What this gate does NOT do is stated rather than left to be discovered. It detects a security-marked commit that no published version contains; it cannot detect a security fix nobody marked, which is a convention problem and belongs to GT-623 (release-please derives no bump from the non-conventional `security(...)` type this repository has used twice). It also reports, but does not fail on, a package whose published version is absent from this history -- an INEXACT boundary after a history rewrite -- because scanning the whole history conservatively is better than skipping the package silently. Finally, the gate needs the network: without a registry answer the package is skipped and named, and a run where EVERY package is skipped is a failure rather than a pass." }, { - "id": "GT-633", + "id": "GT-640", "closedAt": "2026-07-30", "closureCommit": "29e940d4", "evidence": [ @@ -8931,7 +8931,7 @@ ], "validationCommands": [ "node .harness/scripts/ci/49-validate-gap-id-allocation.mjs --verbose -> 515 ids on base, 515 on HEAD, 0 newly allocated, 0 collisions against origin/main.", - "IT FOUND A SECOND COLLISION ON ITS FIRST REAL RUN, which nobody knew about. Against origin/develop it reports GT-633: on develop that id is `evolith init` scaffolds a repository that cannot pass its own governance on the first run (c8270e48, 2026-07-29); on main it is the tautological-guard row (c3221276, 2026-07-30). Two gaps, one number, and the merge had not surfaced it yet.", + "IT FOUND A SECOND COLLISION ON ITS FIRST REAL RUN, which nobody knew about. Against origin/develop it reports GT-633: on develop that id is `evolith init` scaffolds a repository that cannot pass its own governance on the first run (c8270e48, 2026-07-29); on main it is the tautological-guard row (c3221276, 2026-07-30). Two gaps, one number, and the merge had not surfaced it yet. RESOLVED the same day by renumbering the newer row -- main's -- to GT-640, per the guard's own advice.", "THE DISCRIMINATOR IS THE TITLE, deliberately. `the id already exists on base` is the normal case for 500-odd rows and says nothing; what makes it a defect is the id naming a DIFFERENT gap on each side. Evidence, status and criteria are all expected to change on a branch.", "It refuses to guess which side is wrong -- a retitle and a collision are indistinguishable from outside -- so it names both titles and both refs, and says in the failure that it should not guess.", "node --test .harness/scripts/ci/49-validate-gap-id-allocation.test.mjs -> 13/13, including the GT-634 collision reproduced over real git repositories rather than a mock of `git show`.", 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 7eafcf44..79b17b10 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -7860,7 +7860,7 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre **Title:** Nada hace visible el trabajo en vuelo entre ramas, así que el mismo gap se arregla dos veces - **Purpose:** Que un gap que ya se está trabajando sea descubrible ANTES de que una segunda sesión lo empiece, y no en el merge. -- **Evidence:** **El 2026-07-30 el mismo trabajo se hizo dos veces, en tres ocasiones distintas, por sesiones que no podían verse.** (1) [`GT-633`](./gap-reference-catalog.es.md#gt-633) se arregló dos veces: un script de captura aterrizó en `main` como #276 y un renderer independiente dentro del spec aterrizó en `develop` como #279. Los dos eran correctos, los dos recapturaron los mismos números — y dos generadores para un artefacto es el defecto del propio GT-633 un nivel más arriba, así que una TERCERA sesión gastó una reconciliación entera (#294, #295, #296, #297) en dejar un solo renderer. (2) El parser del guard de evidencias se arregló dos veces: `d1ea72a3` en `main` ("stop the evidence guard blaming the evidence for two parser defects") y `5acb29ed` en `develop` ("the ratchet was stuck by two bugs in itself"). (3) Un id de GT se asignó dos veces, forzando la renumeración que registra [`GT-638`](./gap-reference-catalog.es.md#gt-638). **El coste no es el conflicto de merge.** Es el trabajo duplicado que ocurrió antes de que nadie llegara al conflicto, más la reconciliación que hizo falta después — y esa reconciliación introdujo un defecto propio (una comparación sensible al orden que reescribía la fecha de captura en cada corrida, invisible durante un día porque ambas estampaban la misma fecha). Tres duplicaciones, una sesión de reparación y un bug nuevo: ésa es la factura real. **Por qué el tablero no lo ve hoy:** una fila lleva estado (`PENDIENTE`, `EN-PROGRESO`) pero no QUIÉN la trabaja ni DÓNDE. Dos sesiones pueden leer ambas `GT-633 · PENDIENTE` y empezar ambas, correctamente. `08-validate-tracking` valida dentro de un único árbol de trabajo por construcción, así que no puede saber que existe otra rama. **Esto no es una convención que falte — la convención existe.** La regla de un solo driver para las olas de gaps es práctica establecida; lo que falta es cualquier mecanismo que la haga observable, así que en un día cargado se degrada en silencio y nadie se entera hasta el merge. Direcciones de arreglo: exigir una reclamación (rama o PR) en la fila que pasa a `EN-PROGRESO`, y derivar el conjunto de reclamaciones de los PR abiertos para que no pueda quedarse rancio; después, fallar en PR cuando un id `GT-*` esté reclamado por más de una rama abierta, nombrando a las dos. **CERRADO el 2026-07-30 — y en su primera corrida real encontró una SEGUNDA colisión que nadie conocía.** `49-validate-gap-id-allocation` compara el **Title:** de cada id en HEAD contra el título que ese mismo id lleva en la rama base: ausente en base es id nuevo, presente con el mismo título es el mismo gap editado, presente con título DISTINTO es un número nombrando dos gaps. El título es el discriminador a propósito — "el id ya existe en base" es el caso normal de 500 y pico filas y no dice nada. Corrido contra `origin/develop` reporta **`GT-633`**: en develop es "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29) y en main es la fila del guard tautológico (`c3221276`, 2026-07-30). Dos gaps, un número, y el merge todavía no lo había sacado a la luz. **El guard se niega a adivinar qué lado está mal** — un retítulo y una colisión se ven igual desde fuera —, así que nombra los dos y lo dice. Cableado en `Governance guards (GT-578)` con un `git fetch` explícito de la base, porque un checkout superficial de CI no la tiene y un guard que fallara por ESO enseñaría a ignorarlo. Observado en rojo por `43-validate-guard-negative-fixtures` (38/38). **Lo que sigue sin ver, dicho y no insinuado:** un número asignado en una rama de la que nadie ha abierto PR. La salida `--verbose` lo advierte en cada id recién asignado en vez de dejar que el lector suponga una cobertura que no tiene. +- **Evidence:** **El 2026-07-30 el mismo trabajo se hizo dos veces, en tres ocasiones distintas, por sesiones que no podían verse.** (1) [`GT-640`](./gap-reference-catalog.es.md#gt-640) — registrado como GT-633 hasta la renumeración de abajo — se arregló dos veces: un script de captura aterrizó en `main` como #276 y un renderer independiente dentro del spec aterrizó en `develop` como #279. Los dos eran correctos, los dos recapturaron los mismos números — y dos generadores para un artefacto es el defecto del propio GT-633 un nivel más arriba, así que una TERCERA sesión gastó una reconciliación entera (#294, #295, #296, #297) en dejar un solo renderer. (2) El parser del guard de evidencias se arregló dos veces: `d1ea72a3` en `main` ("stop the evidence guard blaming the evidence for two parser defects") y `5acb29ed` en `develop` ("the ratchet was stuck by two bugs in itself"). (3) Un id de GT se asignó dos veces, forzando la renumeración que registra [`GT-638`](./gap-reference-catalog.es.md#gt-638). **El coste no es el conflicto de merge.** Es el trabajo duplicado que ocurrió antes de que nadie llegara al conflicto, más la reconciliación que hizo falta después — y esa reconciliación introdujo un defecto propio (una comparación sensible al orden que reescribía la fecha de captura en cada corrida, invisible durante un día porque ambas estampaban la misma fecha). Tres duplicaciones, una sesión de reparación y un bug nuevo: ésa es la factura real. **Por qué el tablero no lo ve hoy:** una fila lleva estado (`PENDIENTE`, `EN-PROGRESO`) pero no QUIÉN la trabaja ni DÓNDE. Dos sesiones pueden leer ambas `GT-640 · PENDIENTE` (registrado como GT-633 entonces) y empezar ambas, correctamente. `08-validate-tracking` valida dentro de un único árbol de trabajo por construcción, así que no puede saber que existe otra rama. **Esto no es una convención que falte — la convención existe.** La regla de un solo driver para las olas de gaps es práctica establecida; lo que falta es cualquier mecanismo que la haga observable, así que en un día cargado se degrada en silencio y nadie se entera hasta el merge. Direcciones de arreglo: exigir una reclamación (rama o PR) en la fila que pasa a `EN-PROGRESO`, y derivar el conjunto de reclamaciones de los PR abiertos para que no pueda quedarse rancio; después, fallar en PR cuando un id `GT-*` esté reclamado por más de una rama abierta, nombrando a las dos. **CERRADO el 2026-07-30 — y en su primera corrida real encontró una SEGUNDA colisión que nadie conocía.** `49-validate-gap-id-allocation` compara el **Title:** de cada id en HEAD contra el título que ese mismo id lleva en la rama base: ausente en base es id nuevo, presente con el mismo título es el mismo gap editado, presente con título DISTINTO es un número nombrando dos gaps. El título es el discriminador a propósito — "el id ya existe en base" es el caso normal de 500 y pico filas y no dice nada. Corrido contra `origin/develop` reporta **`GT-633`**: en develop es "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29) y en main es la fila del guard tautológico (`c3221276`, 2026-07-30). Dos gaps, un número, y el merge todavía no lo había sacado a la luz. **RESUELTO el 2026-07-30 renumerando la fila más nueva —la de main— a [`GT-640`](./gap-reference-catalog.es.md#gt-640)**, según el propio consejo del guard. Hacerlo a mano es el coste que esta fila existe para describir: hubo que cambiar el id en la fila del tablero, el ancla del catálogo, el registro de evidencia de cierre, las referencias cruzadas de otras dos filas en los dos idiomas y tres comentarios de código — y los mensajes de commit y cuerpos de PR que nombran GT-633 no se pueden cambiar. **El guard se niega a adivinar qué lado está mal** — un retítulo y una colisión se ven igual desde fuera —, así que nombra los dos y lo dice. Cableado en `Governance guards (GT-578)` con un `git fetch` explícito de la base, porque un checkout superficial de CI no la tiene y un guard que fallara por ESO enseñaría a ignorarlo. Observado en rojo por `43-validate-guard-negative-fixtures` (38/38). **Lo que sigue sin ver, dicho y no insinuado:** un número asignado en una rama de la que nadie ha abierto PR. La salida `--verbose` lo advierte en cada id recién asignado en vez de dejar que el lector suponga una cobertura que no tiene. - **Component:** `Governance` · **Criticality:** P2 · **Complexity:** M - **Provenance:** Registrado el 2026-07-30 a partir de tres instancias observadas en un solo día, todas en el merge `develop` → `main` que las hizo visibles. Relacionado pero distinto de [`GT-638`](./gap-reference-catalog.es.md#gt-638): aquella fila trata de asignar un NOMBRE dos veces, ésta de hacer el TRABAJO dos veces. La misma carencia de coordinación produce ambas, y la colisión de id es su síntoma más barato. - **Acceptance criteria:** @@ -7874,7 +7874,7 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre **Title:** El tablero no tiene asignador de ids, así que dos ramas paralelas reparten el mismo número GT - **Purpose:** Que un id de gap sea único por construcción y no por que quien registró el último leyera la rama correcta. -- **Evidence:** **Un id de gap se elige leyendo el `GT-*` más alto de la rama en la que uno está, y nada lo contrasta con ninguna otra rama.** Así que dos sesiones en paralelo asignan el mismo número, y una se entera en el merge. Ya ha pasado: `8449af3d` en `develop` lleva el mensaje *"chore(board): renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"* — esa fila y [`GT-634`](./gap-reference-catalog.es.md#gt-634) en `main` recibieron el mismo id con horas de diferencia, por sesiones que no podían verse. **La renumeración es manual y con pérdidas**, que es lo que cuesta más que el choque: un id aparece en la fila del tablero, en el ancla del catálogo, en el registro de evidencia de cierre, en referencias cruzadas desde otras filas en los dos idiomas, en mensajes de commit y en cuerpos de PR — y sólo los tres primeros son comprobables mecánicamente. `08-validate-tracking` no puede ayudar, y no por falta de ganas: valida paridad EN/ES, registros de cierre y contadores **dentro de un único árbol de trabajo**, y las ramas quedan fuera de su mundo por construcción. **Por qué merece fila y no una convención:** el tablero es la única fuente de verdad de 635 gaps, y un id que significa dos cosas distintas en dos ramas vuelve ambigua toda referencia a él — incluidas las de `gap-closure-evidence.json`, que son datos validados por máquina. Tampoco es un desliz aislado: en el merge `develop` → `main` del 2026-07-30 el mismo patrón de duplicación convergente aparece **tres veces**: GT-633 se arregló dos veces por dos sesiones, el parser del guard de evidencias se arregló dos veces, y este id se asignó dos veces. Arreglo: asignar ids desde algo que no se pueda leer rancio — una marca de agua reservada y commiteada en la rama por defecto, o una comprobación en PR que falle cuando una rama introduce un id que ya existe en su base o en otro PR abierto, nombrando a ambos usuarios. +- **Evidence:** **Un id de gap se elige leyendo el `GT-*` más alto de la rama en la que uno está, y nada lo contrasta con ninguna otra rama.** Así que dos sesiones en paralelo asignan el mismo número, y una se entera en el merge. Ya ha pasado: `8449af3d` en `develop` lleva el mensaje *"chore(board): renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"* — esa fila y [`GT-634`](./gap-reference-catalog.es.md#gt-634) en `main` recibieron el mismo id con horas de diferencia, por sesiones que no podían verse. **La renumeración es manual y con pérdidas**, que es lo que cuesta más que el choque: un id aparece en la fila del tablero, en el ancla del catálogo, en el registro de evidencia de cierre, en referencias cruzadas desde otras filas en los dos idiomas, en mensajes de commit y en cuerpos de PR — y sólo los tres primeros son comprobables mecánicamente. `08-validate-tracking` no puede ayudar, y no por falta de ganas: valida paridad EN/ES, registros de cierre y contadores **dentro de un único árbol de trabajo**, y las ramas quedan fuera de su mundo por construcción. **Por qué merece fila y no una convención:** el tablero es la única fuente de verdad de 635 gaps, y un id que significa dos cosas distintas en dos ramas vuelve ambigua toda referencia a él — incluidas las de `gap-closure-evidence.json`, que son datos validados por máquina. Tampoco es un desliz aislado: en el merge `develop` → `main` del 2026-07-30 el mismo patrón de duplicación convergente aparece **tres veces**: GT-640 (entonces GT-633) se arregló dos veces por dos sesiones, el parser del guard de evidencias se arregló dos veces, y este id se asignó dos veces. Arreglo: asignar ids desde algo que no se pueda leer rancio — una marca de agua reservada y commiteada en la rama por defecto, o una comprobación en PR que falle cuando una rama introduce un id que ya existe en su base o en otro PR abierto, nombrando a ambos usuarios. - **Component:** `Governance` · **Criticality:** P2 · **Complexity:** S - **Provenance:** Registrado el 2026-07-30 tras la colisión que describe, que obligó a renumerar en `develop`. El id de ESTA fila se asignó tomando la unión de ids entre `main` y `develop` en vez del máximo de una — que es justo el apaño que el arreglo debería volver innecesario. - **Acceptance criteria:** @@ -7908,7 +7908,7 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre - [x] Los fixes que reclama la ola de seguridad del 2026-07-23 se verifican presentes en la versión del SDK que el CLI y el MCP resuelven de verdad, en vez de asumirse por el número de versión — y la verificación REFUTÓ la premisa: ninguno de los siete commits de la ola toca `src/packages/sdk-client`, así que no hay fixes de la ola en el SDK que puedan estar o faltar. - [x] `@beyondnet/evolith-sdk@1.1.0` queda deprecada en cuanto exista un sucesor al que sus consumidores puedan llegar — hecho el 2026-07-30, con un mensaje que da el motivo real (superseded por 2.0.0, `.passed` → `verdict`) y dice explícitamente que no es un aviso de seguridad. -#### GT-633 +#### GT-640 **Title:** Un guard cuyo valor esperado era una copia de su valor real, sobre una entrada que se blanquea en un artefacto cinco veces mayor diff --git a/reference/core/control-center/gaps/gap-reference-catalog.md b/reference/core/control-center/gaps/gap-reference-catalog.md index 640d6c79..dea3d6d1 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -7955,7 +7955,7 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo **Title:** Nothing makes in-flight work visible across branches, so the same gap gets fixed twice - **Purpose:** Make a gap that is already being worked discoverable BEFORE a second session starts it, rather than at merge time. -- **Evidence:** **On 2026-07-30 the same work was done twice, three separate times, by sessions that could not see each other.** (1) [`GT-633`](./gap-reference-catalog.md#gt-633) was fixed twice: a standalone capture script landed on `main` as #276, and an independent in-spec renderer landed on `develop` as #279. Both were correct, both recaptured the same numbers — and two generators for one artifact is GT-633's own defect one level up, so a THIRD session spent a full reconciliation (#294, #295, #296, #297) collapsing them to one renderer. (2) The evidence guard's parser was fixed twice: `d1ea72a3` on `main` ("stop the evidence guard blaming the evidence for two parser defects") and `5acb29ed` on `develop` ("the ratchet was stuck by two bugs in itself"). (3) A GT id was allocated twice, forcing the renumber recorded in [`GT-638`](./gap-reference-catalog.md#gt-638). **The cost is not the merge conflict.** It is the duplicated work that happened before anyone reached the conflict, plus the reconciliation it then required — and that reconciliation introduced a defect of its own (an order-sensitive comparison that rewrote the capture date on every run, invisible for a day because both runs stamped the same date). Three duplications, one repair session, one new bug: that is the real bill. **Why the board cannot see it today:** a row carries a status (`PENDING`, `IN-PROGRESS`) but not WHO is working it or WHERE. Two sessions can both read `GT-633 · PENDING` and both start, correctly. `08-validate-tracking` validates within one working tree by construction, so it cannot know another branch exists. **This is not a missing convention — the convention exists.** The single-driver rule for gap waves is already established practice; what is missing is any mechanism that makes it observable, so on a busy day it degrades silently and nobody learns until the merge. Fix directions: require a claim (branch or PR) on a row that moves to `IN-PROGRESS`, and derive the claim set from open PRs so it cannot go stale; then fail at PR time when one GT id is claimed by more than one open branch, naming both. +- **Evidence:** **On 2026-07-30 the same work was done twice, three separate times, by sessions that could not see each other.** (1) [`GT-640`](./gap-reference-catalog.md#gt-640) — registered as GT-633 until the renumber below — was fixed twice: a standalone capture script landed on `main` as #276, and an independent in-spec renderer landed on `develop` as #279. Both were correct, both recaptured the same numbers — and two generators for one artifact is GT-633's own defect one level up, so a THIRD session spent a full reconciliation (#294, #295, #296, #297) collapsing them to one renderer. (2) The evidence guard's parser was fixed twice: `d1ea72a3` on `main` ("stop the evidence guard blaming the evidence for two parser defects") and `5acb29ed` on `develop` ("the ratchet was stuck by two bugs in itself"). (3) A GT id was allocated twice, forcing the renumber recorded in [`GT-638`](./gap-reference-catalog.md#gt-638). **The cost is not the merge conflict.** It is the duplicated work that happened before anyone reached the conflict, plus the reconciliation it then required — and that reconciliation introduced a defect of its own (an order-sensitive comparison that rewrote the capture date on every run, invisible for a day because both runs stamped the same date). Three duplications, one repair session, one new bug: that is the real bill. **Why the board cannot see it today:** a row carries a status (`PENDING`, `IN-PROGRESS`) but not WHO is working it or WHERE. Two sessions can both read `GT-640 · PENDING` (registered as GT-633 at the time) and both start, correctly. `08-validate-tracking` validates within one working tree by construction, so it cannot know another branch exists. **This is not a missing convention — the convention exists.** The single-driver rule for gap waves is already established practice; what is missing is any mechanism that makes it observable, so on a busy day it degrades silently and nobody learns until the merge. Fix directions: require a claim (branch or PR) on a row that moves to `IN-PROGRESS`, and derive the claim set from open PRs so it cannot go stale; then fail at PR time when one GT id is claimed by more than one open branch, naming both. - **Component:** `Governance` · **Criticality:** P2 · **Complexity:** M - **Provenance:** Registered 2026-07-30 from three observed instances in a single day, all of them in the `develop` → `main` merge that made them visible. Related but distinct from [`GT-638`](./gap-reference-catalog.md#gt-638): that row is about allocating a NAME twice, this one about doing the WORK twice. The same coordination gap produces both, and the id collision is the cheapest symptom of it. - **Acceptance criteria:** @@ -7969,7 +7969,7 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo **Title:** The board has no id allocator, so two parallel branches hand out the same GT number - **Purpose:** Make a gap id unique by construction rather than by whoever registered it last reading the right branch. -- **Evidence:** **A gap id is chosen by reading the highest `GT-*` on the branch you happen to be on, and nothing checks that against any other branch.** So two sessions working in parallel allocate the same number, and one of them finds out at merge time. It has already happened: `8449af3d` on `develop` carries the message *"chore(board): renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"* — that row and [`GT-634`](./gap-reference-catalog.md#gt-634) on `main` were allocated the same id within hours of each other, by sessions that could not see each other. **The renumber is manual and lossy**, which is the part that costs more than the clash: an id appears in the board row, the catalog anchor, the closure-evidence record, cross-references from other rows in both languages, commit messages and PR bodies — and only the first three are mechanically checkable. `08-validate-tracking` cannot help, and not for want of trying: it validates EN/ES parity, closure records and counters **within one working tree**, and branches are outside its world by construction. **Why it is worth a row rather than a convention:** the board is the single source of truth for 635 gaps, and an id that means two different things in two branches makes every reference to it ambiguous — including the ones in `gap-closure-evidence.json`, which is machine-validated data. This is also not an isolated slip. In the `develop` → `main` merge of 2026-07-30 the same convergent-duplication pattern appears **three times**: GT-633 was fixed twice by two sessions, the evidence guard's parser was fixed twice, and this id was allocated twice. Fix: allocate ids from something that cannot be read stale — a reserved high-water mark committed on the default branch, or a PR-time check that fails when a branch introduces an id already present on its base or in another open PR, naming both users of it. **CLOSED 2026-07-30 — and on its first real run it found a SECOND collision nobody knew about.** `49-validate-gap-id-allocation` compares every id's **Title:** on HEAD against the title the same id carries on the base branch: absent on base means newly allocated, present with the same title means the same gap edited, present with a DIFFERENT title means one number naming two gaps. The title is the discriminator on purpose — "the id exists on base" is the normal case for 500-odd rows and says nothing. Run against `origin/develop` it reports **`GT-633`**: on develop it is "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29), on main it is the tautological-guard row (`c3221276`, 2026-07-30). Two gaps, one number, and the merge had not surfaced it yet. **The guard refuses to guess which side is wrong** — a retitle and a collision look identical from the outside — so it names both and says so. Wired into `Governance guards (GT-578)` with an explicit `git fetch` of the base, because a shallow CI checkout does not have it and a guard that failed for THAT reason would teach people to ignore it. Observed red by `43-validate-guard-negative-fixtures` (38/38). **What it still cannot see, stated rather than implied:** a number allocated on a branch nobody has opened a PR from. The `--verbose` output says so on every newly allocated id instead of leaving the reader to assume coverage it does not have. +- **Evidence:** **A gap id is chosen by reading the highest `GT-*` on the branch you happen to be on, and nothing checks that against any other branch.** So two sessions working in parallel allocate the same number, and one of them finds out at merge time. It has already happened: `8449af3d` on `develop` carries the message *"chore(board): renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"* — that row and [`GT-634`](./gap-reference-catalog.md#gt-634) on `main` were allocated the same id within hours of each other, by sessions that could not see each other. **The renumber is manual and lossy**, which is the part that costs more than the clash: an id appears in the board row, the catalog anchor, the closure-evidence record, cross-references from other rows in both languages, commit messages and PR bodies — and only the first three are mechanically checkable. `08-validate-tracking` cannot help, and not for want of trying: it validates EN/ES parity, closure records and counters **within one working tree**, and branches are outside its world by construction. **Why it is worth a row rather than a convention:** the board is the single source of truth for 635 gaps, and an id that means two different things in two branches makes every reference to it ambiguous — including the ones in `gap-closure-evidence.json`, which is machine-validated data. This is also not an isolated slip. In the `develop` → `main` merge of 2026-07-30 the same convergent-duplication pattern appears **three times**: GT-640 (then GT-633) was fixed twice by two sessions, the evidence guard's parser was fixed twice, and this id was allocated twice. Fix: allocate ids from something that cannot be read stale — a reserved high-water mark committed on the default branch, or a PR-time check that fails when a branch introduces an id already present on its base or in another open PR, naming both users of it. **CLOSED 2026-07-30 — and on its first real run it found a SECOND collision nobody knew about.** `49-validate-gap-id-allocation` compares every id's **Title:** on HEAD against the title the same id carries on the base branch: absent on base means newly allocated, present with the same title means the same gap edited, present with a DIFFERENT title means one number naming two gaps. The title is the discriminator on purpose — "the id exists on base" is the normal case for 500-odd rows and says nothing. Run against `origin/develop` it reports **`GT-633`**: on develop it is "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29), on main it is the tautological-guard row (`c3221276`, 2026-07-30). Two gaps, one number, and the merge had not surfaced it yet. **RESOLVED 2026-07-30 by renumbering the newer row — main's — to [`GT-640`](./gap-reference-catalog.md#gt-640)**, per this guard's own advice. Doing it by hand is the cost this row exists to describe: the id had to be changed in the board row, the catalog anchor, the closure-evidence record, cross-references from two other rows in both languages and three source comments — and the commit messages and PR bodies that name GT-633 cannot be changed at all. **The guard refuses to guess which side is wrong** — a retitle and a collision look identical from the outside — so it names both and says so. Wired into `Governance guards (GT-578)` with an explicit `git fetch` of the base, because a shallow CI checkout does not have it and a guard that failed for THAT reason would teach people to ignore it. Observed red by `43-validate-guard-negative-fixtures` (38/38). **What it still cannot see, stated rather than implied:** a number allocated on a branch nobody has opened a PR from. The `--verbose` output says so on every newly allocated id instead of leaving the reader to assume coverage it does not have. - **Component:** `Governance` · **Criticality:** P2 · **Complexity:** S - **Provenance:** Registered 2026-07-30 after the collision it describes forced a renumber on `develop`. The id for THIS row was allocated by taking the union of ids across `main` and `develop` rather than the maximum on one — which is the workaround the fix should make unnecessary. - **Acceptance criteria:** @@ -8003,7 +8003,7 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo - [x] The fixes claimed by the 2026-07-23 security wave are verified present in the SDK version the CLI and MCP actually resolve, rather than assumed from the version number — and the verification REFUTED the premise: none of the seven wave commits touches `src/packages/sdk-client`, so there are no wave fixes in the SDK to be present or absent. - [x] `@beyondnet/evolith-sdk@1.1.0` is deprecated once a successor its consumers can reach exists — done 2026-07-30, with a message that states the real reason (superseded by 2.0.0, `.passed` → `verdict`) and says explicitly it is not a security advisory. -#### GT-633 +#### GT-640 **Title:** A guard whose expected value was a copy of its actual value, over an input laundered into an artifact five times its size diff --git a/reference/core/control-center/gaps/gap-tracking.es.md b/reference/core/control-center/gaps/gap-tracking.es.md index 9f9f6cfe..fcf48cdd 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -13,11 +13,11 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | ID | Gap | Qué significa | Ejemplo | Componente | Fase | Criticidad | Complejidad | Estado | |---|---|---|---|:---:|:---:|:---:|:---:|:---:| -| [`GT-639`](./gap-reference-catalog.es.md#gt-639) | **Nada hace visible el trabajo en vuelo entre ramas, así que el mismo gap se arregla dos veces.** El 2026-07-30 el mismo trabajo se hizo dos veces, en tres ocasiones distintas, por sesiones que no podían verse: [`GT-633`](./gap-reference-catalog.es.md#gt-633) arreglado por un script de captura en `main` (#276) Y por un renderer independiente dentro del spec en `develop` (#279) — los dos correctos, los dos recapturando los mismos números, y dos generadores para un artefacto es el defecto del propio GT-633 un nivel más arriba, así que una tercera sesión gastó una reconciliación entera en dejar uno; el parser del guard de evidencias arreglado dos veces (`d1ea72a3` en main, `5acb29ed` en develop); y un id de GT asignado dos veces, forzando la renumeración que hay detrás de [`GT-638`](./gap-reference-catalog.es.md#gt-638). **El coste no es el conflicto de merge** — es el trabajo duplicado antes de que nadie llegara a él, más la reconciliación, que introdujo un defecto propio (una comparación sensible al orden que reescribía la fecha de captura en cada corrida, invisible un día porque ambas estampaban la misma fecha). Una fila lleva estado pero no QUIÉN la trabaja ni DÓNDE, así que dos sesiones pueden leer ambas `PENDIENTE` y empezar ambas, correctamente. **La convención de un solo driver ya existe; lo que falta es un mecanismo que la haga observable**, así que en un día cargado se degrada en silencio. | | | `Governance` | Cross | P2 | M | `PENDIENTE` | -| [`GT-638`](./gap-reference-catalog.es.md#gt-638) | **El tablero no tiene asignador de ids, así que dos ramas paralelas reparten el mismo número GT.** Un id se elige leyendo el `GT-*` más alto de la rama en la que uno está, y nada lo contrasta con otra rama — así que dos sesiones en paralelo asignan el mismo número y una se entera en el merge. Ya pasó: `8449af3d` en `develop` dice *"renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"*; esa fila y [`GT-634`](./gap-reference-catalog.es.md#gt-634) en `main` tomaron el mismo id con horas de diferencia, desde sesiones que no podían verse. **La renumeración es manual y con pérdidas**, que cuesta más que el choque: un id vive en la fila del tablero, el ancla del catálogo, el registro de evidencia de cierre, referencias cruzadas en dos idiomas, mensajes de commit y cuerpos de PR — y sólo los tres primeros son comprobables mecánicamente. `08-validate-tracking` no puede ayudar por construcción: valida dentro de un único árbol de trabajo, y las ramas quedan fuera de su mundo. Tampoco es un desliz aislado — en el merge `develop` → `main` del 2026-07-30 el mismo patrón de duplicación convergente aparece tres veces: GT-633 arreglado dos veces, el parser del guard de evidencias arreglado dos veces, este id asignado dos veces. **CERRADO el 2026-07-30 — y en su primera corrida real encontró una SEGUNDA colisión que nadie conocía.** `49-validate-gap-id-allocation` compara el **Title:** de cada id en HEAD contra el título que ese id lleva en la rama base; un título distinto para el mismo id es un número nombrando dos gaps. Contra `origin/develop` reporta **`GT-633`**: allí es "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29) y en main es la fila del guard tautológico (`c3221276`, 2026-07-30). El merge no lo había sacado a la luz. Se niega a adivinar qué lado está mal — un retítulo y una colisión se ven igual desde fuera —, así que nombra los dos. Cableado en `Governance guards (GT-578)` con un `git fetch` explícito de la base, porque un checkout superficial de CI no la tiene. Observado en rojo por `43-validate-guard-negative-fixtures` (38/38). Sigue sin ver un id asignado en una rama sin PR abierto, y su salida `--verbose` lo advierte en cada id nuevo. | | | `Governance` | Cross | P2 | S | `COMPLETADO` | +| [`GT-640`](./gap-reference-catalog.es.md#gt-640) | **El guard comparaba el snapshot contra seis números copiados del propio snapshot, así que solo podía pasar — y el archivo que no estaba vigilando se estampa en 388 filas de un artefacto mayor.** `native-evaluability-snapshot.json` declaraba en su propia cabecera que era "un SNAPSHOT CAPTURADO, no la fuente de verdad". **No existía ningún script de captura.** Se mantenía a mano y derivó: fijaba `documentation-only: 129` mientras Core fijaba 136, y seguía llamando `unimplemented-native` a reglas que ya tenían handler. Su guard en `iso-5055-mapping.test.mjs` asertaba los seis conteos de clase del snapshot contra seis números escritos a mano en el test — los mismos seis literales que el snapshot ya contenía. **El valor esperado y el valor real eran copias el uno del otro**, así que la aserción se sostenía mientras el archivo no cambiara, fijara Core lo que fijara; informaba de una concordancia que nunca comprobó, lo cual es peor que no tener guard, porque la fila que protege se lee como verificada. **La deriva no se queda donde empieza.** `build-iso-5055-mapping.mjs` estampa `nativeEvaluability` en TODAS las filas del mapeo ISO/IEC 5055 desde ese archivo — 388 tras la recaptura, 381 antes — así que una sola clase rancia se blanquea en un artefacto derivado cinco veces mayor que su entrada, y sobredimensiona el backlog de handlers que es el entregable entero de [`GT-598`](./gap-reference-catalog.es.md#gt-598). El orden recaptura→reconstrucción no estaba escrito en ningún sitio, y el guard del propio mapeo no corría en **ningún workflow**. **CORREGIDO el 2026-07-29:** un script de captura que ejecuta el triage REAL de Core vía `ts-node` en vez de reimplementar la clasificación, la medición extraída del spec de jest a `test/rule-corpus-triage.ts` para que un script pueda alcanzarla — esa inalcanzabilidad es la razón de que el archivo se mantuviera a mano —, aserciones snapshot-contra-triage-fresco en ambas direcciones, un guard del job de documentación que lee los conteos que Core fija LEYENDO el spec y **lanza excepción** cuando el literal falta o cambia de forma, y ambos pasos declarados como eslabones de la cadena de artefactos derivados de [`GT-630`](./gap-reference-catalog.es.md#gt-630). **Recapturado el 2026-07-29 tras mergear el fix en la línea actual, y la deriva había crecido mientras estaba en vuelo:** `native-handler` 139 → 151, `documentation-only` 129 → 136, el backlog de handlers 60 → **48**, corpus 379 → 386, mapeo 381 → 388 filas. Doce de esos handlers los cerraron [`GT-595`](./gap-reference-catalog.es.md#gt-595) y [`GT-632`](./gap-reference-catalog.es.md#gt-632) mientras el snapshot mantenido a mano seguía reportándolos como backlog — el defecto demostrándose una vez más, sobre números que nadie escribió dos veces. **RENUMERADA GT-633 -> GT-640 el 2026-07-30**, después de que `49-validate-gap-id-allocation` detectara que `GT-633` ya nombraba otro gap en `develop` (el scaffolding de `evolith init`, `c8270e48`, registrado un día antes). Se mueve la fila más nueva, según el propio consejo del guard. | | | `Governance` | Cross | P1 | S | `COMPLETADO` | +| [`GT-639`](./gap-reference-catalog.es.md#gt-639) | **Nada hace visible el trabajo en vuelo entre ramas, así que el mismo gap se arregla dos veces.** El 2026-07-30 el mismo trabajo se hizo dos veces, en tres ocasiones distintas, por sesiones que no podían verse: [`GT-640`](./gap-reference-catalog.es.md#gt-640) (registrado como GT-633 entonces) arreglado por un script de captura en `main` (#276) Y por un renderer independiente dentro del spec en `develop` (#279) — los dos correctos, los dos recapturando los mismos números, y dos generadores para un artefacto es el defecto del propio GT-640 un nivel más arriba, así que una tercera sesión gastó una reconciliación entera en dejar uno; el parser del guard de evidencias arreglado dos veces (`d1ea72a3` en main, `5acb29ed` en develop); y un id de GT asignado dos veces, forzando la renumeración que hay detrás de [`GT-638`](./gap-reference-catalog.es.md#gt-638). **El coste no es el conflicto de merge** — es el trabajo duplicado antes de que nadie llegara a él, más la reconciliación, que introdujo un defecto propio (una comparación sensible al orden que reescribía la fecha de captura en cada corrida, invisible un día porque ambas estampaban la misma fecha). Una fila lleva estado pero no QUIÉN la trabaja ni DÓNDE, así que dos sesiones pueden leer ambas `PENDIENTE` y empezar ambas, correctamente. **La convención de un solo driver ya existe; lo que falta es un mecanismo que la haga observable**, así que en un día cargado se degrada en silencio. | | | `Governance` | Cross | P2 | M | `PENDIENTE` | +| [`GT-638`](./gap-reference-catalog.es.md#gt-638) | **El tablero no tiene asignador de ids, así que dos ramas paralelas reparten el mismo número GT.** Un id se elige leyendo el `GT-*` más alto de la rama en la que uno está, y nada lo contrasta con otra rama — así que dos sesiones en paralelo asignan el mismo número y una se entera en el merge. Ya pasó: `8449af3d` en `develop` dice *"renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"*; esa fila y [`GT-634`](./gap-reference-catalog.es.md#gt-634) en `main` tomaron el mismo id con horas de diferencia, desde sesiones que no podían verse. **La renumeración es manual y con pérdidas**, que cuesta más que el choque: un id vive en la fila del tablero, el ancla del catálogo, el registro de evidencia de cierre, referencias cruzadas en dos idiomas, mensajes de commit y cuerpos de PR — y sólo los tres primeros son comprobables mecánicamente. `08-validate-tracking` no puede ayudar por construcción: valida dentro de un único árbol de trabajo, y las ramas quedan fuera de su mundo. Tampoco es un desliz aislado — en el merge `develop` → `main` del 2026-07-30 el mismo patrón de duplicación convergente aparece tres veces: GT-640 (entonces GT-633) arreglado dos veces, el parser del guard de evidencias arreglado dos veces, este id asignado dos veces. **CERRADO el 2026-07-30 — y en su primera corrida real encontró una SEGUNDA colisión que nadie conocía.** `49-validate-gap-id-allocation` compara el **Title:** de cada id en HEAD contra el título que ese id lleva en la rama base; un título distinto para el mismo id es un número nombrando dos gaps. Contra `origin/develop` reportó **`GT-633`**: allí es "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29) y en main era la fila del guard tautológico (`c3221276`, 2026-07-30), renumerada después a `GT-640`. El merge no lo había sacado a la luz. Se niega a adivinar qué lado está mal — un retítulo y una colisión se ven igual desde fuera —, así que nombra los dos. Cableado en `Governance guards (GT-578)` con un `git fetch` explícito de la base, porque un checkout superficial de CI no la tiene. Observado en rojo por `43-validate-guard-negative-fixtures` (38/38). Sigue sin ver un id asignado en una rama sin PR abierto, y su salida `--verbose` lo advierte en cada id nuevo. | | | `Governance` | Cross | P2 | S | `COMPLETADO` | | [`GT-635`](./gap-reference-catalog.es.md#gt-635) | **El ratchet de referencias muertas está por encima del presupuesto en main desde el 2026-07-28, así que el job de gobernanza está en rojo permanente.** `41-validate-evidence-commands --strict --max-dead 305` reporta **309 referencias muertas en un checkout limpio**, así que `Governance guards (GT-578)` sale 1 en cada corrida de `ci-cd.yml` — observado en las corridas `30482497995`, `30470648146` y `30466280865`, todas en `main` y sin nada por mergear. El presupuesto se fijó en `4a1b92d6` "donde se midió"; desde entonces diez commits han editado `gap-closure-evidence.json`, cada uno añadiendo `validationCommands`, y la cuenta se fue por encima. El propósito entero del ratchet es que las referencias muertas no CREZCAN, y está reportando exactamente eso mientras se lo ignora, porque un job en rojo permanente se lee como ruido de fondo — el mismo modo de fallo que [`GT-622`](./gap-reference-catalog.es.md#gt-622) registra para la configuración huérfana de CodeQL. El próximo cambio que añada de verdad una referencia muerta será indistinguible de las cuatro que ya están. **Lo que no hay que hacer:** subir el presupuesto a 309, que convierte un ratchet incumplido en uno ratificado. | | | `Governance` | Cross | P2 | S | `COMPLETADO` | | [`GT-634`](./gap-reference-catalog.es.md#gt-634) | **El CLI y el MCP publicados resuelven una build del SDK anterior a la ola de seguridad, porque un rango caret bajo 1.x la fija.** `@beyondnet/evolith-cli@1.2.1` y `@beyondnet/evolith-mcp@1.2.1` declaran ambos `@beyondnet/evolith-sdk: ^1.1.0`, y eso resuelve **exactamente `1.1.0`** — el SDK publica `1.0.0`, `1.1.0` y `2.0.0` y nada en medio, y un caret no cruza un mayor. Las fechas son el hallazgo: `sdk@1.1.0` se publicó el 2026-07-18, `sdk@2.0.0` el 2026-07-27 como parte de la release que cerró [`GT-570`](./gap-reference-catalog.es.md#gt-570), y ambos consumidores el 2026-07-28 — **después** de que 2.0.0 existiera, y seguían en la línea 1.x. Así que la exposición que GT-570 declaró cerrada para "quien instale `latest`" no está cerrada para esta dependencia. Dicho con precisión, porque la afirmación fuerte no está verificada aquí: lo medido son las fechas y la resolución, no el contenido — esta fila no afirma que `sdk@1.1.0` cargue una vulnerabilidad concreta. Encontrado ejecutando el criterio de deprecación de [`GT-624`](./gap-reference-catalog.es.md#gt-624), que es lo que dejó el rango a la vista: deprecar `sdk@1.1.0` avisaría en cada instalación limpia de la línea actual sin ningún sucesor 1.x que ofrecer. **CERRADO el 2026-07-30 — 1.2.2 está publicada, y el marco de seguridad de esta fila era ERRÓNEO.** Una instalación limpia de `@beyondnet/evolith-cli@latest` resuelve ya `@beyondnet/evolith-sdk@2.0.0` con el binario arrancando. Pero verificar el criterio 2 refutó la premisa: **ninguno de los siete commits de la ola toca `src/packages/sdk-client` — cero ficheros** — y el propio commit de release de la ola nombra los tres paquetes afectados (mcp, cli, agent-runtime). El SDK se republicó con ellos, no se arregló. Lo real es un MAYOR rancio fijado por un caret bajo 1.x más un lockfile que anidaba el tarball del registry tapando el enlace del workspace — la clase de `GT-625`, no una exposición de seguridad. La cautela escrita al abrir la fila es lo que abarató la corrección. | | | `Infra` | Cross | P1 | S | `COMPLETADO` | -| [`GT-633`](./gap-reference-catalog.es.md#gt-633) | **El guard comparaba el snapshot contra seis números copiados del propio snapshot, así que solo podía pasar — y el archivo que no estaba vigilando se estampa en 388 filas de un artefacto mayor.** `native-evaluability-snapshot.json` declaraba en su propia cabecera que era "un SNAPSHOT CAPTURADO, no la fuente de verdad". **No existía ningún script de captura.** Se mantenía a mano y derivó: fijaba `documentation-only: 129` mientras Core fijaba 136, y seguía llamando `unimplemented-native` a reglas que ya tenían handler. Su guard en `iso-5055-mapping.test.mjs` asertaba los seis conteos de clase del snapshot contra seis números escritos a mano en el test — los mismos seis literales que el snapshot ya contenía. **El valor esperado y el valor real eran copias el uno del otro**, así que la aserción se sostenía mientras el archivo no cambiara, fijara Core lo que fijara; informaba de una concordancia que nunca comprobó, lo cual es peor que no tener guard, porque la fila que protege se lee como verificada. **La deriva no se queda donde empieza.** `build-iso-5055-mapping.mjs` estampa `nativeEvaluability` en TODAS las filas del mapeo ISO/IEC 5055 desde ese archivo — 388 tras la recaptura, 381 antes — así que una sola clase rancia se blanquea en un artefacto derivado cinco veces mayor que su entrada, y sobredimensiona el backlog de handlers que es el entregable entero de [`GT-598`](./gap-reference-catalog.es.md#gt-598). El orden recaptura→reconstrucción no estaba escrito en ningún sitio, y el guard del propio mapeo no corría en **ningún workflow**. **CORREGIDO el 2026-07-29:** un script de captura que ejecuta el triage REAL de Core vía `ts-node` en vez de reimplementar la clasificación, la medición extraída del spec de jest a `test/rule-corpus-triage.ts` para que un script pueda alcanzarla — esa inalcanzabilidad es la razón de que el archivo se mantuviera a mano —, aserciones snapshot-contra-triage-fresco en ambas direcciones, un guard del job de documentación que lee los conteos que Core fija LEYENDO el spec y **lanza excepción** cuando el literal falta o cambia de forma, y ambos pasos declarados como eslabones de la cadena de artefactos derivados de [`GT-630`](./gap-reference-catalog.es.md#gt-630). **Recapturado el 2026-07-29 tras mergear el fix en la línea actual, y la deriva había crecido mientras estaba en vuelo:** `native-handler` 139 → 151, `documentation-only` 129 → 136, el backlog de handlers 60 → **48**, corpus 379 → 386, mapeo 381 → 388 filas. Doce de esos handlers los cerraron [`GT-595`](./gap-reference-catalog.es.md#gt-595) y [`GT-632`](./gap-reference-catalog.es.md#gt-632) mientras el snapshot mantenido a mano seguía reportándolos como backlog — el defecto demostrándose una vez más, sobre números que nadie escribió dos veces. | | | `Governance` | Cross | P1 | S | `COMPLETADO` | | [`GT-632`](./gap-reference-catalog.es.md#gt-632) | **El MCP denegaba todas sus herramientas en producción por una ruta mal escrita, y la prueba de que funcionaba era estado local sin versionar.** `abac-evaluator.ts:322` resolvía la política compilada en `/sdk/cli/rulesets/opa/policy.wasm` — la ruta ANTERIOR al refactor `src/`. El fichero vive en `src/sdk/cli/…`. Cuando el stat falla, el evaluador deniega fail-closed en producción, así que en un checkout limpio con `NODE_ENV=production` la política compilada nunca se carga y TODA herramienta MCP se rechaza. Parecía verde porque `policy.wasm` está gitignorado: un fichero rancio dejado en la ruta vieja por una época anterior hacía que la carga funcionara en local, y el test que lo cubre pasaba contra un artefacto que ningún checkout fresco tendría. Misma forma que `GT-625` — un verde que dependía de estado no versionado — pero en el camino de autorización. Encontrado el 2026-07-29 investigando por qué `abac-rego-parity.spec.ts` se había puesto rojo; el rego era correcto (`opa eval` devuelve `allow: true` para un arquitecto en producción). **`40-validate-path-literals` no puede ver esta clase:** barre literales de cadena, y esto es una construcción `path.join(corePath, 'sdk', 'cli', …)`. Sobreviven doce joins así en `src/**` — los otros once están en `opa-input-builder.ts` y `cli-release-rule.handler.ts`, donde una ruta equivocada produce un falso NEGATIVO en un evaluador en vez de una denegación, que es más silencioso y no menos incorrecto. **CORREGIDO para el camino de autorización el 2026-07-29:** el evaluador prueba ahora ambas ubicaciones, la nueva primero — no es dejadez, es lo que mantiene funcionando una imagen ya desplegada mientras se corrige la ruta. Revertir el arreglo pone `abac-rego-parity.spec.ts` en rojo (1 de 11); con él, mcp-server va 434/434. Quedan los otros once joins y el punto ciego del guard. **Criterio 2 CERRADO el 2026-07-29 — y dos de las once estaban mal de una forma que el prefijo por sí solo no arreglaba.** Ocho eran supervivientes simples del movimiento a `src/` y admitieron el prefijo. Las otras tres eran peores: dos apuntaban a `sdk/cli/src/core/mcp/server.ts`, un fichero que salió del CLI por completo cuando mcp-server pasó a ser su propio paquete (hoy `src/packages/mcp-server/src/mcp/mcp-server.service.ts`); y una exigía un `package-lock.json` por paquete, cosa que los WORKSPACES de npm impiden — hay exactamente un lockfile, en la raíz, así que esa comprobación solo podía ser falsa. Una regla que nadie puede satisfacer no es una regla más estricta. **Las specs y las fixtures de paridad también codificaban el layout viejo** (`cli-release-readiness.fixture.json`, `mcp.fixture.json`), y por eso llevaban tiempo en verde sobre rutas que no existen: la fixture y el código coincidían entre sí y ninguno coincidía con el repositorio. Ambas corregidas. core-domain 1367/1367, `28-native-evaluator-parity` exit 0. **Criterio 3 CERRADO el 2026-07-29 — la primera corrida del guard estuvo casi toda equivocada, que es el hallazgo.** `47-validate-joined-paths` reportó 19 rutas rotas; solo 3 eran defectos. Cada una de las otras 16 habría empeorado el código: seis tenían base `root`, un PARÁMETRO que nombra el workspace del cliente, así que corregirlas rompería la evaluación de todos los satélites; nueve eran miembros de CADENAS DE FALLBACK correctas cuando uno de cuatro layouts resuelve, y la forma natural de callarlas es borrar los fallbacks, rompiendo imágenes desplegadas; una era una PROHIBICIÓN cuya ausencia ES el estado que aprueba, así que corregirla habría invertido la regla. Las cadenas se detectan ahora estructuralmente (hermanos de array, o las ramas de un ternario); las prohibiciones no pueden, y llevan razón escrita. Los tres defectos reales: `opa-evaluator.ts` resolvía `policy.wasm` y los esquemas de entrada sin el prefijo `src/`, misma clase fail-closed que el P0 de arriba; `satellite-upgrade-diff.ts` sondeaba `rulesets` detrás de un return temprano, reportando cero cambios de rulesets en cada upgrade de satélite; `artifact-path-resolver.ts` nombraba un `adr-matrix.json` que no existe en ningún layout. 11 fixtures negativas, los dos suelos anti-vacuos, y `43-validate-guard-negative-fixtures` lo OBSERVÓ en rojo sobre 36 guards. Una indulgencia conocida (dos joins como argumentos hermanos) queda anotada en su cabecera en vez de escondida. | | | `MCP Server` | Cross | P0 | M | `COMPLETADO` | | [`GT-631`](./gap-reference-catalog.es.md#gt-631) | **Extraído de [`GT-573`](./gap-reference-catalog.es.md#gt-573) para que su mitad cross-repo se siga en vez de absorberse en un cierre.** La mitad del Core del tercer criterio de GT-573 está hecha: `evaluation-context` y `evaluation-result` están fijados en `MACHINE_CONTRACT_SET` junto a `gate-evidence` y `output-envelope`, así que un cambio en cualquiera de esas formas ya rompe `10-validate-contract-conformance` en vez de llegar en silencio a los consumidores. Lo que queda está en otro repositorio y no se puede hacer desde aquí: el **Tracker** debe re-fijar esos dos esquemas en su propio `contracts/evolith-core-contracts.json` con su sha256, enlazar las tres fixtures publicadas (`EVALUATION_RESULT_PASS/FAIL/OPA_GATE_FAIL`) en un test de binding de DTOs, quitar o documentar `resolvedTopology` (que el resultado canónico nunca llevó), renombrar su `phase` de gate a `phaseId`, y eliminar su caída a `SKIPPED` para un payload que trae un `overallVerdict` no vacío. Hasta entonces, un cambio de forma del lado Core se caza aquí y sigue sorprendiendo al consumidor. | | | `Evolith Tracker` | Cross | P1 | S | `PENDIENTE` | | [`GT-630`](./gap-reference-catalog.es.md#gt-630) | **Los artefactos derivados tienen un ORDEN de dependencia y nada lo exigía — costó tres checks requeridos en rojo solo el 2026-07-28.** `generate-executive-summary.mjs` lee `maturity-reconciliation.json`, que a su vez se deriva del tablero de gaps. Generar el resumen ANTES de reconciliar captura un valor que el reconciliador está a punto de mover. El `--check` de cada artefacto pasa en ese momento — el resumen coincide de verdad con lo que se acaba de escribir — así que `ci-runner governance` sale verde en local y `Validate documentation` sale rojo en el runner, donde los pasos corren en el orden declarado. No es un fallo de artefacto rancio, que los `--check` individuales ya cazan; es un fallo de ORDEN, invisible para cualquier comprobación que mire un artefacto cada vez. La secuencia correcta (reconciliar → suite → generar → validar) no estaba escrita en ningún sitio del repositorio, que es la razón de que el mismo error se cometiera tres veces en una sesión por alguien a quien ya le había mordido dos. **CERRADO al registrarse.** La cadena es ahora DATOS en `46-validate-derived-artifact-order.mjs` — productor, qué consume, qué escribe — recorrida en orden de dependencia y cableada en `Validate documentation`. Informa del PRIMER eslabón rancio con el comando que lo arregla y las entradas que deben estar al día antes que él, porque un artefacto construido sobre entrada rancia no está mal por sí mismo. Escribir los tests acotó cuánto vale la pasada de punto fijo, y ese límite queda recogido en el guard: con generadores deterministas, la comprobación de orden ya la subsume; se gana su sitio ante un generador cuya salida no depende solo de sus entradas declaradas, que es el caso que ejercita el autotest. 8 autotests, cuatro de ellos anti-vacuos. Registrado en `guard-classification.mjs`; 58 guards clasificados, 35 vistos fallar. | | | `Governance` | Cross | P2 | S | `COMPLETADO` | diff --git a/reference/core/control-center/gaps/gap-tracking.md b/reference/core/control-center/gaps/gap-tracking.md index 38faafe5..0b1416bd 100644 --- a/reference/core/control-center/gaps/gap-tracking.md +++ b/reference/core/control-center/gaps/gap-tracking.md @@ -13,11 +13,11 @@ This board is the single source of truth for technical debt, gaps, opportunities | ID | Gap | What it means | Example | Component | Phase | Criticality | Complexity | Status | |---|---|---|---|:---:|:---:|:---:|:---:|:---:| -| [`GT-639`](./gap-reference-catalog.md#gt-639) | **Nothing makes in-flight work visible across branches, so the same gap gets fixed twice.** On 2026-07-30 the same work was done twice, three separate times, by sessions that could not see each other: [`GT-633`](./gap-reference-catalog.md#gt-633) fixed by a standalone capture script on `main` (#276) AND by an independent in-spec renderer on `develop` (#279) — both correct, both recapturing the same numbers, and two generators for one artifact is GT-633's own defect one level up, so a third session spent a full reconciliation collapsing them; the evidence guard's parser fixed twice (`d1ea72a3` on main, `5acb29ed` on develop); and a GT id allocated twice, forcing the renumber behind [`GT-638`](./gap-reference-catalog.md#gt-638). **The cost is not the merge conflict** — it is the duplicated work before anyone reached it, plus the reconciliation, which itself introduced a defect (an order-sensitive comparison that rewrote the capture date every run, invisible for a day because both runs stamped the same date). A row carries a status but not WHO is working it or WHERE, so two sessions can both read `PENDING` and both start, correctly. **The single-driver convention already exists; what is missing is any mechanism that makes it observable**, so on a busy day it degrades silently. | | | `Governance` | Cross | P2 | M | `PENDING` | -| [`GT-638`](./gap-reference-catalog.md#gt-638) | **The board has no id allocator, so two parallel branches hand out the same GT number.** An id is chosen by reading the highest `GT-*` on the branch you happen to be on, and nothing contrasts that with any other branch — so two sessions in parallel allocate the same number and one finds out at merge time. It already happened: `8449af3d` on `develop` reads *"renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"*; that row and [`GT-634`](./gap-reference-catalog.md#gt-634) on `main` took the same id within hours, from sessions that could not see each other. **The renumber is manual and lossy**, which costs more than the clash: an id lives in the board row, the catalog anchor, the closure-evidence record, cross-references in both languages, commit messages and PR bodies — and only the first three are mechanically checkable. `08-validate-tracking` cannot help by construction: it validates within one working tree, and branches are outside its world. Not an isolated slip either — in the 2026-07-30 `develop` → `main` merge the same convergent-duplication pattern appears three times: GT-633 fixed twice, the evidence guard's parser fixed twice, this id allocated twice. **CLOSED 2026-07-30 — and on its first real run it found a SECOND collision nobody knew about.** `49-validate-gap-id-allocation` compares every id's **Title:** on HEAD against the title the same id carries on the base branch; a different title on the same id is one number naming two gaps. Against `origin/develop` it reports **`GT-633`**: there it is "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29), on main it is the tautological-guard row (`c3221276`, 2026-07-30). The merge had not surfaced it. It refuses to guess which side is wrong — a retitle and a collision look identical from outside — so it names both. Wired into `Governance guards (GT-578)` with an explicit `git fetch` of the base, since a shallow CI checkout lacks it. Observed red by `43-validate-guard-negative-fixtures` (38/38). Still cannot see an id allocated on a branch with no open PR, and its `--verbose` output says so on every newly allocated id. | | | `Governance` | Cross | P2 | S | `DONE` | +| [`GT-640`](./gap-reference-catalog.md#gt-640) | **The guard compared the snapshot against six numbers copied out of the snapshot, so it could only ever pass — and the file it was failing to guard is stamped onto 388 rows of a larger artifact.** `native-evaluability-snapshot.json` declared in its own header that it was "a CAPTURED SNAPSHOT, not the source of truth". **No capture script existed.** It was hand-maintained and it drifted: it pinned `documentation-only: 129` while Core pinned 136, and went on calling rules `unimplemented-native` after they had handlers. Its guard in `iso-5055-mapping.test.mjs` asserted the snapshot's six class counts against six numbers typed into the test — the same six literals the snapshot already contained. **Expected value and actual value were copies of each other**, so the assertion held for as long as the file was unchanged, whatever Core actually pinned; it reported an agreement it never checked, which is worse than no guard, because the row it protects reads as verified. **The drift does not stay where it starts.** `build-iso-5055-mapping.mjs` stamps `nativeEvaluability` onto EVERY row of the ISO/IEC 5055 mapping from that file — 388 rows after the recapture, 381 before — so one stale class is laundered into a derived artifact five times the size of its input, and overstates the handler backlog that is [`GT-598`](./gap-reference-catalog.md#gt-598)'s entire deliverable. The recapture→rebuild order was written down nowhere, and the mapping's own guard ran in **no workflow at all**. **FIXED 2026-07-29:** a capture script that runs Core's REAL triage through `ts-node` instead of reimplementing the classification, the measurement extracted out of the jest spec into `test/rule-corpus-triage.ts` so a script can reach it — that unreachability is why the file was maintained by hand — snapshot-vs-fresh-triage assertions in both directions, a documentation-job guard that reads Core's pinned counts OUT of the spec and **throws** when the literal is missing or reshaped, and both steps declared as links in the [`GT-630`](./gap-reference-catalog.md#gt-630) derived-artifact chain. **Recaptured on 2026-07-29 after merging the fix into the current line, and the drift had grown while it was in flight:** `native-handler` 139 → 151, `documentation-only` 129 → 136, the handler backlog 60 → **48**, corpus 379 → 386, mapping 381 → 388 rows. Twelve of those handlers were closed by [`GT-595`](./gap-reference-catalog.md#gt-595) and [`GT-632`](./gap-reference-catalog.md#gt-632) while the hand-maintained snapshot went on reporting them as backlog — the defect demonstrating itself one more time, on numbers nobody typed twice. **RENUMBERED GT-633 -> GT-640 on 2026-07-30**, after `49-validate-gap-id-allocation` found that `GT-633` already named a different gap on `develop` (`evolith init` scaffolding, `c8270e48`, registered a day earlier). The newer row moves, per the guard's own advice. | | | `Governance` | Cross | P1 | S | `DONE` | +| [`GT-639`](./gap-reference-catalog.md#gt-639) | **Nothing makes in-flight work visible across branches, so the same gap gets fixed twice.** On 2026-07-30 the same work was done twice, three separate times, by sessions that could not see each other: [`GT-640`](./gap-reference-catalog.md#gt-640) (registered as GT-633 at the time) fixed by a standalone capture script on `main` (#276) AND by an independent in-spec renderer on `develop` (#279) — both correct, both recapturing the same numbers, and two generators for one artifact is GT-640's own defect one level up, so a third session spent a full reconciliation collapsing them; the evidence guard's parser fixed twice (`d1ea72a3` on main, `5acb29ed` on develop); and a GT id allocated twice, forcing the renumber behind [`GT-638`](./gap-reference-catalog.md#gt-638). **The cost is not the merge conflict** — it is the duplicated work before anyone reached it, plus the reconciliation, which itself introduced a defect (an order-sensitive comparison that rewrote the capture date every run, invisible for a day because both runs stamped the same date). A row carries a status but not WHO is working it or WHERE, so two sessions can both read `PENDING` and both start, correctly. **The single-driver convention already exists; what is missing is any mechanism that makes it observable**, so on a busy day it degrades silently. | | | `Governance` | Cross | P2 | M | `PENDING` | +| [`GT-638`](./gap-reference-catalog.md#gt-638) | **The board has no id allocator, so two parallel branches hand out the same GT number.** An id is chosen by reading the highest `GT-*` on the branch you happen to be on, and nothing contrasts that with any other branch — so two sessions in parallel allocate the same number and one finds out at merge time. It already happened: `8449af3d` on `develop` reads *"renumber the ratchet fix GT-634 -> GT-637, ID collision with develop"*; that row and [`GT-634`](./gap-reference-catalog.md#gt-634) on `main` took the same id within hours, from sessions that could not see each other. **The renumber is manual and lossy**, which costs more than the clash: an id lives in the board row, the catalog anchor, the closure-evidence record, cross-references in both languages, commit messages and PR bodies — and only the first three are mechanically checkable. `08-validate-tracking` cannot help by construction: it validates within one working tree, and branches are outside its world. Not an isolated slip either — in the 2026-07-30 `develop` → `main` merge the same convergent-duplication pattern appears three times: GT-640 (then GT-633) fixed twice, the evidence guard's parser fixed twice, this id allocated twice. **CLOSED 2026-07-30 — and on its first real run it found a SECOND collision nobody knew about.** `49-validate-gap-id-allocation` compares every id's **Title:** on HEAD against the title the same id carries on the base branch; a different title on the same id is one number naming two gaps. Against `origin/develop` it reported **`GT-633`**: there it is "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29), on main it was the tautological-guard row (`c3221276`, 2026-07-30), since renumbered to `GT-640`. The merge had not surfaced it. It refuses to guess which side is wrong — a retitle and a collision look identical from outside — so it names both. Wired into `Governance guards (GT-578)` with an explicit `git fetch` of the base, since a shallow CI checkout lacks it. Observed red by `43-validate-guard-negative-fixtures` (38/38). Still cannot see an id allocated on a branch with no open PR, and its `--verbose` output says so on every newly allocated id. | | | `Governance` | Cross | P2 | S | `DONE` | | [`GT-635`](./gap-reference-catalog.md#gt-635) | **The dead-reference ratchet has been over budget on main since 2026-07-28, so the governance job is permanently red.** `41-validate-evidence-commands --strict --max-dead 305` reports **309 dead references on a clean checkout**, so `Governance guards (GT-578)` exits 1 on every run of `ci-cd.yml` — observed on runs `30482497995`, `30470648146` and `30466280865`, all on `main` with nothing unmerged. The budget was set in `4a1b92d6` "where it was measured"; ten commits have edited `gap-closure-evidence.json` since, each adding `validationCommands`, and the count drifted past it. The ratchet's whole purpose is that dead references must not GROW, and it is reporting exactly that while being ignored, because a permanently red job reads as background noise — the same failure mode [`GT-622`](./gap-reference-catalog.md#gt-622) records for the orphaned CodeQL configuration. The next change that genuinely adds a dead reference will be indistinguishable from the four already there. **What not to do:** raise the budget to 309, which converts a breached ratchet into a ratified one. | | | `Governance` | Cross | P2 | S | `DONE` | | [`GT-634`](./gap-reference-catalog.md#gt-634) | **The published CLI and MCP resolve an SDK build that predates the security wave, because a caret range under 1.x pins it.** `@beyondnet/evolith-cli@1.2.1` and `@beyondnet/evolith-mcp@1.2.1` both declare `@beyondnet/evolith-sdk: ^1.1.0`, and that resolves to **exactly `1.1.0`** — the SDK publishes `1.0.0`, `1.1.0` and `2.0.0` and nothing in between, and a caret cannot cross a major. The dates are the finding: `sdk@1.1.0` was published 2026-07-18, `sdk@2.0.0` on 2026-07-27 as part of the release that closed [`GT-570`](./gap-reference-catalog.md#gt-570), and both consumers on 2026-07-28 — **after** 2.0.0 existed, still on the 1.x line. So the exposure GT-570 declared closed for "anyone installing `latest`" is not closed for this dependency. Stated precisely, because the stronger claim is unverified here: what is measured is the dates and the resolution, not the contents — this row does not assert that `sdk@1.1.0` carries a specific vulnerability. Found while executing [`GT-624`](./gap-reference-catalog.md#gt-624)'s deprecation criterion, which is what exposed the range: deprecating `sdk@1.1.0` would warn on every fresh install of the current line with no 1.x successor to offer. **CLOSED 2026-07-30 — 1.2.2 is published, and the security framing of this row was WRONG.** A clean install of `@beyondnet/evolith-cli@latest` now resolves `@beyondnet/evolith-sdk@2.0.0` with the binary booting. But verifying criterion 2 refuted the premise: **none of the seven security-wave commits touches `src/packages/sdk-client` — zero files** — and the wave's own release commit names the three packages it affected (mcp, cli, agent-runtime). The SDK was republished alongside them, not fixed. What was real is a stale MAJOR pinned by a caret under 1.x plus a lockfile that nested the registry tarball where it shadowed the workspace link — the `GT-625` class, not a security exposure. The hedge written in when the row was opened is what made the correction cheap. | | | `Infra` | Cross | P1 | S | `DONE` | -| [`GT-633`](./gap-reference-catalog.md#gt-633) | **The guard compared the snapshot against six numbers copied out of the snapshot, so it could only ever pass — and the file it was failing to guard is stamped onto 388 rows of a larger artifact.** `native-evaluability-snapshot.json` declared in its own header that it was "a CAPTURED SNAPSHOT, not the source of truth". **No capture script existed.** It was hand-maintained and it drifted: it pinned `documentation-only: 129` while Core pinned 136, and went on calling rules `unimplemented-native` after they had handlers. Its guard in `iso-5055-mapping.test.mjs` asserted the snapshot's six class counts against six numbers typed into the test — the same six literals the snapshot already contained. **Expected value and actual value were copies of each other**, so the assertion held for as long as the file was unchanged, whatever Core actually pinned; it reported an agreement it never checked, which is worse than no guard, because the row it protects reads as verified. **The drift does not stay where it starts.** `build-iso-5055-mapping.mjs` stamps `nativeEvaluability` onto EVERY row of the ISO/IEC 5055 mapping from that file — 388 rows after the recapture, 381 before — so one stale class is laundered into a derived artifact five times the size of its input, and overstates the handler backlog that is [`GT-598`](./gap-reference-catalog.md#gt-598)'s entire deliverable. The recapture→rebuild order was written down nowhere, and the mapping's own guard ran in **no workflow at all**. **FIXED 2026-07-29:** a capture script that runs Core's REAL triage through `ts-node` instead of reimplementing the classification, the measurement extracted out of the jest spec into `test/rule-corpus-triage.ts` so a script can reach it — that unreachability is why the file was maintained by hand — snapshot-vs-fresh-triage assertions in both directions, a documentation-job guard that reads Core's pinned counts OUT of the spec and **throws** when the literal is missing or reshaped, and both steps declared as links in the [`GT-630`](./gap-reference-catalog.md#gt-630) derived-artifact chain. **Recaptured on 2026-07-29 after merging the fix into the current line, and the drift had grown while it was in flight:** `native-handler` 139 → 151, `documentation-only` 129 → 136, the handler backlog 60 → **48**, corpus 379 → 386, mapping 381 → 388 rows. Twelve of those handlers were closed by [`GT-595`](./gap-reference-catalog.md#gt-595) and [`GT-632`](./gap-reference-catalog.md#gt-632) while the hand-maintained snapshot went on reporting them as backlog — the defect demonstrating itself one more time, on numbers nobody typed twice. | | | `Governance` | Cross | P1 | S | `DONE` | | [`GT-632`](./gap-reference-catalog.md#gt-632) | **The MCP denied every tool in production from a path typo, and the evidence that it worked was untracked local state.** `abac-evaluator.ts:322` resolved the compiled policy at `/sdk/cli/rulesets/opa/policy.wasm` — the PRE-`src/`-refactor path. The file lives at `src/sdk/cli/…`. When the stat fails the evaluator denies fail-closed in production, so on a clean checkout with `NODE_ENV=production` the compiled policy never loads and EVERY MCP tool is refused. It looked green because `policy.wasm` is gitignored: a stale file left at the old path by an earlier era made the load succeed locally, and the test that covers it passed against an artifact no fresh checkout would have. Same shape as `GT-625` — a green that depended on untracked state — but on the authorization path. Found on 2026-07-29 while investigating why `abac-rego-parity.spec.ts` had gone red; the rego itself was correct (`opa eval` returns `allow: true` for an architect in production). **`40-validate-path-literals` cannot see this class:** it scans string literals, and this is a `path.join(corePath, 'sdk', 'cli', …)` construction. Twelve such joins survive the refactor across `src/**` — the other eleven are in `opa-input-builder.ts` and `cli-release-rule.handler.ts`, where a wrong path produces a false NEGATIVE in an evaluator rather than a denial, which is quieter and no less wrong. **FIXED for the authorization path on 2026-07-29:** the evaluator now tries both locations, newest first — not sloppiness, but what keeps an already-deployed image working while the path is corrected. Reverting the fix turns `abac-rego-parity.spec.ts` red (1 of 11); with it, mcp-server is 434/434. The other eleven joins and the guard blind spot remain. **Criterion 2 CLOSED 2026-07-29 — and two of the eleven were wrong in a way the prefix alone would not have fixed.** Eight were plain survivors of the `src/` move and took the prefix. The other three were worse: two pointed at `sdk/cli/src/core/mcp/server.ts`, a file that left the CLI entirely when mcp-server became its own package (now `src/packages/mcp-server/src/mcp/mcp-server.service.ts`); and one asserted a per-package `package-lock.json`, which npm WORKSPACES forbids — there is exactly one lockfile, at the root, so that check could only ever be false. A rule nobody can satisfy is not a stricter rule. **The specs and parity fixtures encoded the old layout too** (`cli-release-readiness.fixture.json`, `mcp.fixture.json`), which is why they had stayed green over paths that do not exist: the fixture and the code agreed with each other and neither agreed with the repository. Both are corrected. core-domain 1367/1367, `28-native-evaluator-parity` exit 0. **Criterion 3 CLOSED 2026-07-29 — the guard's first run was mostly wrong, which is the finding.** `47-validate-joined-paths` reported 19 broken paths; only 3 were defects. The other 16 would each have been made WORSE by a fix: six had base `root`, a PARAMETER naming the customer workspace under evaluation, so correcting them would break evaluation for every satellite; nine were members of FALLBACK CHAINS that are correct when one of four layouts resolves, and the natural way to silence those is to delete the fallbacks, breaking deployed images; one was a PROHIBITION whose absence IS the passing state, so correcting it would have inverted the rule. Chains are now detected structurally (array siblings, or the arms of a ternary); prohibitions cannot be, so they carry a written reason. The three real defects: `opa-evaluator.ts` resolved `policy.wasm` and the input schemas without the `src/` prefix, same fail-closed class as the P0 above; `satellite-upgrade-diff.ts` probed `rulesets` behind an early return, reporting zero ruleset changes on every satellite upgrade; `artifact-path-resolver.ts` named an `adr-matrix.json` present in no layout. 11 negative fixtures, both anti-vacuous floors, and `43-validate-guard-negative-fixtures` OBSERVED it red across 36 guards. A known leniency (two joins as sibling arguments) is recorded in its header rather than hidden. | | | `MCP Server` | Cross | P0 | M | `DONE` | | [`GT-631`](./gap-reference-catalog.md#gt-631) | **Carved out of [`GT-573`](./gap-reference-catalog.md#gt-573) so its cross-repo half is tracked rather than absorbed into a closure.** The Core half of GT-573's third criterion is done: `evaluation-context` and `evaluation-result` are pinned in `MACHINE_CONTRACT_SET` alongside `gate-evidence` and `output-envelope`, so a change to either shape now fails `10-validate-contract-conformance` instead of silently reaching consumers. What remains is in another repository and cannot be done from here: the **Tracker** must re-pin those two schemas in its own `contracts/evolith-core-contracts.json` with their sha256, bind the three published fixtures (`EVALUATION_RESULT_PASS/FAIL/OPA_GATE_FAIL`) in a DTO binding test, drop or document `resolvedTopology` (which the canonical result never carried), rename its gate `phase` to `phaseId`, and delete its fall-through to `SKIPPED` for a payload carrying a non-blank `overallVerdict`. Until it does, a Core-side shape change is caught here and still surprises the consumer. | | | `Evolith Tracker` | Cross | P1 | S | `PENDING` | | [`GT-630`](./gap-reference-catalog.md#gt-630) | **Derived artifacts have a dependency ORDER and nothing enforced it — it cost three red required checks on 2026-07-28 alone.** `generate-executive-summary.mjs` reads `maturity-reconciliation.json`, which is itself derived from the gap board. Generate the summary BEFORE reconciling and it captures a value the reconciler is about to move. Each artifact's own `--check` then passes at that moment — the summary genuinely matches what was just written — so `ci-runner governance` goes green locally and `Validate documentation` goes red on the runner, where the steps run in the declared order. That is not a stale-artifact bug, which the individual `--check` modes already catch; it is an ORDER bug, invisible to any check that looks at one artifact at a time. The correct sequence (reconcile → suite → generate → validate) was written down nowhere in the repository, which is why the same mistake was made three times in one session by someone who had already been bitten by it twice. **CLOSED on registration.** The chain is now DATA in `46-validate-derived-artifact-order.mjs` — producer, what it consumes, what it writes — walked in dependency order, wired into `Validate documentation`. It reports the FIRST stale link with the command that fixes it and the inputs that must be current before it, because an artifact built on stale input is not independently wrong. Writing the tests bounded what the fixed-point pass is worth, and the bound is recorded in the guard: with deterministic generators, currency-in-order already subsumes it; it earns its place on a generator whose output does not depend only on its declared inputs, which is the case the self-test exercises. 8 self-tests, four of them anti-vacuous. Registered in `guard-classification.mjs`; 58 guards classified, 35 observed failing. | | | `Governance` | Cross | P2 | S | `DONE` | diff --git a/src/packages/core-domain/src/application/validators/rule-corpus-triage.spec.ts b/src/packages/core-domain/src/application/validators/rule-corpus-triage.spec.ts index a82d113d..e9e30306 100644 --- a/src/packages/core-domain/src/application/validators/rule-corpus-triage.spec.ts +++ b/src/packages/core-domain/src/application/validators/rule-corpus-triage.spec.ts @@ -35,10 +35,10 @@ import { REPO_ROOT, RULESETS_ROOT, triageCorpus, renderSnapshot } from '../../.. // meant jest was the only thing that could run them — and that is why // `native-evaluability-snapshot.json` was maintained by hand and drifted. // -// GT-633 RECONCILIATION: the renderer in particular had two implementations, one +// GT-640 (registered as GT-633) RECONCILIATION: the renderer in particular had two implementations, one // here and one in `capture-native-evaluability-snapshot.mjs`, written in parallel // by sessions that could not see each other. Two generators for one artifact is -// the defect GT-633 exists to remove, one level up: whichever ran last would win +// the defect GT-640 exists to remove, one level up: whichever ran last would win // and the other's `--check` would go red for no visible reason. There is now ONE // renderer, and this suite is its PIN rather than a second copy of it. const TRIAGE = triageCorpus(); @@ -153,7 +153,7 @@ describe('GT-595 · the published breakdown, with its denominator', () => { // Asserted against the SINGLE declaration above, not against a second inline // copy of the same six numbers. There used to be two — this constant, read by // the dependency-free guard in `src/rulesets/standards`, and a literal here — - // which is GT-633's own defect in miniature: two copies of one fact, and + // which is GT-640's own defect in miniature: two copies of one fact, and // editing either leaves the other silently disagreeing. expect(SUMMARY.byClass).toEqual(PINNED_CLASS_COUNTS); }); diff --git a/src/packages/core-domain/test/rule-corpus-triage.ts b/src/packages/core-domain/test/rule-corpus-triage.ts index 3b6522e8..73727e28 100644 --- a/src/packages/core-domain/test/rule-corpus-triage.ts +++ b/src/packages/core-domain/test/rule-corpus-triage.ts @@ -168,16 +168,16 @@ export function triageCorpus(rulesetsRoot: string = RULESETS_ROOT): CorpusTriage // --------------------------------------------------------------------------- /** - * GT-633 reconciliation: this function had TWO implementations. + * GT-640 (registered as GT-633) reconciliation: this function had TWO implementations. * - * The fix for GT-633 was written twice, in parallel, by sessions that could not + * The fix for GT-640 was written twice, in parallel, by sessions that could not * see each other. One shipped a standalone capture script (`capture-native- * evaluability-snapshot.mjs`, reachable by the derived-artifact chain guard and * by the documentation job); the other rendered the document inside * `rule-corpus-triage.spec.ts`, where jest can pin it byte-for-byte. Both were * right about their half, and both recaptured the same numbers. * - * But two generators for one artifact is EXACTLY the defect GT-633 exists to + * But two generators for one artifact is EXACTLY the defect GT-640 exists to * remove, reproduced one level up: whichever ran last would win, and the other's * `--check` would go red for no reason a reader could see. * diff --git a/src/rulesets/standards/capture-native-evaluability-snapshot.mjs b/src/rulesets/standards/capture-native-evaluability-snapshot.mjs index c1386f19..d3687132 100644 --- a/src/rulesets/standards/capture-native-evaluability-snapshot.mjs +++ b/src/rulesets/standards/capture-native-evaluability-snapshot.mjs @@ -119,9 +119,9 @@ function resolveTsNodeRegister() { * them is a process, not a module resolution problem worth solving. */ function runTriage(capturedOn) { - // GT-633 RECONCILIATION: this script used to BUILD the document itself, while + // GT-640 (registered as GT-633) RECONCILIATION: this script used to BUILD the document itself, while // `rule-corpus-triage.spec.ts` built a second copy of it. Two generators for one - // artifact is the defect GT-633 exists to remove, one level up — whichever ran + // artifact is the defect GT-640 exists to remove, one level up — whichever ran // last would win and the other's `--check` would go red for no visible reason. // // The renderer now lives once, in `test/rule-corpus-triage.ts`, and this script