## 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 ` 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/") ``` 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.