## Validacion end-to-end de apps (bucle reactivo, fase 4)

**Contrato obligatorio para apps que vayan a master con gate automatico**: declarar `e2e_checks` en su `app.md`. Sin contrato, `fn-analizador` no puede validar y la app cae al modo "manual": el humano sigue iterando.

Ver tambien: `apps_tbd.md`, `feature_flags.md`, issue 0068.

### Por que

El bucle reactivo del registry tiene 5 fases. Las 3 primeras (`fn-constructor`, `fn-executor`, `fn-recopilador`) cubren CONSTRUIR/EJECUTAR/RECOPILAR. La fase 4 (ANALIZAR) y la 5 (MEJORAR) no funcionan sin un contrato explicito de "como sabe el agente que esta app esta sana". Ese contrato es `e2e_checks`.

### Donde vive

En el frontmatter de cada `app.md`, lista `e2e_checks`. Convencion: `id` unico por check, ejecucion en orden declarado, falla = stop o continue segun severidad (TBD por implementar).

### Tipos de check

| Campo | Que hace |
|---|---|
| `id` | Identificador unico del check dentro de la app (`build`, `smoke`, `tests_unit`, ...) |
| `cmd` | Comando shell. Exit 0 = pass salvo override de `expect_exit`. |
| `health` | URL HTTP. Hace GET, espera 200, util tras un `cmd` que arranca un servicio en background (con `&`). |
| `ref` | Referencia a otro agente / funcion del registry (ej. `fn-recopilador:apps/X`, `fn-doctor:artefacts`). |
| `timeout_s` | Timeout en segundos. Default 60. |
| `expect_exit` | Codigo de salida esperado (default 0). |
| `expect_stdout_contains` | Substring que debe aparecer en stdout. |
| `expect_stdout_json` | JSONPath o key=value que debe satisfacer la salida. |
| `severity` | `critical` (default) o `warning`. Critical = bloquea merge; warning = registra y sigue. |

### Patrones por stack

#### Go service con frontend embebido

```yaml
e2e_checks:
  - id: build_frontend
    cmd: "cd frontend && pnpm install --frozen-lockfile && pnpm build"
    timeout_s: 180
  - id: build_backend
    cmd: "CGO_ENABLED=1 go build -tags fts5 -o myapp ."
  - id: smoke
    cmd: "./myapp --port 8200 --db /tmp/myapp_e2e.db &"
    health: "http://127.0.0.1:8200/api/health"
  - id: tests
    cmd: "go test -tags fts5 -count=1 ./..."
```

#### C++ ImGui app

```yaml
e2e_checks:
  - id: build
    cmd: "cmake --build build --target myapp -j"
    timeout_s: 300
  - id: self_test
    cmd: "./build/myapp --self-test"
    timeout_s: 30
  - id: pytest
    cmd: "cd tests && python3 -m pytest -x -q"
```

Apps C++ deben implementar `--self-test` que arranca, verifica subsistemas (GL loader, fonts, DBs locales), y sale con codigo 0/1.

#### Python pipeline / CLI

```yaml
e2e_checks:
  - id: import
    cmd: "python3 -c 'import myapp'"
  - id: cli_help
    cmd: "python3 -m myapp --help"
    expect_stdout_contains: "usage:"
  - id: dry_run
    cmd: "python3 -m myapp --dry-run --input examples/sample.json"
```

#### App con operations.db

Anadir siempre:

```yaml
  - id: ops_audit
    ref: "fn-recopilador:apps/myapp"
```

Esto invoca al recopilador en modo audit sobre `apps/myapp/operations.db`.

### Reglas

1. **Idempotente**: cada check debe poderse correr N veces sin efectos secundarios. Usar BDs en `/tmp/`, puertos altos, `--port 0` cuando se pueda.
2. **Sin credenciales reales**: ningun check toca produccion ni servicios externos sensibles. Si necesita HTTP de prueba, usar `httpbin.org` o un mock local.
3. **Tiempo acotado**: cada check declara `timeout_s`. Suma total de la app < 10 min como objetivo razonable.
4. **Determinista**: si el check depende de red flaky, marcalo `severity: warning` o usalo solo como diagnostico, no como gate.
5. **Cleanup implicito**: si el check arranca un proceso en background (`&`), debe morir al final. `fn-analizador` mata el grupo de procesos al terminar la suite.

### Como diseñar `e2e_checks` para una app existente

`fn-recopilador` tiene un modo `design-e2e <app_id>` que:

1. Inspecciona `app.md` (lang, framework, entry_point, uses_functions).
2. Revisa estructura del directorio (presencia de `tests/`, `frontend/`, `Makefile`, `CMakeLists.txt`, etc.).
3. Audita `operations.db` (si existe) para sugerir `ops_audit`.
4. Devuelve bloque `e2e_checks_suggested:` listo para copiar al `app.md` tras revision humana.

Comando indicativo:
```
Agent(subagent_type="fn-recopilador",
      prompt="design-e2e apps/<app>")
```

El recopilador NO escribe directo al `app.md`; deja la propuesta para que el humano apruebe (similar a `proposals`).

### Adopcion gradual

- Apps SIN `e2e_checks` declarado: `fn doctor` muestra warning, no bloquea nada.
- Apps CON `e2e_checks`: `fn-analizador` corre la suite. Si critical falla → `fn-mejorador` crea proposal. Gate opcional en `/git-push`.
- Pilotos iniciales: `apps/kanban`, `projects/osint_graph/apps/graph_explorer`. Resto de apps van migrando segun necesidad.

### Anti-patrones

| Anti-patron | Por que es malo |
|---|---|
| `cmd: "make test"` con make-target opaco | Ilegible. El check debe ser ejecutable directo y auditable. |
| Check que tarda > 5 min sin razon (smoke pesado) | Bloquea iteracion. Mover a CI nocturno con tag `slow`. |
| Smoke que toca produccion | Riesgo. Smoke usa BD efimera, puertos altos, mocks. |
| `expect_stdout_contains: ""` | Vacio = siempre pass. No es un check. |
| Anidar checks (uno depende de side-effects de otro sin declararlo) | Frigil. Cada check arranca lo que necesita. |
| Usar `e2e_checks` como sustituto de tests unitarios | Son cosas distintas. Unit tests viven en `*_test.go`/`pytest`. e2e valida que el sistema arranque y haga su trabajo. |

### Tabla `e2e_runs` en operations.db

Cada corrida de `fn-analizador` se persiste:

```sql
CREATE TABLE IF NOT EXISTS e2e_runs (
    id            TEXT PRIMARY KEY,
    app_id        TEXT NOT NULL,
    started_at    INTEGER NOT NULL,
    finished_at   INTEGER,
    status        TEXT NOT NULL,        -- pass|fail|partial
    checks_total  INTEGER NOT NULL,
    checks_pass   INTEGER NOT NULL,
    checks_fail   INTEGER NOT NULL,
    summary_json  TEXT NOT NULL
);
```

Migracion: `fn_operations/migrations/006_e2e_runs.sql` (issue 0068, paso 3).

### Output canonico de fn-analizador

Tabla caveman, una linea por check:

```
build           ✓ 42s
smoke           ✓ 0.8s
ops_audit       ✓
tests           ✗ 12s   exit 1, 3/45 failures
assertion:R1    ✗       warning  duration drift +47% vs p50
golden:home     ✓
```

Rojo cuando `severity: critical` y status fail. Esto es lo que el agente principal lee y reenvia al humano.
