# E-002 — Un bloqueo informado que no llegaba al código de salida

- Fecha: 2026-08-02
- Origen: dogfooding del alpha portable mediante fixture Git externo
- Estado: confirmado y corregido
- Intervención que lo detectó: test E2E de cierre de ronda

## Jerarquía de objetivos

### Objetivo principal

Detectar tempranamente errores deterministas y desvíos del objetivo, con una señal que un agente, hook o CI pueda consumir sin interpretación adicional.

### Objetivo de fase

Entregar un alpha portable que funcione desde un repositorio Git ajeno, conserve evidencia y ejecute sólo verificaciones declaradas.

### Objetivo de ronda

Agregar `init`, `round-end` portable y un fixture externo que valide descubrimiento seguro de tests, check declarado y debilitamiento de test.

## Trayectoria observada

El fixture convirtió un test activo a `test.skip` y ejecutó `oguard check --file`. La salida JSON informó correctamente `status: "block"` y guardó el finding `weak-test` en `.oguard/state.json`.

Al invocarlo como subproceso, sin embargo, el proceso terminaba con código cero. El posterior `round-end` sí bloqueaba al releer el finding activo, por lo que el problema no era la detección; era la pérdida de su semántica de salida en la integración inmediata.

## Comportamiento esperado del guard

Un finding de severidad `error` debe tener dos representaciones coherentes:

```text
JSON: { "status": "block" }
exit code: 1
```

Así un agente que invoca el guard por archivo puede detenerse antes de acumular trabajo, sin depender de un cierre de ronda posterior ni de parsear estado local.

## Comportamiento real

Antes de la corrección, `oguard check` emitía `block` en stdout pero no asignaba `process.exitCode`. El runner portable clasificaba ese subcheck como `pass`; el estado persistido evitaba un `PASS` global falso, pero el feedback rápido era incompleto para consumidores basados en códigos de salida.

## Evidencia

- `test/portable-alpha.test.mjs`: el escenario con `test.skip` recibió JSON bloqueante;
- el subproceso de `oguard check` resolvía con código 0 antes de la corrección;
- `round-end` obtenía el finding mediante `oguard list` y devolvía `block` pese a ese subresultado;
- la regresión ahora exige rechazo con código 1 en `test/weak-test.test.mjs` y valida el cierre portable.

## Clasificación y límites

Es un defecto de propagación de una señal determinista, no un falso negativo de `weak-test`: la regla encontró el cambio y el estado lo preservó. Tampoco prueba que el guard detecte desvío semántico; sólo demuestra que un error cubierto no debe degradarse al cruzar el límite CLI/proceso.

## Corrección adoptada

`src/cli.mjs` fija `process.exitCode = 1` cuando `oguard check` devuelve `status: "block"`. Las advertencias permanecen con código cero. El runner conserva además su decisión independiente por findings activos y checks declarados.

## Impacto sobre la tesis

El episodio fortalece una condición necesaria de la tesis: una señal rápida sólo ahorra tiempo y tokens si es accionable por el canal que usa el agente. Mensaje y exit code son partes del mismo contrato observable.

No aporta evidencia estadística de reducción de deriva. Aporta un caso orgánico de integración que debe conservarse como regresión E2E.

## Seguimiento verificable

1. Mantener el fixture externo como regresión de `status:block` y exit code 1.
2. Al agregar nuevos comandos bloqueantes, verificar JSON, texto, estado y exit code en una misma prueba.
3. Medir en evidencia futura cuánto tarda la señal nivel 0; este episodio sólo valida su propagación, no su latencia.
