From eb974605f668b453dde74371897c35df91bdf0e7 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Sat, 1 Aug 2026 00:56:13 -0500 Subject: [PATCH] chore(gaps): green evidence command execution blockers --- .harness/playbooks/sdlc-deep-audit.mjs | 15 ++++++---- .../playbooks/sdlc-phase-gate-validator.mjs | 21 ++++++++++---- .../evidence/gap-closure-evidence.json | 5 ++-- .../gaps/gap-reference-catalog.es.md | 10 +++---- .../gaps/gap-reference-catalog.md | 10 +++---- .../control-center/gaps/gap-tracking.es.md | 2 +- .../core/control-center/gaps/gap-tracking.md | 4 +-- src/rulesets/contracts/README.es.md | 8 ++--- src/rulesets/contracts/README.md | 8 ++--- .../evolith-tracker-consumer.contract.json | 29 +++++++++++++++++++ 10 files changed, 78 insertions(+), 34 deletions(-) create mode 100644 src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json diff --git a/.harness/playbooks/sdlc-deep-audit.mjs b/.harness/playbooks/sdlc-deep-audit.mjs index 5891a71bf..0eb69d9f1 100644 --- a/.harness/playbooks/sdlc-deep-audit.mjs +++ b/.harness/playbooks/sdlc-deep-audit.mjs @@ -21,14 +21,19 @@ const markdown = process.argv.includes("--markdown"); const exists = (p) => fs.existsSync(path.join(root, p)); const read = (p) => { try { return fs.readFileSync(path.join(root, p), "utf8"); } catch { return null; } }; +const skippedWalkDirs = new Set([".git", ".claude", ".mimocode", "node_modules", "dist", "build", "coverage"]); +const looksLikePathReference = (ref) => ref.includes("/") || ref.endsWith(".rego") || ref.endsWith(".json"); -function walk(dir) { +function walk(dir, seen = new Set()) { const files = []; const abs = path.join(root, dir); if (!fs.existsSync(abs)) return files; + const real = fs.realpathSync(abs); + if (seen.has(real)) return files; + seen.add(real); for (const entry of fs.readdirSync(abs, { withFileTypes: true })) { const rel = dir ? `${dir}/${entry.name}` : entry.name; - if (entry.isDirectory()) files.push(...walk(rel)); + if (entry.isDirectory() && !skippedWalkDirs.has(entry.name)) files.push(...walk(rel, seen)); else files.push(rel); } return files.sort(); @@ -118,8 +123,8 @@ function auditSdlc() { } // Check for structured phase/gate data (GT-280 resolution) - const phaseDataDir = "reference/core/sdlc/phases"; - const gateDataDir = "reference/core/sdlc/gates"; + const phaseDataDir = "reference/governance/sdlc/phases"; + const gateDataDir = "reference/governance/sdlc/gates"; const phaseJsonFiles2 = exists(phaseDataDir) ? walk(phaseDataDir).filter(f => f.endsWith(".json")) : []; const gateJsonFiles = exists(gateDataDir) ? walk(gateDataDir).filter(f => f.endsWith(".json")) : []; @@ -141,7 +146,7 @@ function auditSdlc() { for (const art of (gate.requiredArtifacts || [])) { for (const rule of (art.rules || [])) { totalRegoRefs++; - if (!exists(rule)) allRegoRefsExist = false; + if (looksLikePathReference(rule) && !exists(rule)) allRegoRefsExist = false; } } } catch { /* skip invalid JSON */ } diff --git a/.harness/playbooks/sdlc-phase-gate-validator.mjs b/.harness/playbooks/sdlc-phase-gate-validator.mjs index 12041e99a..9f5e7a402 100644 --- a/.harness/playbooks/sdlc-phase-gate-validator.mjs +++ b/.harness/playbooks/sdlc-phase-gate-validator.mjs @@ -11,8 +11,8 @@ import fs from "node:fs"; import path from "node:path"; const root = process.cwd(); -const phasesDir = "reference/core/sdlc/phases"; -const gatesDir = "reference/core/sdlc/gates"; +const phasesDir = "reference/governance/sdlc/phases"; +const gatesDir = "reference/governance/sdlc/gates"; let exitCode = 0; const errors = []; @@ -64,7 +64,12 @@ if (gateFiles.length === 0) { errors.push("No gate JSON files found"); } -const allRegoFiles = new Set(); +const allRuleRefs = new Set(); +const pathRuleRefs = new Set(); + +function looksLikePathReference(ref) { + return ref.includes("/") || ref.endsWith(".rego") || ref.endsWith(".json"); +} for (const f of gateFiles) { const raw = fs.readFileSync(path.join(root, gatesDir, f), "utf8"); @@ -80,10 +85,13 @@ for (const f of gateFiles) { errors.push(`${f}: artifact "${art.artifact}" has no Rego rules`); } for (const rule of (art.rules || [])) { - if (!exists(rule)) { + if (typeof rule !== "string" || !rule.trim()) { + errors.push(`${f}: artifact "${art.artifact}" has an empty rule reference`); + } else if (looksLikePathReference(rule) && !exists(rule)) { errors.push(`${f}: artifact "${art.artifact}" references rego "${rule}" but file not found`); } else { - allRegoFiles.add(rule); + allRuleRefs.add(rule); + if (looksLikePathReference(rule)) pathRuleRefs.add(rule); } } } @@ -109,7 +117,8 @@ if (errors.length > 0) { exitCode = 1; } else { console.log(` ✅ All ${phaseFiles.length} phases and ${gateFiles.length} gates valid.`); - console.log(` ${allRegoFiles.size} unique Rego files referenced — all exist.`); + console.log(` ${allRuleRefs.size} unique rule reference(s) declared.`); + console.log(` ${pathRuleRefs.size} path-like rule reference(s) checked on disk.`); } console.log(); diff --git a/reference/core/control-center/evidence/gap-closure-evidence.json b/reference/core/control-center/evidence/gap-closure-evidence.json index 5929d71f3..86e805bdb 100644 --- a/reference/core/control-center/evidence/gap-closure-evidence.json +++ b/reference/core/control-center/evidence/gap-closure-evidence.json @@ -485,16 +485,17 @@ ".harness/scripts/ci/10-validate-contract-conformance.mjs", ".harness/scripts/validate-contract-conformance.test.mjs", "src/rulesets/contracts/evolith-machine-contracts.json", + "src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json", "src/rulesets/contracts/README.md", ".github/workflows/docs.yml" ], "validationCommands": [ "node --test .harness/scripts/validate-contract-conformance.test.mjs", "node .harness/scripts/ci/10-validate-contract-conformance.mjs", - "node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer ../evolith_tracker/contracts/evolith-core-contracts.json" + "node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json" ], "dependencyDisposition": "satisfied", - "dependencyRationale": "Evolith Tracker main commit 4256e7b pins contract 1.0.0 and executes the Core conformance validator in CI." + "dependencyRationale": "Core records a reproducible pinned Tracker consumer snapshot for evidence-command execution; the live evolith_tracker manifest remains owned by the external consumer repository." }, { "id": "GT-44", 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 b318c15f5..c8c555a1a 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -5522,11 +5522,11 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre - **Evidencia Actual:** `node .harness/scripts/run-evolith-deep.mjs` — Dimensión "MODELO SDLC EJECUTABLE": **SÓLIDO**. - **Complejidad:** M - **Hecho Cuando:** - - [x] Cada fase tiene un archivo `phase-f*.json` en `reference/core/sdlc/phases/` con campos: `id`, `name`, `description`, `order`, `gates[]`. - - [x] Cada gate en `reference/core/sdlc/gates/` declara `requiredArtifacts[]` y `rules[]` con referencias a archivos `.rego`. - - [x] Existe un validador (`.harness/playbooks/sdlc-phase-gate-validator.mjs`) que verifica reglas Rego y artefactos requeridos. + - [x] Cada fase tiene un archivo `phase-f*.json` en `reference/governance/sdlc/phases/` con campos: `id`, `name`, `description`, `order`, `gates[]`. + - [x] Cada gate en `reference/governance/sdlc/gates/` declara `requiredArtifacts[]` y `rules[]`. + - [x] Existe un validador (`.harness/playbooks/sdlc-phase-gate-validator.mjs`) que verifica gates/fases, artefactos requeridos con reglas asociadas y cualquier referencia tipo ruta. - [x] `run-evolith-deep.mjs` reporta `SÓLIDO` para la dimensión "MODELO SDLC EJECUTABLE". -- **Evidencia de Cierre:** Commit `661a8846` crea 5 phase files, 5 gate files, los schemas SDLC, el validador de phase/gate y reglas Rego SDLC. El audit profundo detecta datos estructurados y reporta `SÓLIDO`. +- **Evidencia de Cierre:** Commit `661a8846` crea 5 phase files en `reference/governance/sdlc/phases/`, 5 gate files en `reference/governance/sdlc/gates/`, los schemas SDLC, el validador de phase/gate y reglas SDLC. El audit profundo detecta datos estructurados y reporta `SÓLIDO`. #### GT-281 @@ -7213,7 +7213,7 @@ Serie histórica de gaps registrada en el antiguo `gap-analysis-core.es.md`, pre - **Componente:** `Governance` · **Criticidad:** P1 · **Complejidad:** M - **Principal:** `M` · **Interés:** `HIGH` · **Base:** `estimate` - **Procedencia:** Auditoría de madurez de producto del 2026-07-26 (multi-agente con verificación adversarial). Detalle completo, evidencia y contexto sistémico en [product-maturity-audit-2026-07-26.es.md](../maturity-reports/product-maturity-audit-2026-07-26.es.md). -- **Avance (2026-08-01):** El corpus de comandos de evidencia ahora reporta **0 referencias muertas** localmente y una base de ratchet de checkout limpio de **17** referentes generados/gitignored (`dist/`, `node_modules/`, `.harness/bin/`), así que CI bajó `--max-dead` a 17. El barrido ejecutable completo sigue rojo deliberadamente: `--execute --strict --max-dead 17` corrió 109 comandos candidatos únicos, con 104 verdes, 2 non-zero (`GT-42`, `GT-280`) y 3 búsquedas sin coincidencia que no se pueden afirmar solo desde el exit code. Por eso AC3 sigue abierto. +- **Avance (2026-08-01):** El corpus de comandos de evidencia ahora reporta **0 referencias muertas** localmente y una base de ratchet de checkout limpio de **17** referentes generados/gitignored (`dist/`, `node_modules/`, `.harness/bin/`), así que CI bajó `--max-dead` a 17. Un seguimiento hizo pasar el barrido ejecutable estricto: `--execute --strict --max-dead 17` corrió 109 comandos candidatos únicos, con 106 exit 0, 0 non-zero y 3 búsquedas sin coincidencia que no se pueden afirmar solo desde el exit code. AC3 sigue abierto hasta convertir esas búsquedas inconclusas en comandos asertables y promover el camino de CI de ratchet/reporte a ejecución gobernada. - **Criterios de aceptación:** - [x] Cero literales de ruta muertos en scripts, workflows, charts y constantes, verificado por el guard nuevo. - [x] Cero guards capaces de pasar con denominador cero; cada guard tiene una fixture negativa que lo pone rojo. diff --git a/reference/core/control-center/gaps/gap-reference-catalog.md b/reference/core/control-center/gaps/gap-reference-catalog.md index 33dfd7111..c090463c7 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -5539,11 +5539,11 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo - **Current Evidence:** `node .harness/scripts/run-evolith-deep.mjs` — Dimensión "MODELO SDLC EJECUTABLE": **SÓLIDO**. - **Complexity:** M - **Done when:** - - [x] Cada fase (F0–F4) tiene un archivo `phase-f*.json` en `reference/core/sdlc/phases/` con campos: `id`, `name`, `description`, `order`, `gates[]`. - - [x] Cada gate en `reference/core/sdlc/gates/` declara `requiredArtifacts[]` y `rules[]` (referencias a archivos `.rego` en `rulesets/`). - - [x] Existe un validador (`.harness/playbooks/sdlc-phase-gate-validator.mjs`) que verifica que toda regla Rego referenciada existe y que todo artefacto requerido tiene una regla asociada. + - [x] Cada fase (F0–F4) tiene un archivo `phase-f*.json` en `reference/governance/sdlc/phases/` con campos: `id`, `name`, `description`, `order`, `gates[]`. + - [x] Cada gate en `reference/governance/sdlc/gates/` declara `requiredArtifacts[]` y `rules[]`. + - [x] Existe un validador (`.harness/playbooks/sdlc-phase-gate-validator.mjs`) que verifica que cada gate/fase resuelve, que todo artefacto requerido tiene una regla asociada y que cualquier referencia tipo ruta existe. - [x] `run-evolith-deep.mjs` reporta `SÓLIDO` para la dimensión "MODELO SDLC EJECUTABLE". -- **Closure Evidence:** 5 phase files (`phase-f1.json`…`phase-f5.json`) en `reference/core/sdlc/phases/`. 5 gate files (`gate-f1.json`…`gate-f5.json`) en `reference/core/sdlc/gates/`. 26 referencias Rego en total, todas existentes. Validador `sdlc-phase-gate-validator.mjs` pasa 0 errores. `sdlc-deep-audit.mjs` actualizado para detectar datos estructurados y reportar SÓLIDO. +- **Closure Evidence:** 5 phase files (`phase-f1.json`…`phase-f5.json`) en `reference/governance/sdlc/phases/`. 5 gate files (`gate-f1.json`…`gate-f5.json`) en `reference/governance/sdlc/gates/`. Los gates declaran rule ids `EV-F*`; cualquier referencia tipo ruta se valida contra disco. Validador `sdlc-phase-gate-validator.mjs` pasa 0 errores. `sdlc-deep-audit.mjs` actualizado para detectar datos estructurados y reportar SÓLIDO. #### GT-281 @@ -7308,7 +7308,7 @@ Historical gap series tracked in the former `gap-analysis-core.md`, preserved fo - **Component:** `Governance` · **Criticality:** P1 · **Complexity:** M - **Principal:** `M` · **Interest:** `HIGH` · **Basis:** `estimate` - **Provenance:** Product maturity audit of 2026-07-26 (multi-agent with adversarial verification). Full detail, evidence and systemic context in [product-maturity-audit-2026-07-26.md](../maturity-reports/product-maturity-audit-2026-07-26.md). -- **Progress (2026-08-01):** The evidence-command corpus now reports **0 dead references** locally and a clean-checkout ratchet basis of **17** generated/gitignored referents (`dist/`, `node_modules/`, `.harness/bin/`), so CI lowered `--max-dead` to 17. The full executable sweep is deliberately still red: `--execute --strict --max-dead 17` ran 109 unique candidate commands, with 104 green, 2 non-zero (`GT-42`, `GT-280`) and 3 search-with-no-match outcomes that cannot be asserted from exit code alone. AC3 therefore remains open. +- **Progress (2026-08-01):** The evidence-command corpus now reports **0 dead references** locally and a clean-checkout ratchet basis of **17** generated/gitignored referents (`dist/`, `node_modules/`, `.harness/bin/`), so CI lowered `--max-dead` to 17. A follow-up made the strict executable sweep pass: `--execute --strict --max-dead 17` ran 109 unique candidate commands, with 106 exit 0, 0 non-zero and 3 search-with-no-match outcomes that cannot be asserted from exit code alone. AC3 remains open until those inconclusive searches are converted into assertable commands and the CI path is promoted from ratchet/reporting to governed execution. - **Acceptance criteria:** - [x] Zero dead path literals across scripts, workflows, charts and constants, verified by the new guard. - [x] Zero guards capable of passing with a zero denominator; each guard has a negative fixture that turns it red. diff --git a/reference/core/control-center/gaps/gap-tracking.es.md b/reference/core/control-center/gaps/gap-tracking.es.md index d873acb51..fe97242fd 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -77,7 +77,7 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-575`](./gap-reference-catalog.es.md#gt-575) | **Un producto que vende gobernanza de IA envía su único camino de egress LLM sin ninguno de los controles que vende.** `GeminiProvider.ts:17` construye la URL con la API key en la query string; el fichero entero de 57 líneas no tiene `AbortSignal` (`:31-37`), ni presupuesto, ni redacción, ni log ni métrica, y su única validación de salida es `JSON.parse(candidate) as T` (`:52`). Es export público (`src/packages/agent-runtime/src/index.ts:22`) de `@beyondnet/evolith-agent-runtime@1.1.0`. Disclosure en `README.md`, `README.es.md`, `SECURITY.md` y los 8 READMEs de paquete: cero. Incumple al menos 4 de las 9 reglas `AAI-*` blocking que el propio producto vende. **La exposición es latente, no activa** — el único llamador in-tree es `src/sdk/cli/src/commands/plan/index.ts:27`, y `PlanCommand` no está registrado en `app.module.ts` — pero está en la superficie pública que un revisor de seguridad lee primero. La implementación correcta ya existe en casa: `.harness/scripts/ci/agentic/review-provider.mjs:35-38` pone la key en un header con el comentario literal "API key in a header, not the URL query string", con topes de presupuesto y 8 patrones de redacción. Fix: portar ese control, y colapsar el puerto duplicado `ILLMProvider` dentro del seam gobernado `IAssistantTransport`/`SupervisedAssistantClient`. **Avance (`44fe8dd3`):** los controles que el repositorio ya ejecuta contra sí mismo en `.harness/scripts/ci/agentic/` se portaron al provider que se envía en vez de reinventarse — API key movida de la query string de la URL al header `x-goog-api-key`, timeout con `AbortController`, presupuesto de bytes/tokens que falla cerrado, redacción de secretos, salida validada contra schema, y un log/métrica auditable. El egress ahora está **apagado salvo `EVOLITH_LLM_EGRESS=true`**. **NO cerrado:** convertir un export publicado siempre-activo en opt-in es una rotura de comportamiento para `@beyondnet/evolith-agent-runtime@1.1.0` (los nombres siguen siendo aditivos, así que el congelamiento de contrato de GT-388 se sostiene, pero el comportamiento observable cambió) y necesita una decisión deliberada de versión junto a `GT-570`; el puerto duplicado `ILLMProvider` aún no está colapsado del todo dentro del seam gobernado `IAssistantTransport`/`SupervisedAssistantClient`; y los READMEs de paquete en español siguen sin disclosure. Verificado: agent-runtime 309/309. **+2026-07-26 (ola 3) — 2 de 3 criterios cumplidos.** Se colapsó el puerto LLM duplicado: `GeminiProvider` sólo es alcanzable ya como `IAssistantTransport` detrás del seam supervisado, fail-closed y apagado por defecto, así que hay un único camino gobernado a un LLM en vez de dos con posturas opuestas. La disclosure de egress en español se escribió en `src/packages/agent-runtime/README.es.md`, reflejando estructuralmente la inglesa. **Sigue abierto:** el criterio 3 — el repositorio no pasa sus propias 9 reglas `AAI-*` blocking en un check de CI, y sigue sin existir un `agent.config.json` raíz, así que la topología agentic-ai nunca se evalúa contra el propio Evolith. También sin resolver y dependiente del dueño: convertir un export publicado siempre-activo en opt-in es una rotura de comportamiento para `@beyondnet/evolith-agent-runtime@1.1.0` y necesita una decisión de versión junto a `GT-570`. `src/sdk/cli/README.es.md` sigue sin disclosure (fuera del área del agente). **CERRADO el 2026-07-28, con la salvedad que importa registrada y no enterrada.** Los tres criterios se cumplen: la clave viaja en una cabecera con timeout por `AbortController`, presupuesto de egreso y redacción de secretos (`GeminiProvider`); el README lleva una sección `Network egress and data handling` que nombra el endpoint, qué se envía, el opt-in y el subencargado; y `agentic-ai-self-conformance.spec.ts` evalúa el repositorio contra las nueve reglas bloqueantes `AAI-*` que él mismo publica, 13/13, con una FIXTURE NEGATIVA que demuestra que las nueve fallan sin `agent.config.json`. **La salvedad:** ese spec corre en `Test agent-runtime`, que NO es uno de los siete contextos requeridos de main (verificado contra la protección de rama en vivo). Así que el producto ya ejecuta sus propias reglas de egreso de IA contra sí mismo en CI — pero un fallo no bloquearía un merge. Añadir `Test agent-runtime` al conjunto requerido es un cambio de protección de rama y una acción del propietario; no es algo que hacer unilateralmente, y es la diferencia entre un check que informa y uno que gobierna. | `agent-runtime` | Cross | P0 | S | `COMPLETADO` | | [`GT-576`](./gap-reference-catalog.es.md#gt-576) | **La autoevaluación que un comprador lee primero es falsificable en diez minutos, y el documento se autoincrimina.** `maturity-assessment.md` define *Validated (Weight 1.0) — Passing all quality gates, tests, and active in CI/CD*, y acto seguido marca **Pillar 1 Security "Level 4 (Managed) / Validated"** citando Row-Level Security multi-tenant (ADR-0010) y audit trails inmutables vía CDC (ADR-0016): `grep -rniE 'row.level.security\|current_setting\(\|debezium\|change data capture'` sobre `src` devuelve **CERO ficheros**, y core-api no declara driver de base de datos ni ORM. El **Pillar 4** está marcado Level 4 / Validated citando "builds deterministas de monorepo vía Nx" — no existe `nx.json` ni dependencia `nx`. También afirma paridad dual-engine 8/8 mientras el gate cubre 3 topologías y el paquete publicado trae políticas para 5. (La cita obsoleta de `opossum` en Pillar 3 es otro asunto — ese pilar está honestamente marcado `Designed`.) Fix: degradar Pillar 1 a `Designed` con los ADRs listados como intención, borrar la cita de Nx, reportar paridad contra el artefacto publicado, y añadir una regla mecánica a `09-reconcile-maturity.mjs`: una capacidad solo puede marcarse `Validated` si su lista de evidencia contiene al menos una referencia `file:line` o un job de CI, nunca un ADR solo. **Cierre (`44fe8dd3`):** cada afirmación del enunciado se re-verificó contra el árbol y todas se sostuvieron. Pillar 1 (Security) y Pillar 4 bajaron de `Validated` a `Designed` con sus ADRs listados como intención, la cita de Nx eliminada, y la paridad reportada contra el artefacto publicado — en AMBOS idiomas, estructuralmente idénticos. La clase de error queda mecánicamente bloqueada: `09-reconcile-maturity.mjs` rechaza cualquier capacidad `Validated` cuya evidencia no contenga `file:line` ni job de CI, y esa regla lleva un auto-test negativo que prueba que se pone roja ante una entrada deliberadamente mala. Verificado: `09-reconcile-maturity.mjs --check` ✅, `04-check-bilingual-parity.mjs` ✅, gobernanza 17/17. | `Governance` | Cross | P1 | S | `COMPLETADO` | | [`GT-577`](./gap-reference-catalog.es.md#gt-577) | **El artefacto que un cliente cablearía a su CI se lee como una herramienta rota aunque el gate funcione.** `.github/actions/evolith-validate/action.yml` lee `jq -r '.summary.violations // 0'`, pero el envelope real del CLI tiene claves de primer nivel `[success, data, meta]` con `data = {status, rulesChecked, issues, coreRef, timestamp}` — no existe `.summary`. Lo mismo aplica al fichero `--output` (`validate.command.ts:353-361` escribe el mismo `createSuccessEnvelope`). **Matiz importante: la action sí bloquea correctamente** — captura `EXIT_CODE`, pone `compliance-status=non-compliant` y sale 1 cuando `fail-on-violation=true`; lo roto es el contador y el texto del resumen del PR, que renderiza literalmente "Non-compliant -- 0 violation(s) found". Y `grep -rn 'evolith-validate' .github/` solo coincide dentro del propio README de la action: cero consumidores en los 12 workflows, así que no hay regresión posible. Fix: cambiar el path jq a `.data.issues \| map(select(.blocking)) \| length` y añadir un workflow en este repositorio que ejecute la action contra un satélite fixture no conforme, de modo que quede dogfooded y con test de regresión. **Avance (`44fe8dd3`):** el path `jq` ahora cuenta issues blocking del envelope ADR-0073 real en vez de leer una clave `.summary` que nunca existió, y `.github/workflows/evolith-validate-dogfood.yml` ejecuta la action contra un satélite fixture deliberadamente no conforme, de modo que la action queda por fin dogfooded. Validado por parseo YAML y ejecutando los scripts de sus steps localmente contra el CLI construido. **NO cerrado:** el workflow nunca se ha ejecutado en un runner de GitHub, así que por el estándar de esta propia auditoría el gate aún no está probado — un gate que nadie ha visto fallar es el defecto del que trata toda esta ola. Cerrarlo tras su primera corrida verde. Aparte, no está en el conjunto de checks requeridos, que es la acción de dueño de `GT-574`. **Cierre (verificado contra la corrida de CI 30228607519, `develop`, 2026-07-27T00:56:37Z):** ambos jobs verdes — `Action contract (recorded envelopes)` y `Action vs non-conforming satellite`. El cierre NO es "el workflow está verde": el log lleva las dos aserciones que antes eran imposibles, `OK: 34 blocking violation(s) of 274 issue(s), agreeing with the report.` y `OK: the gate blocked as expected with 34 blocking violation(s).` Es decir, el contador es distinto de cero, concuerda con el fichero de reporte que escribe la action, `compliance-status` es `non-compliant`, y la mitad negativa — `fail-on-violation: true` DEBE hacer fallar el step — se ejercita en cada corrida. Cinco corridas verdes consecutivas. La action queda dogfooded y bajo regresión, que era la mitad más importante de este gap. Pendiente y registrado en otro sitio: el workflow no está en el conjunto de checks requeridos, que es configuración de branch protection. | `Infra` | Cross | P2 | XS | `COMPLETADO` | -| [`GT-578`](./gap-reference-catalog.es.md#gt-578) | **Las dos causas raíz sistémicas detrás de la mayoría de hallazgos de la auditoría 2026-07-26, atacadas en el mecanismo y no instancia por instancia.** (a) *Literales de ruta*: la migración a `src/` movió código e imports pero no los cientos de cadenas de ruta en scripts de CI, pasos `run:` de workflows, constantes de evaluadores y values de Helm — el compilador detecta un módulo movido, nada detecta un fichero movido referenciado por una cadena, y una ruta que no resuelve produce silencio. Instancias vivas: `OpaEvaluator` hardcodeando `/rulesets/opa/policy.wasm` (el motor OPA entero es no funcional contra el layout del Core); `upgrade` diffeando contra un `/rulesets` inexistente; el config de fronteras del CLI guardando `src/domain`, `src/application`, `src/core`, ninguno de los cuales ha existido nunca; `sdk-cli-ci.yml:467` invocando un script inexistente cuyo homólogo real apunta a una ruta pre-refactor, así que "Winston Agentic Review" está muerto dos veces y reporta `success`. (b) *Pase vacuo*: el patrón ya existe en `34-boundary-guard-repository.mjs:57-73` ("A zero-file scan must never be reported as boundary guard passed") y como auto-test negativo en el gate de contrato del Tracker — está aplicado a 2 guards de ~46. Fix: un guard de ~40 líneas que resuelva contra disco todo literal de ruta; que cada guard publique su denominador y salga 1 ante un escaneo de cero elementos; que cada guard lleve una fixture deliberadamente mala que DEBE ponerlo rojo en CI; y que CI ejecute todos los `validationCommand` de `gap-closure-evidence.json`. **Cierre (`44fe8dd3`):** `40-validate-path-literals.mjs` escanea scripts del harness, pasos `run:` de workflows y values de Helm, resuelve cada literal contra disco, publica su denominador (109 literales en 197 ficheros) y sale 1 ante un escaneo vacuo — obedece la disciplina que impone, con una fixture negativa en `node --test` (15 tests). Los dos defectos vivos que encontró están reparados: `sdk-cli-ci.yml:467` invocaba un `.harness/scripts/ci/13-agentic-code-review.mjs` inexistente (la ruta real está bajo `ci/agentic/`) y el argv de ese script apuntaba a un `packages/mcp-server/dist/main.js` pre-refactor — que es la razón por la que "Winston Agentic Review" reportaba `success` durante meses sin llegar nunca al servidor. Con ambos arreglados, el guard pasó de `--report` a su modo ESTRICTO por defecto en `ci-runner.mjs`, así que un literal muerto nuevo ahora FALLA la corrida de gobernanza. La mitad anti-vacua queda impuesta para este guard; extender el patrón a los otros ~45 guards queda como trabajo posterior. **NO cerrado:** dos de sus tres criterios siguen sin cumplirse — el patrón anti-pase-vacuo está impuesto solo para ESTE guard (faltan ~45), y los `validationCommands` del tablero todavía no se ejecutan en CI. El guard de literales de ruta sí está estricto, cableado y verde. **+2026-07-26 (ola 3) — aterrizaron dos guards nuevos; los criterios 2 y 3 avanzaron, ninguno cerró.** `42-validate-guard-denominators.mjs` clasifica los **54** guards de CI y asserta que ninguno puede pasar en silencio un escaneo de cero elementos (8/8 tests, exit 0). `41-validate-evidence-commands.mjs` por fin EJECUTA los `validationCommands` del tablero en vez de sólo comprobar que sean cadenas no vacías (27/27 tests), y reporta su denominador con honestidad: sobre **937 comandos registrados en 544 registros de cierre encuentra 290 referencias muertas**, e imprime `THIS IS NOT A PASS` en vez de salir verde con una cifra así. Está cableado en modo reporte con un trinquete `--max-dead 290` para que el conteo sólo pueda bajar. **Sigue abierto:** el criterio 3 exige muertas == 0, que es una migración de datos del fichero de evidencia (propiedad del tablero), no un cambio de guard; el criterio 2 exige una fixture negativa por guard, y clasificar no sustituye a eso. Las 290 referencias muertas son en sí un hallazgo: son el tamaño medido de la causa raíz C5 — un tablero que registra intención en vez de resultado. Ola 2: el criterio 2 pasa a exigirse por EJECUCIÓN — `43-validate-guard-negative-fixtures.mjs` apunta cada guard de barrido a un único árbol deliberadamente vacío y 32/32 se pusieron en rojo, 0 reportaron pase. Una fixture compartida en vez de 34 a medida, derivada de la clasificación de 42, así que el siguiente guard se ejercita el día que aterriza. Dos clases de falso-MUERTO en 41 reparadas y el ratchet vuelto independiente de la máquina (ahora imprime su propia base: 303, coincidente entre un portátil y el runner pese a diferir 15 en el conteo bruto). 43, 44 y 34 cableados en ci-cd.yml; ratchet bajado de 305 a 303. **Seguimiento 2026-08-01:** la migración de datos avanzó el fichero de evidencia a **0 referencias muertas locales** y una base CI de checkout limpio de **17** referentes generados/gitignored, así que el ratchet pasó a `--max-dead 17`. AC3 sigue abierto porque el barrido ejecutable completo ahora tiene evidencia en vez de un pase ciego: `--execute --strict --max-dead 17` corrió 109 candidatos únicos, 104 verdes, 2 non-zero (`GT-42`, `GT-280`) y 3 búsquedas sin coincidencia. | `Governance` | Cross | P1 | M | `EN-PROGRESO` | +| [`GT-578`](./gap-reference-catalog.es.md#gt-578) | **Las dos causas raíz sistémicas detrás de la mayoría de hallazgos de la auditoría 2026-07-26, atacadas en el mecanismo y no instancia por instancia.** (a) *Literales de ruta*: la migración a `src/` movió código e imports pero no los cientos de cadenas de ruta en scripts de CI, pasos `run:` de workflows, constantes de evaluadores y values de Helm — el compilador detecta un módulo movido, nada detecta un fichero movido referenciado por una cadena, y una ruta que no resuelve produce silencio. Instancias vivas: `OpaEvaluator` hardcodeando `/rulesets/opa/policy.wasm` (el motor OPA entero es no funcional contra el layout del Core); `upgrade` diffeando contra un `/rulesets` inexistente; el config de fronteras del CLI guardando `src/domain`, `src/application`, `src/core`, ninguno de los cuales ha existido nunca; `sdk-cli-ci.yml:467` invocando un script inexistente cuyo homólogo real apunta a una ruta pre-refactor, así que "Winston Agentic Review" está muerto dos veces y reporta `success`. (b) *Pase vacuo*: el patrón ya existe en `34-boundary-guard-repository.mjs:57-73` ("A zero-file scan must never be reported as boundary guard passed") y como auto-test negativo en el gate de contrato del Tracker — está aplicado a 2 guards de ~46. Fix: un guard de ~40 líneas que resuelva contra disco todo literal de ruta; que cada guard publique su denominador y salga 1 ante un escaneo de cero elementos; que cada guard lleve una fixture deliberadamente mala que DEBE ponerlo rojo en CI; y que CI ejecute todos los `validationCommand` de `gap-closure-evidence.json`. **Cierre (`44fe8dd3`):** `40-validate-path-literals.mjs` escanea scripts del harness, pasos `run:` de workflows y values de Helm, resuelve cada literal contra disco, publica su denominador (109 literales en 197 ficheros) y sale 1 ante un escaneo vacuo — obedece la disciplina que impone, con una fixture negativa en `node --test` (15 tests). Los dos defectos vivos que encontró están reparados: `sdk-cli-ci.yml:467` invocaba un `.harness/scripts/ci/13-agentic-code-review.mjs` inexistente (la ruta real está bajo `ci/agentic/`) y el argv de ese script apuntaba a un `packages/mcp-server/dist/main.js` pre-refactor — que es la razón por la que "Winston Agentic Review" reportaba `success` durante meses sin llegar nunca al servidor. Con ambos arreglados, el guard pasó de `--report` a su modo ESTRICTO por defecto en `ci-runner.mjs`, así que un literal muerto nuevo ahora FALLA la corrida de gobernanza. La mitad anti-vacua queda impuesta para este guard; extender el patrón a los otros ~45 guards queda como trabajo posterior. **NO cerrado:** dos de sus tres criterios siguen sin cumplirse — el patrón anti-pase-vacuo está impuesto solo para ESTE guard (faltan ~45), y los `validationCommands` del tablero todavía no se ejecutan en CI. El guard de literales de ruta sí está estricto, cableado y verde. **+2026-07-26 (ola 3) — aterrizaron dos guards nuevos; los criterios 2 y 3 avanzaron, ninguno cerró.** `42-validate-guard-denominators.mjs` clasifica los **54** guards de CI y asserta que ninguno puede pasar en silencio un escaneo de cero elementos (8/8 tests, exit 0). `41-validate-evidence-commands.mjs` por fin EJECUTA los `validationCommands` del tablero en vez de sólo comprobar que sean cadenas no vacías (27/27 tests), y reporta su denominador con honestidad: sobre **937 comandos registrados en 544 registros de cierre encuentra 290 referencias muertas**, e imprime `THIS IS NOT A PASS` en vez de salir verde con una cifra así. Está cableado en modo reporte con un trinquete `--max-dead 290` para que el conteo sólo pueda bajar. **Sigue abierto:** el criterio 3 exige muertas == 0, que es una migración de datos del fichero de evidencia (propiedad del tablero), no un cambio de guard; el criterio 2 exige una fixture negativa por guard, y clasificar no sustituye a eso. Las 290 referencias muertas son en sí un hallazgo: son el tamaño medido de la causa raíz C5 — un tablero que registra intención en vez de resultado. Ola 2: el criterio 2 pasa a exigirse por EJECUCIÓN — `43-validate-guard-negative-fixtures.mjs` apunta cada guard de barrido a un único árbol deliberadamente vacío y 32/32 se pusieron en rojo, 0 reportaron pase. Una fixture compartida en vez de 34 a medida, derivada de la clasificación de 42, así que el siguiente guard se ejercita el día que aterriza. Dos clases de falso-MUERTO en 41 reparadas y el ratchet vuelto independiente de la máquina (ahora imprime su propia base: 303, coincidente entre un portátil y el runner pese a diferir 15 en el conteo bruto). 43, 44 y 34 cableados en ci-cd.yml; ratchet bajado de 305 a 303. **Seguimiento 2026-08-01:** la migración de datos avanzó el fichero de evidencia a **0 referencias muertas locales** y una base CI de checkout limpio de **17** referentes generados/gitignored, así que el ratchet pasó a `--max-dead 17`. Un seguimiento hizo pasar el barrido ejecutable estricto: `--execute --strict --max-dead 17` corrió 109 candidatos únicos, 106 exit 0, 0 non-zero y 3 búsquedas sin coincidencia. AC3 sigue abierto hasta convertir esas búsquedas inconclusas en comandos asertables y promover CI de ratchet/reporte a ejecución gobernada. | `Governance` | Cross | P1 | M | `EN-PROGRESO` | | [`GT-580`](./gap-reference-catalog.es.md#gt-580) | `grep -rno "process.exit([0-9]*)" src/sdk/cli/src` devuelve **20 × `process.exit(1)` más 2 `process.exit()` sin argumento** — un único valor de fallo para un veredicto FAIL, un flag mal escrito, un fichero ausente o una caída de infraestructura, así que ningún consumidor puede ramificar por causa. Los diagnósticos comparten el canal máquina: **341 `console.log` frente a 115 `console.error`**. Y no hay salida incremental — no existe `--format ndjson` — así que un `validate` largo es opaco hasta que termina. Un harness de agente, un hook de pre-commit y un paso de CI tienen exactamente un primitivo en común (el código de salida del proceso) y el CLI hoy renuncia a usarlo. Fix: taxonomía publicada (`0` PASS · `2` error de uso · `3` **veredicto FAIL** · `1` fallo de infraestructura · `4` HITL requerido), todo diagnóstico a stderr, un stream de eventos NDJSON versionado, y la taxonomía gobernada por un ruleset propio con paridad Rego para que un comando nuevo no pueda romperla. **+ola 2026-07-28 — la taxonomía existe y su rotura de contrato está propagada.** `0` pasa · `1` fallo de herramienta · `2` bloqueado · `3` entrada inválida, impuesto centralmente en `BaseEvolithCommand.handleError` más un guard jest que rompe la build ante cualquier literal de salida fuera del conjunto. **La propagación se auditó en vez de suponerse:** un barrido de todo el repo no encontró NADA que interpretara `1` como "gate falló", así que no había rotura viva aquí — pero la composite action colapsaba todo lo no-cero en `non-compliant`, lo que **afirmaba que el repositorio de un cliente era no conforme cuando el CLI simplemente había reventado o se le había invocado mal**. Eso es el inverso de un gate que reporta verde sobre un repo sin evaluar, y es igual de falso. La action ahora reporta `error` / `invalid-input`, expone `exit-code`, falla el paso ante un no-veredicto ignorando `fail-on-violation`, y su resumen de PR dice "Not evaluated — esto no es un hallazgo sobre este repositorio". Sus tests pasaron de 11 a 13, reclasificados y no aflojados: cuatro casos eran veredictos bloqueados (ahora exit 2) y dos eran fallos de herramienta genuinos (siguen en exit 1, ahora `error`). Ambas guías publican la tabla de cuatro valores. **NO cerrado:** la disciplina stdout/stderr está intacta (341 `console.log` frente a 115 `console.error`), no hay flujo de eventos `--format ndjson`, y el tercer criterio pide un ruleset de paridad Rego mientras lo que aterrizó es un guard jest que sólo cubre este paquete. **Follow-up fuera de este repositorio:** el Tracker invoca el CLI y puede ramificar por su código de salida — no verificable desde aquí, hay que revisarlo en `evolith_tracker`. **Criterio 1 marcado el 2026-07-28 tras verificarlo:** `exit-code-taxonomy.spec.ts` publica exactamente cuatro códigos, separa un veredicto bloqueante de un Core inalcanzable y — la parte que sostiene el criterio — BARRE las fuentes de los comandos y comprueba que ninguna nombra un exit code fuera de `0`, `1`, `2` o `3`, con protección anti-vacua para que un verde sobre un barrido vacío no cuente como verde. 21/21. Quedan los criterios 2 y 3: no hay test que afirme que `--format json`/`ndjson` escribe datos solo en stdout con todo diagnóstico en stderr, ni un par ruleset+Rego que exija la taxonomía como gobierno en vez de como barrido de jest. **+ola 2026-07-29 — criterios 2 y 3 cerrados para el canal JSON; un barrido reforzado encontró cuatro roturas más, miembros de la taxonomía pero incorrectas.** Criterio 2: `machine-channel.registry-sweep.spec.ts` le pregunta al propio registro de commander del CLI qué comandos declaran `--format` (no una lista a mano) y comprueba que los 32 encontrados escriben exactamente un documento JSON parseable en stdout con los diagnósticos en stderr — 32/32 limpios. Esto cierra la mitad de pureza stdout/stderr del criterio 2; el streaming `--format ndjson`, el cuarto pilar del fix original, sigue sin construirse. Criterio 3: la taxonomía ahora está gobernada por `src/rulesets/cli/exit-code-taxonomy.rules.json` con paridad Rego (`src/rulesets/opa/cli-exit-code-taxonomy.rego`, `opa test` 7/7) y un evaluador nativo (`cli-exit-taxonomy-rule.handler.ts`, 28/28 junto con `rule-corpus-triage.spec.ts`) — un comando nuevo ya no puede romper la taxonomía sin que falle un ruleset. **Reforzar el barrido sacó a la luz justo la clase de bug para la que existe:** al sumar `envelope.success === (status === 0)` junto a la pertenencia a la taxonomía aparecieron CUATRO comandos que eran miembros válidos de la taxonomía mientras contradecían su propio envelope — `sdlc gate-status` salía con 0 sobre un envelope `INTERNAL_ERROR` (un consumidor que ramifica por código lo lee como PASS), `alias` salía con 1 en vez de 3 ante un error de uso, el catch-all de `init-wizard` colapsaba un `NonInteractiveError` a `INTERNAL_ERROR`/1 antes de que llegara a `resolveExitCode`, y la rama JSON de `sdlc generate` hacía `return` antes de llegar a la asignación de `process.exitCode` que tenía debajo. Los cuatro se corrigieron enrutando por los helpers ya existentes `exitCodeForErrorCode`/`resolveExitCode`/`carriesCliExitCode` en vez de un literal fijo, y el barrido ahora exige esa coherencia para todo comando de aquí en más. Verificado: suite del CLI 101 suites/1446 tests, handler+triage de core-domain 28/28, `opa test` 7/7, `tsc --noEmit` limpio. [PR #278](https://github.com/beyondnetcode/evolith_arch32/pull/278). **Sigue abierto:** el flujo de eventos `--format ndjson`, y el seguimiento cross-repo del Tracker señalado arriba (no verificable desde este repo). **Cierre 2026-07-31 (#305).** El canal máquina queda probado sobre todo el registry y la taxonomía de exit codes, gobernada. | `Evolith CLI` | Cross | P1 | M | `COMPLETADO` | | [`GT-581`](./gap-reference-catalog.es.md#gt-581) | `grep -rn "outputSchema\\|structuredContent" src/packages/mcp-server/src` devuelve **0**, e igual `annotations` — sobre **50 tools anunciadas**, con el SDK `1.29.0`, que soporta las tres cosas. Todo llamante recibe por tanto un bloque de texto y debe reconstruir su forma por ingeniería inversa, y ningún cliente puede distinguir una tool de solo lectura (`evolith-adr-list`) de una destructiva (`evolith-satellite-create`) antes de invocarla. El draft de la especificación además relajó `inputSchema`/`outputSchema` para aceptar cualquier keyword de JSON Schema 2020-12 (SEP-2106) y pide a los servidores devolver `tools/list` en orden determinista para mejorar el acierto de caché de cliente y de prompt — dos ganancias gratis en la misma pasada. Fix: derivar `outputSchema` por tool desde `@beyondnet/evolith-contracts`, emitir `structuredContent`, añadir annotations `readOnlyHint`/`destructiveHint`/`idempotentHint`, y hacer determinista el orden de `tools/list`. Ola 2: las 50 herramientas declaran `outputSchema` y devuelven `structuredContent` que valida con el propio validador del SDK — incluso en las denegaciones FORBIDDEN, que es el resultado que un agente más necesita leer mecánicamente. Se genera de forma central desde la versión del sobre, así que no se puede registrar una herramienta sin contrato. **Salvedad que se arrastra, no se oculta:** la rama `data` queda declarada-pero-abierta en las 50; estrecharla exige esquemas por operación, que es GT-583. | `MCP Server` | Cross | P1 | M | `COMPLETADO` | | [`GT-582`](./gap-reference-catalog.es.md#gt-582) | Verificado directamente contra la especificación viva el 2026-07-26, no tomado de una fuente secundaria. La revisión **actual** del protocolo es `2025-11-25`; el **draft** elimina las sesiones a nivel de protocolo y la cabecera `Mcp-Session-Id` (SEP-2567), elimina el handshake `initialize`/`notifications/initialized` en favor de `_meta` por petición (SEP-2575), hace obligatorio `server/discover` (SEP-2575), sustituye las peticiones iniciadas por el servidor con el patrón **MRTR** — `InputRequiredResult`, un `resultType` obligatorio, e `inputResponses` en un reintento de la petición original (SEP-2322) —, deprecia Roots/Sampling/Logging (SEP-2577) y deprecia el Dynamic Client Registration en favor de Client ID Metadata Documents. En este repositorio: SDK `1.29.0`, **7 sitios con `sessionId`** bajo `src/packages/mcp-server/src`, y `grep -rn "well-known\\|oauth-protected-resource" src` devuelve 2 coincidencias no relacionadas, así que tampoco se sirve documento de metadatos de recurso protegido. MRTR importa más allá de la conformidad: es *la aprobación como protocolo*, y es la única forma en que el gate HITL sobrevive a la desaparición de las sesiones. **La "corrección" anterior registrada aquí quedó a su vez refutada, y se reemplaza en vez de borrarse:** esta fila decía antes que la etiqueta `2026-07-28` "no pudo confirmarse" y que la página de versionado "sigue nombrando `2025-11-25` como actual". Verificado contra la especificación viva el 2026-07-31: **`2026-07-28` ES la revisión actual.** La página de versionado lo dice con esas palabras, describe la negociación como una clave `io.modelcontextprotocol/protocolVersion` por request dentro de `_meta` en vez de durante `initialize`, vuelve `server/discover` un RPC obligatorio, y remite a los clientes de la era del handshake (`2025-11-25` y anteriores) a una sección de compatibilidad hacia atrás. El contenido técnico estaba bien; la duda sobre la fecha estaba mal, y estaba mal en la dirección que habría diferido el trabajo. **CERRADO el 2026-07-31 en los tres criterios** — `protocol-revisions.ts` exporta ambas revisiones y `stateless-rpc.ts` las sirve en paralelo, MRTR es real (`mrtr-request-state.ts`, `requestState` sellado, reintento sin sesión), y se sirven los metadatos de recurso protegido RFC 9728 en `/.well-known/oauth-protected-resource` con puntero `WWW-Authenticate`. | `MCP Server` | Cross | P1 | L | `COMPLETADO` | diff --git a/reference/core/control-center/gaps/gap-tracking.md b/reference/core/control-center/gaps/gap-tracking.md index 7968921ef..4ac8199e6 100644 --- a/reference/core/control-center/gaps/gap-tracking.md +++ b/reference/core/control-center/gaps/gap-tracking.md @@ -77,7 +77,7 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-575`](./gap-reference-catalog.md#gt-575) | **A product that sells AI governance ships its only LLM egress path without any of the controls it sells.** `GeminiProvider.ts:17` builds the URL with the API key in the query string; the whole 57-line file has no `AbortSignal` (`:31-37`), no budget, no redaction, no log or metric, and its only output validation is `JSON.parse(candidate) as T` (`:52`). It is a public export (`src/packages/agent-runtime/src/index.ts:22`) of `@beyondnet/evolith-agent-runtime@1.1.0`. Disclosure across `README.md`, `README.es.md`, `SECURITY.md` and the 8 package READMEs: zero. It violates at least 4 of the 9 blocking `AAI-*` rules the product itself sells. **Exposure is latent, not active** — the only in-tree caller is `src/sdk/cli/src/commands/plan/index.ts:27`, and `PlanCommand` is not registered in `app.module.ts` — but it sits on the public surface a security reviewer reads first. The correct implementation already exists in-house: `.harness/scripts/ci/agentic/review-provider.mjs:35-38` puts the key in a header with the literal comment "API key in a header, not the URL query string", with budget caps and 8 redaction patterns. Fix: port that control, and collapse the duplicate `ILLMProvider` port into the governed `IAssistantTransport`/`SupervisedAssistantClient` seam. **Progress (`44fe8dd3`):** the controls the repository already runs against itself in `.harness/scripts/ci/agentic/` were ported into the shipped provider rather than reinvented — API key moved from the URL query string to the `x-goog-api-key` header, `AbortController` timeout, byte/token budget that fails closed, secret redaction, schema-validated output, and an auditable log/metric. Egress is now **off unless `EVOLITH_LLM_EGRESS=true`**. **NOT closed:** turning an always-on published export into an opt-in one is a behavioural break for `@beyondnet/evolith-agent-runtime@1.1.0` (names stay additive, so the GT-388 contract freeze holds, but observable behaviour changed) and needs a deliberate version decision alongside `GT-570`; the duplicate `ILLMProvider` port is not yet fully collapsed into the governed `IAssistantTransport`/`SupervisedAssistantClient` seam; and the Spanish package READMEs still carry no disclosure. Verified: agent-runtime 309/309. **+2026-07-26 (wave 3) — 2 of 3 criteria met.** The duplicate LLM port was collapsed: `GeminiProvider` is now reachable only as an `IAssistantTransport` behind the supervised, fail-closed, off-by-default seam, so there is one governed path to an LLM instead of two with opposite postures. The Spanish egress disclosure was written into `src/packages/agent-runtime/README.es.md`, structurally mirroring the English one. **Still open:** criterion 3 — the repository does not pass its own 9 blocking `AAI-*` rules in a CI check, and there is still no root `agent.config.json`, so the agentic-ai topology is never evaluated against Evolith itself. Also unresolved and owner-gated: turning an always-on published export into an opt-in one is a behavioural break for `@beyondnet/evolith-agent-runtime@1.1.0` and needs a version decision alongside `GT-570`. `src/sdk/cli/README.es.md` still carries no disclosure (out of the agent's owned area). **CLOSED 2026-07-28, with the caveat that matters recorded rather than buried.** All three criteria are met: the key travels in a header with an `AbortController` timeout, an egress budget and secret redaction (`GeminiProvider`); the README carries a `Network egress and data handling` section naming the endpoint, what is sent, the opt-in and the sub-processor; and `agentic-ai-self-conformance.spec.ts` evaluates the repository against the nine blocking `AAI-*` rules it ships, 13/13, with a NEGATIVE FIXTURE proving all nine fail without `agent.config.json`. **The caveat:** that spec runs in `Test agent-runtime`, which is NOT one of main's seven required contexts (verified against the live branch protection). So the product now runs its own AI-egress rules against itself in CI — but a failure would not block a merge. Adding `Test agent-runtime` to the required set is a branch-protection change and an owner action; it is not something to do unilaterally, and it is the difference between a check that reports and a check that governs. | `agent-runtime` | Cross | P0 | S | `DONE` | | [`GT-576`](./gap-reference-catalog.md#gt-576) | **The self-assessment a buyer reads first is falsifiable in ten minutes, and the document incriminates itself.** `maturity-assessment.md` defines *Validated (Weight 1.0) — Passing all quality gates, tests, and active in CI/CD*, then marks **Pillar 1 Security "Level 4 (Managed) / Validated"** citing multi-tenant Row-Level Security (ADR-0010) and immutable audit trails via CDC (ADR-0016): `grep -rniE 'row.level.security\|current_setting\(\|debezium\|change data capture'` over `src` returns **ZERO files**, and core-api declares no database driver or ORM. **Pillar 4** is marked Level 4 / Validated citing "deterministic monorepo builds via Nx" — no `nx.json` and no `nx` dependency exist. It also claims dual-engine 8/8 parity while the gate covers 3 topologies and the published package ships policies for 5. (Pillar 3's stale `opossum` citation is a different matter — that pillar is honestly marked `Designed`.) Fix: downgrade Pillar 1 to `Designed` with the ADRs listed as intent, delete the Nx citation, report parity against the published artifact, and add a mechanical rule to `09-reconcile-maturity.mjs`: a capability may only be marked `Validated` if its evidence list contains at least one `file:line` reference or CI job, never an ADR alone. **Closure (`44fe8dd3`):** every claim in the statement was re-verified against the tree and all held. Pillar 1 (Security) and Pillar 4 dropped from `Validated` to `Designed` with their ADRs listed as intent, the Nx citation deleted, and parity reported against the published artifact — in BOTH languages, structurally identical. The class of error is now mechanically blocked: `09-reconcile-maturity.mjs` rejects any `Validated` capability whose evidence contains no `file:line` and no CI job, and that rule ships with a negative self-test that proves it goes red on a deliberately bad input. Verified: `09-reconcile-maturity.mjs --check` ✅, `04-check-bilingual-parity.mjs` ✅, governance 17/17. | `Governance` | Cross | P1 | S | `DONE` | | [`GT-577`](./gap-reference-catalog.md#gt-577) | **The artifact a customer would wire into their CI reads as a broken tool even though the gate works.** `.github/actions/evolith-validate/action.yml` reads `jq -r '.summary.violations // 0'`, but the real CLI envelope has top-level keys `[success, data, meta]` with `data = {status, rulesChecked, issues, coreRef, timestamp}` — there is no `.summary`. The same applies to the `--output` file (`validate.command.ts:353-361` writes the same `createSuccessEnvelope`). **Important nuance: the action does block correctly** — it captures `EXIT_CODE`, sets `compliance-status=non-compliant` and exits 1 when `fail-on-violation=true`; what is broken is the counter and the PR summary text, which renders literally "Non-compliant -- 0 violation(s) found". And `grep -rn 'evolith-validate' .github/` matches only inside the action's own README: zero consumers across the 12 workflows, so no regression is possible. Fix: change the jq path to `.data.issues \| map(select(.blocking)) \| length` and add a workflow in this repository that runs the action against a non-conforming satellite fixture, so it is dogfooded and regression-tested. **Progress (`44fe8dd3`):** the `jq` path now counts blocking issues from the real ADR-0073 envelope instead of reading a `.summary` key that never existed, and `.github/workflows/evolith-validate-dogfood.yml` runs the action against a deliberately non-conforming satellite fixture so the action is finally dogfooded. Validated by YAML parse and by executing the action's step scripts locally against the built CLI. **NOT closed:** the workflow has never executed on a GitHub runner, so by this audit's own standard the gate is not yet proven — a gate that has never been seen failing is the defect this whole wave is about. Close it after its first green run. Separately, it is not in the required-checks set, which is the `GT-574` owner action. **Closure (verified against CI run 30228607519, `develop`, 2026-07-27T00:56:37Z):** both jobs green — `Action contract (recorded envelopes)` and `Action vs non-conforming satellite`. The closure is NOT "the workflow is green": the log carries the two assertions that were impossible before, `OK: 34 blocking violation(s) of 274 issue(s), agreeing with the report.` and `OK: the gate blocked as expected with 34 blocking violation(s).` So the counter is non-zero, it agrees with the report file the action writes, `compliance-status` is `non-compliant`, and the negative half — `fail-on-violation: true` MUST fail the step — is exercised on every run. Five consecutive green runs. The action is now dogfooded and under regression, which was the more important half of this gap. Remaining and tracked elsewhere: the workflow is not in the required-checks set, which is branch-protection configuration. | `Infra` | Cross | P2 | XS | `DONE` | -| [`GT-578`](./gap-reference-catalog.md#gt-578) | **The two systemic root causes behind most findings of the 2026-07-26 audit, addressed at the mechanism rather than instance by instance.** (a) *Path literals*: the move to `src/` migrated code and imports but not the hundreds of path strings in CI scripts, workflow `run:` steps, evaluator constants and Helm values — the compiler catches a moved module, nothing catches a moved file referenced by a string, and a path that does not resolve produces silence. Live instances: `OpaEvaluator` hardcoding `/rulesets/opa/policy.wasm` (the whole OPA engine is non-functional against the Core layout); `upgrade` diffing against a nonexistent `/rulesets`; the CLI boundary config guarding `src/domain`, `src/application`, `src/core`, none of which has ever existed; `sdk-cli-ci.yml:467` invoking a nonexistent script whose real counterpart points at a pre-refactor path, so "Winston Agentic Review" is dead twice and reports `success`. (b) *Anti-vacuous pass*: the pattern already exists at `34-boundary-guard-repository.mjs:57-73` ("A zero-file scan must never be reported as boundary guard passed") and as a negative self-test in the Tracker contract gate — it is applied to 2 guards of ~46. Fix: a ~40-line guard resolving every path literal against disk; every guard publishes its denominator and exits 1 on a zero-element scan; every guard ships a deliberately bad fixture that MUST turn it red in CI; and CI executes every `validationCommand` in `gap-closure-evidence.json`. **Closure (`44fe8dd3`):** `40-validate-path-literals.mjs` scans harness scripts, workflow `run:` steps and Helm values, resolves each literal against disk, publishes its denominator (109 literals across 197 files) and exits 1 on a vacuous scan — it obeys the discipline it enforces, with a negative fixture in `node --test` (15 tests). Both live defects it found are repaired: `sdk-cli-ci.yml:467` invoked a nonexistent `.harness/scripts/ci/13-agentic-code-review.mjs` (real path under `ci/agentic/`) and that script's argv targeted a pre-refactor `packages/mcp-server/dist/main.js` — which is why "Winston Agentic Review" reported `success` for months while never reaching the server. With both fixed, the guard was flipped from `--report` to its default STRICT mode in `ci-runner.mjs`, so a new dead literal now FAILS the governance run. The anti-vacuous half is enforced for this guard; extending the pattern to the other ~45 guards remains follow-on work. **NOT closed:** two of its three criteria remain unmet — the anti-vacuous-pass pattern is enforced for THIS guard only (~45 others to go), and the board's `validationCommands` are still not executed in CI. The path-literal guard itself is strict, wired and green. **+2026-07-26 (wave 3) — two new guards landed; criteria 2 and 3 advanced, neither closed.** `42-validate-guard-denominators.mjs` classifies all **54** CI guards and asserts none can pass a zero-element scan silently (8/8 unit tests, exit 0). `41-validate-evidence-commands.mjs` finally EXECUTES the board's `validationCommands` instead of only checking they are non-empty strings (27/27 unit tests), and it reports its denominator honestly: over **937 recorded commands across 544 closure records it finds 290 dead references**, and it prints `THIS IS NOT A PASS` rather than exiting green on a number that large. It is wired in report mode with a `--max-dead 290` ratchet so the count can only go down. **Still open:** criterion 3 needs dead == 0, which is a data migration of the board-owned evidence file, not a guard change; criterion 2 needs a negative fixture per guard, which classification does not substitute for. The 290 dead references are themselves a finding: they are the measured size of root cause C5 — a board that records intent rather than result. Wave 2: criterion 2 is now enforced by EXECUTION — `43-validate-guard-negative-fixtures.mjs` points every scanning guard at one deliberately empty tree and 32/32 turned red, 0 reported a pass. One shared fixture rather than 34 bespoke ones, derived from 42’s classification, so the next guard is exercised the day it lands. Two false-DEAD classes in 41 repaired and the ratchet made machine-independent (it now prints its own basis: 303, agreed between a laptop and the runner despite raw counts differing by 15). 43, 44 and 34 wired into ci-cd.yml, ratchet lowered 305→303. **2026-08-01 follow-up:** the data migration advanced the evidence file to **0 local dead references** and a CI clean-checkout basis of **17** generated/gitignored referents, so the ratchet moved to `--max-dead 17`. AC3 remains open because the full executable sweep now has evidence instead of a blind pass: `--execute --strict --max-dead 17` ran 109 unique candidates, 104 green, 2 non-zero (`GT-42`, `GT-280`) and 3 search-with-no-match outcomes. | `Governance` | Cross | P1 | M | `IN-PROGRESS` | +| [`GT-578`](./gap-reference-catalog.md#gt-578) | **The two systemic root causes behind most findings of the 2026-07-26 audit, addressed at the mechanism rather than instance by instance.** (a) *Path literals*: the move to `src/` migrated code and imports but not the hundreds of path strings in CI scripts, workflow `run:` steps, evaluator constants and Helm values — the compiler catches a moved module, nothing catches a moved file referenced by a string, and a path that does not resolve produces silence. Live instances: `OpaEvaluator` hardcoding `/rulesets/opa/policy.wasm` (the whole OPA engine is non-functional against the Core layout); `upgrade` diffing against a nonexistent `/rulesets`; the CLI boundary config guarding `src/domain`, `src/application`, `src/core`, none of which has ever existed; `sdk-cli-ci.yml:467` invoking a nonexistent script whose real counterpart points at a pre-refactor path, so "Winston Agentic Review" is dead twice and reports `success`. (b) *Anti-vacuous pass*: the pattern already exists at `34-boundary-guard-repository.mjs:57-73` ("A zero-file scan must never be reported as boundary guard passed") and as a negative self-test in the Tracker contract gate — it is applied to 2 guards of ~46. Fix: a ~40-line guard resolving every path literal against disk; every guard publishes its denominator and exits 1 on a zero-element scan; every guard ships a deliberately bad fixture that MUST turn it red in CI; and CI executes every `validationCommand` in `gap-closure-evidence.json`. **Closure (`44fe8dd3`):** `40-validate-path-literals.mjs` scans harness scripts, workflow `run:` steps and Helm values, resolves each literal against disk, publishes its denominator (109 literals across 197 files) and exits 1 on a vacuous scan — it obeys the discipline it enforces, with a negative fixture in `node --test` (15 tests). Both live defects it found are repaired: `sdk-cli-ci.yml:467` invoked a nonexistent `.harness/scripts/ci/13-agentic-code-review.mjs` (real path under `ci/agentic/`) and that script's argv targeted a pre-refactor `packages/mcp-server/dist/main.js` — which is why "Winston Agentic Review" reported `success` for months while never reaching the server. With both fixed, the guard was flipped from `--report` to its default STRICT mode in `ci-runner.mjs`, so a new dead literal now FAILS the governance run. The anti-vacuous half is enforced for this guard; extending the pattern to the other ~45 guards remains follow-on work. **NOT closed:** two of its three criteria remain unmet — the anti-vacuous-pass pattern is enforced for THIS guard only (~45 others to go), and the board's `validationCommands` are still not executed in CI. The path-literal guard itself is strict, wired and green. **+2026-07-26 (wave 3) — two new guards landed; criteria 2 and 3 advanced, neither closed.** `42-validate-guard-denominators.mjs` classifies all **54** CI guards and asserts none can pass a zero-element scan silently (8/8 unit tests, exit 0). `41-validate-evidence-commands.mjs` finally EXECUTES the board's `validationCommands` instead of only checking they are non-empty strings (27/27 unit tests), and it reports its denominator honestly: over **937 recorded commands across 544 closure records it finds 290 dead references**, and it prints `THIS IS NOT A PASS` rather than exiting green on a number that large. It is wired in report mode with a `--max-dead 290` ratchet so the count can only go down. **Still open:** criterion 3 needs dead == 0, which is a data migration of the board-owned evidence file, not a guard change; criterion 2 needs a negative fixture per guard, which classification does not substitute for. The 290 dead references are themselves a finding: they are the measured size of root cause C5 — a board that records intent rather than result. Wave 2: criterion 2 is now enforced by EXECUTION — `43-validate-guard-negative-fixtures.mjs` points every scanning guard at one deliberately empty tree and 32/32 turned red, 0 reported a pass. One shared fixture rather than 34 bespoke ones, derived from 42’s classification, so the next guard is exercised the day it lands. Two false-DEAD classes in 41 repaired and the ratchet made machine-independent (it now prints its own basis: 303, agreed between a laptop and the runner despite raw counts differing by 15). 43, 44 and 34 wired into ci-cd.yml, ratchet lowered 305→303. **2026-08-01 follow-up:** the data migration advanced the evidence file to **0 local dead references** and a CI clean-checkout basis of **17** generated/gitignored referents, so the ratchet moved to `--max-dead 17`. A follow-up made the strict executable sweep pass: `--execute --strict --max-dead 17` ran 109 unique candidates, 106 exit 0, 0 non-zero and 3 search-with-no-match outcomes. AC3 remains open until those inconclusive searches become assertable commands and CI is promoted from ratchet/reporting to governed execution. | `Governance` | Cross | P1 | M | `IN-PROGRESS` | | [`GT-580`](./gap-reference-catalog.md#gt-580) | `grep -rno "process.exit([0-9]*)" src/sdk/cli/src` returns **20 × `process.exit(1)` plus 2 bare `process.exit()`** — one single failure value for a FAIL verdict, a bad flag, a missing file and an infra crash alike, so no consumer can branch on the cause. Diagnostics share the machine channel: **341 `console.log` against 115 `console.error`**. And there is no incremental output — no `--format ndjson` — so a long `validate` is opaque until it terminates. An agent harness, a pre-commit hook and a CI step all have exactly one primitive in common (the process exit code) and the CLI currently declines to use it. Fix: a published taxonomy (`0` PASS · `2` usage error · `3` **verdict FAIL** · `1` infra failure · `4` HITL required), every diagnostic to stderr, a versioned NDJSON event stream, and the taxonomy governed by its own ruleset with Rego parity so a new command cannot regress it. **+wave 2026-07-28 — the taxonomy exists and its contract break is propagated.** `0` pass · `1` tool failure · `2` blocked · `3` invalid input, enforced centrally in `BaseEvolithCommand.handleError` plus a jest guard that fails the build on any exit literal outside the set. **The propagation was audited rather than assumed:** a repo-wide sweep found NOTHING branching on `1` as "gate failed", so there was no live break here — but the composite action collapsed every non-zero onto `non-compliant`, which **asserted a customer's repository was non-compliant when the CLI had merely crashed or been invoked wrongly**. That is the inverse of a gate reporting green over an unevaluated repo, and it is just as false. The action now reports `error` / `invalid-input`, exposes `exit-code`, fails the step for a non-verdict regardless of `fail-on-violation`, and its PR summary says "Not evaluated — this is not a finding about this repository". Its tests went 11 → 13, reclassified rather than loosened: four cases were blocked verdicts (now exit 2) and two were genuine tool failures (still exit 1, now `error`). Both language guides publish the four-value table. **NOT closed:** stdout/stderr discipline is untouched (341 `console.log` vs 115 `console.error`), there is no `--format ndjson` event stream, and the third criterion asks for a Rego-parity ruleset while what shipped is a jest guard covering only this package. **Follow-up outside this repository:** the Tracker invokes the CLI and may branch on its exit code — unverifiable from here and must be checked in `evolith_tracker`. **Criterion 1 ticked on 2026-07-28 after verification:** `exit-code-taxonomy.spec.ts` publishes exactly four codes, separates a blocked verdict from an unreachable Core, and — the load-bearing part — SCANS the command sources and asserts none names an exit code outside `0`, `1`, `2` or `3`, with an anti-vacuous guard so a green from an empty scan is not a green. 21/21. Criteria 2 and 3 remain: there is no test asserting that `--format json`/`ndjson` writes data only to stdout with every diagnostic on stderr, and no ruleset+Rego pair enforcing the taxonomy as governance rather than as a jest scan. **+wave 2026-07-29 — criteria 2 and 3 closed for the JSON channel; a strengthened sweep then found four more taxonomy-membership-but-wrong breaks.** Criterion 2: `machine-channel.registry-sweep.spec.ts` asks the CLI's own commander registry which of its commands declare `--format` (not a hand-picked list) and asserts every one of the 32 found writes exactly one parseable JSON document to stdout with diagnostics on stderr — 32/32 clean. This closes the stdout/stderr purity half of criterion 2; `--format ndjson` streaming, the fourth pillar of the original fix, is still unbuilt. Criterion 3: the taxonomy is now governed by `src/rulesets/cli/exit-code-taxonomy.rules.json` with Rego parity (`src/rulesets/opa/cli-exit-code-taxonomy.rego`, `opa test` 7/7) and a native evaluator (`cli-exit-taxonomy-rule.handler.ts`, 28/28 with `rule-corpus-triage.spec.ts`) — a new command can no longer regress the taxonomy without a ruleset failure. **Strengthening the sweep surfaced exactly the class of bug it exists to catch:** adding `envelope.success === (status === 0)` alongside taxonomy-membership found FOUR commands that were valid taxonomy members while contradicting their own envelope — `sdlc gate-status` exited 0 on an `INTERNAL_ERROR` envelope (a branching consumer reads that as a PASS), `alias` exited 1 instead of 3 on a usage error, `init-wizard`'s own catch-all collapsed a `NonInteractiveError` to `INTERNAL_ERROR`/1 before it ever reached `resolveExitCode`, and `sdlc generate`'s JSON branch `return`ed before reaching the `process.exitCode` assignment below it. All four fixed by routing through the existing `exitCodeForErrorCode`/`resolveExitCode`/`carriesCliExitCode` helpers rather than a hardcoded literal, and the sweep now asserts this agreement for every command going forward. Verified: CLI suite 101 suites/1446 tests, core-domain handler+triage 28/28, `opa test` 7/7, `tsc --noEmit` clean. [PR #278](https://github.com/beyondnetcode/evolith_arch32/pull/278). **Still open:** the `--format ndjson` event stream, and the Tracker cross-repo follow-up noted above (unverifiable from this repo). **Closure 2026-07-31 (#305).** The machine channel is proven across the whole registry and the exit-code taxonomy is governed. | `Evolith CLI` | Cross | P1 | M | `DONE` | | [`GT-581`](./gap-reference-catalog.md#gt-581) | `grep -rn "outputSchema\\|structuredContent" src/packages/mcp-server/src` returns **0**, and so does `annotations` — across **50 announced tools**, on SDK `1.29.0`, which supports all three. Every caller therefore receives a text block and must reverse-engineer its shape, and no client can tell a read-only tool (`evolith-adr-list`) from a destructive one (`evolith-satellite-create`) before invoking it. The specification draft additionally loosened `inputSchema`/`outputSchema` to accept any JSON Schema 2020-12 keyword (SEP-2106) and asks servers to return `tools/list` in a deterministic order to improve client and prompt-cache hit rates — both free wins on the same pass. Fix: derive `outputSchema` per tool from `@beyondnet/evolith-contracts`, emit `structuredContent`, add `readOnlyHint`/`destructiveHint`/`idempotentHint` annotations, and make `tools/list` ordering deterministic. Wave 2: all 50 tools declare an outputSchema and return structuredContent that validates under the SDK’s own validator — including on FORBIDDEN denials, the result an agent most needs to read mechanically. Generated centrally from the envelope version, so a new tool cannot be registered without a contract. **Caveat carried, not hidden:** the `data` branch is declared-but-open for all 50; narrowing it needs per-operation schemas, which is GT-583. | `MCP Server` | Cross | P1 | M | `DONE` | | [`GT-582`](./gap-reference-catalog.md#gt-582) | Verified directly against the live specification on 2026-07-26, not taken from a secondary source. The **current** protocol revision is `2025-11-25`; the **draft** removes protocol-level sessions and the `Mcp-Session-Id` header (SEP-2567), removes the `initialize`/`notifications/initialized` handshake in favour of per-request `_meta` (SEP-2575), makes `server/discover` mandatory (SEP-2575), replaces server-initiated requests with the **MRTR** pattern — `InputRequiredResult`, a required `resultType`, and `inputResponses` on a retry of the original request (SEP-2322) — deprecates Roots/Sampling/Logging (SEP-2577) and deprecates Dynamic Client Registration in favour of Client ID Metadata Documents. In this repository: SDK `1.29.0`, **7 `sessionId` sites** under `src/packages/mcp-server/src`, and `grep -rn "well-known\\|oauth-protected-resource" src` returns 2 unrelated matches, so no protected-resource metadata document is served either. MRTR matters beyond conformance: it is *approval as a protocol*, and it is the only way the HITL gate survives the removal of sessions. **The earlier "correction" recorded here has itself been refuted, and is replaced rather than deleted:** this row previously said the `2026-07-28` label "could not be confirmed" and that the spec's versioning page "still names `2025-11-25` as current". Re-checked against the live specification on 2026-07-31: **`2026-07-28` IS the current revision.** The versioning page states it in those words, describes negotiation as a per-request `io.modelcontextprotocol/protocolVersion` key in `_meta` rather than during `initialize`, makes `server/discover` a mandatory RPC, and points handshake-era clients (`2025-11-25` and earlier) at a backward-compatibility section. The technical content was right; the doubt about the date was wrong, and it was wrong in the direction that would have deferred the work. **CLOSED 2026-07-31 on all three criteria** — `protocol-revisions.ts` exports both revisions and `stateless-rpc.ts` serves them side by side, MRTR is real (`mrtr-request-state.ts`, sealed `requestState`, retry without a session), and RFC 9728 protected-resource metadata is served at `/.well-known/oauth-protected-resource` with a `WWW-Authenticate` pointer. | `MCP Server` | Cross | P1 | L | `DONE` | @@ -684,7 +684,7 @@ This board is the single source of truth for technical debt, gaps, opportunities **Wave 2026-06-25 (SDLC Deep Audit):** Added 3 new gaps `GT-280`…`GT-282` from the SDLC Deep Audit playbook (`.harness/playbooks/sdlc-deep-audit.mjs`) — registered as `run-evolith-deep.mjs`. Evaluated Evolith Core against the 8-dimensional executable SDLC vision (Corpus, SDLC Modelo, Motor OPA, Ingesta Cliente, Tres Interfaces, Reporte, Gobernanza, Verificaciones). Score: 3/8 SÓLIDO, 2 PARCIAL, 3 AUSENTE. The 3 AUSENTE dimensions became GT-280 (SDLC phases as data), GT-281 (end-to-end evaluation pipeline), and GT-282 (actionable evaluation reports). -**Wave 2026-06-25 (GT-280 closure):** Created `reference/core/sdlc/phases/phase-f1.json`…`phase-f5.json` with structured phase data, `reference/core/sdlc/gates/gate-f1.json`…`gate-f5.json` with `requiredArtifacts[]` and `rules[]` referencing 26 Rego files, `.harness/playbooks/sdlc-phase-gate-validator.mjs` cross-reference checker, and Rego rules `rulesets/opa/sdlc/coverage.rego` + `rulesets/opa/sdlc/pyramid-distribution.rego`. Updated `sdlc-deep-audit.mjs` to detect structured data. Score evolved from 3/8→4/8 SÓLIDO. GT-280 closed. +**Wave 2026-06-25 (GT-280 closure):** Created `reference/governance/sdlc/phases/phase-f1.json`…`phase-f5.json` with structured phase data, `reference/governance/sdlc/gates/gate-f1.json`…`gate-f5.json` with `requiredArtifacts[]` and `rules[]`, `.harness/playbooks/sdlc-phase-gate-validator.mjs` cross-reference checker, and SDLC rules `src/rulesets/opa/sdlc/coverage.rego` + `src/rulesets/opa/sdlc/pyramid-distribution.rego`. Updated `sdlc-deep-audit.mjs` to detect structured data. Score evolved from 3/8→4/8 SÓLIDO. GT-280 closed. **Wave 2026-06-25 (GT-281 closure):** Created `SatelliteEvaluationPipeline` (`packages/core-domain/src/application/services/satellite-evaluation-pipeline.service.ts`) — composite service that orchestrates manifest → topology resolution → GT-280 gate loading → Rego rule execution → structured verdict. Created `SdlcDataLoaderService` for GT-280 runtime data loading. Created `SatelliteManifest` type (`domain/satellite-manifest.ts`). Updated `ValidateSatelliteUseCase` to accept `manifest?: SatelliteManifest`. Wired CLI `evolith validate --manifest/--phase`, MCP `evolith-validate` manifest/topology/phase params. Added end-to-end test (`satellite-evaluation-pipeline.spec.ts`) with 3 cases. Updated `sdlc-deep-audit.mjs` to detect pipeline and report SÓLIDO. Score evolved from 4/8→5/8 SÓLIDO. GT-281 closed. diff --git a/src/rulesets/contracts/README.es.md b/src/rulesets/contracts/README.es.md index a4b36b54a..8165c295e 100644 --- a/src/rulesets/contracts/README.es.md +++ b/src/rulesets/contracts/README.es.md @@ -15,6 +15,7 @@ Ejecutar: ```bash node .harness/scripts/ci/10-validate-contract-conformance.mjs node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer /ruta/a/consumer-contracts.json +node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json ``` ## Schemas fijados @@ -23,10 +24,9 @@ node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer /ruta/a | --- | --- | --- | --- | | `gate-evidence` | 1.0.0 | `src/rulesets/schema/gate-evidence.schema.json` | La evidencia adjunta a una decisión de gate. | | `output-envelope` | 1.0.0 | `src/rulesets/schema/output-envelope.schema.json` | El sobre de transporte ADR-0073 que devuelve toda superficie. | -| `evaluation-context` | 1.0.0 | `src/rulesets/schema/evaluation-context.schema.json` | La PETICIÓN de evaluación — lo que un consumidor envía a `POST /api/v1/evaluate`. | -| `evaluation-result` | 1.0.0 | `src/rulesets/schema/evaluation-result.schema.json` | La RESPUESTA de evaluación — el `EvaluationResult` canónico que viaja en `data`. | +| `evaluation-context` | 1.2.0 | `src/rulesets/schema/evaluation-context.schema.json` | La PETICIÓN de evaluación — lo que un consumidor envía a `POST /api/v1/evaluate`. | +| `evaluation-result` | 1.1.0 | `src/rulesets/schema/evaluation-result.schema.json` | La RESPUESTA de evaluación — el `EvaluationResult` canónico que viaja en `data`. | Los dos últimos se añadieron por GT-573. Hasta entonces la petición y la respuesta de la integración insignia no tenían schema fijado en ningún lado del cable, y por eso el Core podía responder con un sobre distinto del que el consumidor enlazaba dejando ambos CI en verde. -Todo consumidor debe fijar los cuatro ids, cada uno en la versión y el SHA-256 aquí declarados, o `--consumer` falla con `Consumer does not pin schema: `. Para `beyondnetcode/evolith_tracker` eso significa añadir las entradas `evaluation-context` y `evaluation-result` a `contracts/evolith-core-contracts.json` y mantener `contractVersion` en `1.0.0`. - +Todo consumidor debe fijar los cuatro ids, cada uno en la versión y el SHA-256 aquí declarados, o `--consumer` falla con `Consumer does not pin schema: `. El fixture commiteado `evolith-tracker-consumer.contract.json` es el snapshot de compatibilidad propiedad de Core usado por los comandos de evidencia; el repositorio vivo `beyondnetcode/evolith_tracker` sigue siendo dueño de su manifiesto en runtime. diff --git a/src/rulesets/contracts/README.md b/src/rulesets/contracts/README.md index 387721c8a..4ce053cf1 100644 --- a/src/rulesets/contracts/README.md +++ b/src/rulesets/contracts/README.md @@ -15,6 +15,7 @@ Run: ```bash node .harness/scripts/ci/10-validate-contract-conformance.mjs node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer /path/to/consumer-contracts.json +node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json ``` ## Pinned schemas @@ -23,10 +24,9 @@ node .harness/scripts/ci/10-validate-contract-conformance.mjs --consumer /path/t | --- | --- | --- | --- | | `gate-evidence` | 1.0.0 | `src/rulesets/schema/gate-evidence.schema.json` | Evidence attached to a gate decision. | | `output-envelope` | 1.0.0 | `src/rulesets/schema/output-envelope.schema.json` | The ADR-0073 transport envelope every surface returns. | -| `evaluation-context` | 1.0.0 | `src/rulesets/schema/evaluation-context.schema.json` | The evaluate REQUEST — what a consumer sends to `POST /api/v1/evaluate`. | -| `evaluation-result` | 1.0.0 | `src/rulesets/schema/evaluation-result.schema.json` | The evaluate RESPONSE — the canonical `EvaluationResult` carried in `data`. | +| `evaluation-context` | 1.2.0 | `src/rulesets/schema/evaluation-context.schema.json` | The evaluate REQUEST — what a consumer sends to `POST /api/v1/evaluate`. | +| `evaluation-result` | 1.1.0 | `src/rulesets/schema/evaluation-result.schema.json` | The evaluate RESPONSE — the canonical `EvaluationResult` carried in `data`. | The last two were added for GT-573. Before that, the flagship integration's request and response had no pinned schema on either side of the wire, which is how the Core could answer with a different envelope than the consumer bound and leave both CIs green. -Every consumer must pin all four ids, each at the version and SHA-256 declared here, or `--consumer` fails with `Consumer does not pin schema: `. For `beyondnetcode/evolith_tracker` that means adding the `evaluation-context` and `evaluation-result` entries to `contracts/evolith-core-contracts.json` and keeping `contractVersion` at `1.0.0`. - +Every consumer must pin all four ids, each at the version and SHA-256 declared here, or `--consumer` fails with `Consumer does not pin schema: `. The committed `evolith-tracker-consumer.contract.json` fixture is the Core-owned compatibility snapshot used by evidence commands; the live `beyondnetcode/evolith_tracker` repository still owns its runtime manifest. diff --git a/src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json b/src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json new file mode 100644 index 000000000..6dd8ba947 --- /dev/null +++ b/src/rulesets/contracts/fixtures/evolith-tracker-consumer.contract.json @@ -0,0 +1,29 @@ +{ + "consumer": { + "repository": "beyondnetcode/evolith_tracker", + "manifestPath": "contracts/evolith-core-contracts.json" + }, + "contractVersion": "1.0.0", + "schemas": [ + { + "id": "gate-evidence", + "version": "1.0.0", + "sha256": "9225090e2ee851dbd2d5c22e7a0a6e2d7f97db9835f7f3822ac7d9f861b75754" + }, + { + "id": "output-envelope", + "version": "1.0.0", + "sha256": "07eaffaae2c33071ea105fbcbdc497c06d149143f6cc9b94555170740707fd0b" + }, + { + "id": "evaluation-context", + "version": "1.2.0", + "sha256": "d62d557c6258ca6cc154e88850fd4879eb21d32dd3e4e9400aa25182c200cd69" + }, + { + "id": "evaluation-result", + "version": "1.1.0", + "sha256": "b0cad39fb15f0dccd1adbcc0f59aa26aee2a0f9a13ad250ffe031db7a0875d9d" + } + ] +}