feat(infra): auto-commit con 86 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 19:38:15 +02:00
parent de9bfec498
commit fe65c5e527
85 changed files with 11840 additions and 92 deletions
+274
View File
@@ -0,0 +1,274 @@
---
name: agentes-dispositivos-mesh
id: 0009
status: pending
created: 2026-05-23
updated: 2026-05-23
priority: high
risk: high
related_issues: [0134, 0135, 0136, 0137, 0138, 0139, 0140, 0141, 0142, 0143]
apps: [agents_dashboard, agents_and_robots, wg_hub, device_agent]
projects: [element_agents]
vaults: []
capability_groups: [wireguard, device-agent, docker-agent]
trigger: manual
schedule: ""
expected_runtime_s: 300
tags: [mesh, wireguard, matrix, e2ee, agents, devices, docker, sandboxing]
---
## Goal
Hablar desde Element con dispositivos completos (PCs, moviles, raspberry, IoT) y con
contenedores Docker como si fueran agentes Matrix. Cada device/container ejecuta sus
capabilities declaradas (shell/fs/camera/docker/sensores) bajo:
1. **Mesh WireGuard** anclado en `organic-machine.com` — sin abrir puertos en los devices.
2. **Matrix E2EE** como bus de control y chat — un room por device/container.
3. **Capability manifest firmado** ed25519 — el device rechaza lo que no este firmado.
## Pre-requisitos
- VPS `organic-machine.com` con root SSH (alias `vps` en `~/.ssh/config`).
- `agents_and_robots` y `agents_dashboard` desplegados (ya OK).
- `pass` con clave operador ed25519 (`pass insert operator/ed25519` — crear si falta).
- `apt-get install wireguard wireguard-tools` permitido en el VPS.
- Devices Linux/WSL: sudo sin password para `wg`, `wg-quick`, `systemctl`.
- Devices Android: Termux + WireGuard app + `pkg install golang openssh-client`.
## Funciones del registry recomendadas
| Rol | Funcion candidata | Estado |
|---|---|---|
| WG install (host) | `wg_install_bash_infra` | FALTA: crear |
| WG keygen | `wg_keygen_go_infra` | FALTA: crear |
| WG hub setup | `wg_hub_setup_bash_infra` | FALTA: crear |
| WG peer add (hub) | `wg_peer_add_go_infra` | FALTA: crear |
| WG peer remove (hub) | `wg_peer_remove_go_infra` | FALTA: crear |
| WG peer revoke (kill switch) | `wg_peer_revoke_go_infra` | FALTA: crear |
| WG client config gen | `wg_client_config_go_infra` | FALTA: crear |
| WG client install (device) | `wg_client_install_bash_infra` | FALTA: crear |
| WG status (parse `wg show`) | `wg_status_bash_infra` | FALTA: crear |
| Docker list (host) | `docker_container_list_go_infra` | FALTA: crear |
| Docker exec capability | `docker_container_exec_go_infra` | FALTA: crear |
| Docker logs tail | `docker_container_logs_go_infra` | FALTA: crear |
| Docker container enroll | `docker_container_enroll_go_infra` | FALTA: crear |
| Capability sign | `capability_manifest_sign_go_infra` | FALTA: crear |
| Capability verify | `capability_manifest_verify_go_infra` | FALTA: crear |
| Enrollment token gen | `enrollment_token_create_go_infra` | FALTA: crear |
| Enrollment token verify | `enrollment_token_verify_go_infra` | FALTA: crear |
| Matrix room per device | `matrix_room_for_device_py_browser` (extender) | OK base, EXTENDER |
| Provision hub pipeline | `provision_wg_hub_bash_pipelines` | FALTA: crear |
| Enroll device pipeline | `enroll_device_bash_pipelines` | FALTA: crear |
| Sink audit log | `device_audit_append_go_infra` | FALTA: crear |
| Notify approval | `matrix_send_message_py_browser` (existente) | OK |
## Apps tocadas
- `agents_dashboard` (cockpit ImGui) — panel "Mesh" + "Devices" + "Containers" + approval queue.
- `agents_and_robots` (hub Matrix VPS) — listener Matrix por device/container.
- `wg_hub` (nuevo service Go en VPS) — enrollment endpoint, peer CRUD, SSE stream.
- `device_agent` (nuevo binario per-host) — capability dispatcher con sandbox.
- `container_agent_sidecar` (opcional, nuevo) — sidecar para containers que necesitan WG-peer propio.
## Projects relacionados
- `element_agents` (parent project — agents Matrix).
## Vaults / storage
- `apps/wg_hub/operations.db` — tabla `wg_peers`, `wg_enrollment_tokens`, `device_audit`.
- `apps/agents_dashboard/local_files/agents_dashboard.db` — cache devices + capabilities.
- `pass operator/ed25519` — clave maestra del operador (firma manifests).
- `pass wg/preshared/<device_id>` — PSK por peer.
## Capability groups consultados
- `wireguard` (nuevo, ver `docs/capabilities/wireguard.md`).
- `device-agent` (nuevo, capability dispatcher + sandbox + audit).
- `docker-agent` (nuevo, capabilities sobre containers locales).
## Flow
### Fase A — registry primero (delegar a fn-constructor en paralelo)
1. `function: wg_install_bash_infra` (delegada).
2. `function: wg_keygen_go_infra` (delegada).
3. `function: wg_hub_setup_bash_infra` (delegada).
4. `function: wg_peer_add_go_infra` (delegada).
5. `function: wg_peer_remove_go_infra` (delegada).
6. `function: wg_peer_revoke_go_infra` (delegada).
7. `function: wg_client_config_go_infra` (delegada).
8. `function: wg_client_install_bash_infra` (delegada).
9. `function: wg_status_bash_infra` (delegada).
10. `cmd: ./fn index` — registra las 9 nuevas.
11. `cmd: fn doctor unused | grep wg_` — confirma que estan listas y no huerfanas (se usan en pasos C).
### Fase C — POC manual end-to-end
12. `function: wg_install_bash_infra` (sobre `organic-machine.com` via SSH).
13. `function: wg_keygen_go_infra` → key par hub.
14. `function: wg_hub_setup_bash_infra` — wg0, 10.42.0.1/24, ufw 51820/udp, persistencia.
15. `function: wg_keygen_go_infra` → key par device `home-wsl`.
16. `function: wg_peer_add_go_infra` (en hub) → asigna 10.42.0.10.
17. `function: wg_client_config_go_infra` → genera client.conf.
18. `function: wg_client_install_bash_infra` (en `home-wsl`).
19. `cmd: ping -c3 10.42.0.1` desde `home-wsl` — verifica handshake.
20. `cmd: curl http://10.42.0.1:8080/healthz` — agents_and_robots accesible por IP privada.
21. Repetir 15-19 para `pc-aurgi`.
### Fase B — spec + capability manifest + bot Matrix
22. Issue 0134 spec protocol: envelope JSON `{request_id, capability, args, signature, nonce}`,
error model, approval flow, audit chain hash.
23. `function: capability_manifest_sign_go_infra` (operator firma).
24. `function: capability_manifest_verify_go_infra` (device verifica antes de aceptar request).
25. `function: enrollment_token_create_go_infra` (token QR firmado, TTL 10min).
26. `function: enrollment_token_verify_go_infra` (hub valida en `/enroll`).
27. Implementar `apps/device_agent/` (Go cross-compile) — Matrix client + capability dispatcher + sandbox firejail.
28. Panel "Devices" en `agents_dashboard` — lista + capability matrix + approval queue + boton revoke.
29. Bot Matrix por device: cuando hablas en el room `#dev-aurgi:organic-machine.com`,
`agents_and_robots` parsea, valida capability, despacha a device_agent, devuelve resultado al room.
### Fase D — agentes-contenedores docker
30. `function: docker_container_list_go_infra` — corre en host con docker socket access.
31. `function: docker_container_exec_go_infra` — exec en container con whitelist binarios.
32. `function: docker_container_logs_go_infra` — tail logs SSE.
33. Modo "light": container expuesto via host's `device_agent` capability `docker.*`.
Element room: `#host-aurgi:organic-machine.com` con comando `!docker exec mycontainer ps`.
34. Modo "deep": container = peer WG propio. `container_agent_sidecar` corre WG dentro del container
(privileged) o sidecar gluetun-wg. Manifest firmado mapea `agent_X` → container_id.
35. Sub-bot Matrix por container: `#cont-mycontainer:organic-machine.com` (opcional, modo deep).
## Acceptance
- [ ] 9 funciones `wg_*` creadas + indexadas + sin huerfanas.
- [ ] Hub WG corriendo en `organic-machine.com`, `wg show` muestra interface wg0.
- [ ] `home-wsl` y `pc-aurgi` con IP estable 10.42.0.10/11, `ping` OK.
- [ ] `agents_and_robots` accesible solo desde subnet 10.42.0.0/24 (publico = DROP en :8080).
- [ ] `agents_dashboard` panel "Mesh" muestra peers vivos via SSE.
- [ ] Chat en `#dev-aurgi` ejecuta capability (ej. `!ls /home/lucas`) y devuelve resultado.
- [ ] Capability fuera del manifest rechazada con error en room.
- [ ] Capability `requires_approval=true` espera confirmacion en `#operator-approvals` antes de ejecutar.
- [ ] `docker.container.list` invocado desde Element devuelve containers del host.
- [ ] `docker.container.exec` con binario fuera de whitelist rechazado.
- [ ] Revoke device desde `agents_dashboard` → device pierde acceso en <5s.
- [ ] Audit log append-only inviolable (hash chain) sobrevive reinicio.
## Definition of Done
Triada obligatoria (ver `.claude/rules/dod_quality.md`). Sin las 3 capas + 0 anti-criterios el flow NO se mueve a `completed/`.
### Mecanica (pre-requisito)
- [ ] **Build device_agent**: `cd apps/device_agent && CGO_ENABLED=0 GOOS=linux go build -o device_agent .` exit 0; cross-compile `GOOS=windows` + `GOOS=android GOARCH=arm64` tambien verdes.
- [ ] **Build agents_and_robots + agents_dashboard**: `./fn run redeploy_cpp_app_windows agents_dashboard apps/agents_dashboard --build` + Go build de `agents_and_robots` exit 0.
- [ ] **Tests unitarios funciones nuevas verdes**: `CGO_ENABLED=1 go test -tags fts5 -count=1 ./functions/infra/...` cubriendo wg_*, capability_*, enrollment_*, device_audit_*. Lista de IDs en `## Notas`.
- [ ] **`./fn index`** sin warnings nuevos tras anadir las ~20 funciones.
- [ ] **`./fn doctor unused --json | jq '.[]|select(.id|startswith("wg_"))'`** vacio (las wg_* tienen consumidores reales).
- [ ] **`./fn doctor uses-functions`** verde para `apps/device_agent/app.md`, `apps/wg_hub/app.md`, `apps/agents_dashboard/app.md`.
- [ ] **`./fn doctor services-spec`** verde para `wg_hub.service` y `device_agent.service` con bloque service: completo.
### Cobertura de comportamiento
Minimo: golden + 8 edge/error documentados aqui con assert ejecutable. Cada uno deja entry en `e2e_runs` de la app afectada (`apps/device_agent/operations.db`, `apps/wg_hub/operations.db`).
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: comando whitelist OK | e2e | Element `!exec ls /home/lucas` en `#dev-home-wsl` | output `ls` en <3s, entry en `device_audit` con hash valido |
| Edge: comando NO whitelist rechazado | e2e | Element `!exec rm -rf /` | reply `capability rejected: shell.exec.rm not in manifest`; entry `device_audit` status=`rejected_capability` |
| Edge: capability fuera de manifest | e2e | Element `!camera.snapshot` en device sin esa capability | reply `capability not in manifest`; alerta a `#operator-approvals` |
| Edge: replay nonce viejo | e2e | reenviar mismo envelope con nonce ya visto (cmd test: `device_agent --replay-test <envelope.json>`) | rechazo + log `nonce_replay`; entry `device_audit` status=`rejected_nonce` |
| Edge: ed25519 manifest invalido | e2e | servir manifest firmado por clave que no es operator; `device_agent` lo recibe en enrollment | `device_agent` rechaza + no instala wg_peer; hub log muestra `manifest_invalid_signature` |
| Edge: token enrollment expirado | e2e | `enrollment_token_create` con TTL=1s, esperar 5s, `POST /enroll` | hub responde 401 `token_expired`; cmd `curl ...` exit != 0 |
| Approval flow honrado | e2e | Element `!fs.write /tmp/x hello` (requires_approval=true); operador hace 👍 en `#operator-approvals` | exec ocurre SOLO tras approval; sin approval no escribe; entry `device_audit` con `approval_msg_id` |
| Approval flow no se salta | e2e | Forzar via API directa salto del approval queue (test negativo: cmd `curl --data ...` directo al device) | device rechaza + log; sin approval_msg_id en envelope = rechazo |
| Mesh-down handled | e2e | `wg-quick down wg0` en hub mientras device manda comando | device entra en `degraded`, comando encolado o respuesta `mesh_unreachable`; al volver hub: handshake reanuda, cola se vacia |
| Dos devices simultaneos sin interferencia | e2e | `home-wsl` y `pc-aurgi` ejecutan capabilities en paralelo (script python con 2 threads) | cada audit chain es independiente, sin cross-contamination; `device_audit` muestra 2 chains separadas, hash chain valido en cada una |
| Audit chain valida tras restart | e2e | matar `device_agent` mid-flight (`kill -9`) + relanzar; `cmd: device_audit_verify_chain --device home-wsl` | chain integra, hash anterior coincide, sin huecos |
| Revoke device <5s | e2e | desde `agents_dashboard` panel "Mesh" boton "Revoke home-wsl"; medir tiempo hasta `wg show` no liste peer | peer ausente en <5s; siguientes comandos a `#dev-home-wsl` -> `peer_revoked` |
**Regla**: cada fila genera `e2e_check` en `app.md` correspondiente (issue 0068). `fn-analizador` los corre periodicamente.
### Vida util validada
| Metrica | Umbral | Donde se observa | Ventana |
|---|---|---|---|
| Peers vivos en mesh | `>=2` constantes (home-wsl + pc-aurgi) | `agents_dashboard` panel "Mesh" (last_handshake < 3min) | 7 dias |
| Crashes `device_agent` | `0` | `journalctl --user -u device_agent.service` en cada device | 7 dias |
| Crashes `wg_hub` | `0` | `ssh vps journalctl -u wg_hub.service` | 7 dias |
| Huecos en audit chain | `0` | `cmd: device_audit_verify_chain --all` | continuo |
| Rollback de wg config | `0 ocurrencias` | hub: `git -C /etc/wireguard status` debe ser clean; sin restore manual | 7 dias |
| Handshake fail rate | `<5%` | `wg show all dump` parseado por `agents_dashboard` | 7 dias |
| Approval queue stuck | `0 pendientes >24h` | `agents_dashboard` panel "Approvals" | continuo |
| Comandos exec latencia p95 | `<3s` | `call_monitor.function_stats` para `capability.shell.exec` | 7 dias |
| Replay attacks bloqueados | `>=1 detectado y bloqueado` (pen-test real) | `device_audit` status=`rejected_nonce` count | 30 dias |
### User-facing (reforzado)
- [ ] **User-facing surface**: humano abre Element en movil/web (`element.organic-machine.com`), entra a `#dev-<nombre>` y escribe comandos. Output en el mismo room. NO en una BD, NO en un log.
- [ ] **User-facing usage real**: el operador (humano) usa Element con `home-wsl` Y `pc-aurgi` (>=2 maquinas reales), **>=1 sesion/dia durante >=7 dias consecutivos**, **>=20 comandos totales** repartidos entre devices.
- [ ] **User-facing variado**: cubre capabilities de **>=4 tipos**: read (`!fs.read`, `!ls`), write (`!fs.write`), exec (`!exec`), approval-required (`!fs.write` en path sensible), docker (`!docker exec`).
- [ ] **User-facing onboarding**: parrafo en `## Notas` con pasos numerados: abrir Element -> entrar a room -> `!help` -> ejemplo de comando. Sin leer el flow entero.
- [ ] **User-facing latencia**: tras enviar mensaje en Element, output visible en <3s (read/exec) o <5s (con approval) — medido y registrado en `## Notas`.
### Anti-criterios (invalidan DoD aunque checkboxes verdes)
- [ ] **Solo-en-home-wsl**: el flow funciona en mi WSL pero falla en `pc-aurgi` u otro device fisico.
- [ ] **device_agent muere cada noche**: cualquier crash recurrente del proceso device_agent en los 7 dias de validacion.
- [ ] **Approval flow se salta**: alguna entrada en `device_audit` con capability `requires_approval=true` ejecutada sin `approval_msg_id` valido.
- [ ] **Audit chain rota**: `device_audit_verify_chain` reporta huecos o hash mismatch en algun device.
- [ ] **wg config drift**: cambios manuales en `/etc/wireguard/wg0.conf` del hub sin pasar por `wg_peer_add/remove/revoke`. Git status muestra cambios sin trackear.
- [ ] **Dashboard fantasma**: `agents_dashboard` declarado pero el operador no lo abre durante la ventana de 7 dias. Telemetria muerta.
- [ ] **Pen-test no ejercitado**: replay attack / capability fuera de manifest / token expirado declarados pero sin entry real en `device_audit` con status `rejected_*` en los 7 dias.
- [ ] **Silent-fail**: peer cae >24h y nadie se entera (sin alerta a `#operator-approvals` ni badge rojo en dashboard).
- [ ] **Secrets en repo**: cualquier hit de `git grep -E 'PrivateKey|PSK|operator/ed25519' -- ':!*.md'` en cualquier rama.
### Custom (security-specific, deben tener evidencia en `device_audit`)
- [ ] _(custom)_ Pen-test capability fuera de manifest: entry `device_audit` status=`rejected_capability` ejercitado intencionalmente >=1 vez.
- [ ] _(custom)_ Pen-test replay: entry `device_audit` status=`rejected_nonce` ejercitado >=1 vez con cmd reproducible.
- [ ] _(custom)_ Stale device: forzar `home-wsl` offline >24h, verificar badge `stale` en `agents_dashboard` + mensaje en `#operator-approvals`.
- [ ] _(custom)_ Operator key rotation: ejecutar rollover de la clave ed25519 maestra + revoke-all + re-enroll, sin perder audit chain historica. Documentado en `## Notas`.
## Telemetria esperada
- `call_monitor.calls`: cada `wg_*`, `capability.*`, `docker.*` con duration_ms, success.
- `apps/wg_hub/operations.db`: tabla `wg_peers` + `device_audit` (hash-chained append-only).
- `apps/agents_and_robots/operations.db`: tabla `matrix_capability_dispatches`.
- `apps/agents_dashboard/local_files/agents_dashboard.db`: cache devices + approval queue.
- Dashboards visibles: `agents_dashboard` panel "Mesh" (peers vivos + last handshake + bytes rx/tx).
- Matrix room `#operator-approvals` recibe cada approval_request.
- Element en movil aprueba/rechaza con reacciones (👍/👎) o comando `!approve <id>`.
## Riesgos / gotchas
- **VPS UDP/51820**: firewall del proveedor del VPS puede bloquearlo. Verificar con `nc -u -v vps 51820`.
- **NAT carrier-grade (4G/5G)**: device tras NAT estricto → `PersistentKeepalive = 25` obligatorio.
- **Sleep laptop / android doze**: handshake muere. Auto-reconnect via `systemd-networkd-wait-online` + script.
- **Privilegio sudo**: `wg-quick` requiere root. Devices necesitan sudo-NOPASSWD para `wg-quick@wg0`.
- **Clock skew**: tokens enrollment + nonces dependen de NTP. Forzar `chrony` en VPS y devices.
- **Container privileged**: modo "deep" docker requiere `--cap-add NET_ADMIN`. Riesgo si container compromised.
Mitigacion: solo modo "deep" para containers de tu propio control (ej. `agents_and_robots` self-hosted), no third-party.
- **Operator key compromise**: si tu ed25519 leaks → cualquiera firma manifests. Plan B: rotacion + revoke-all + re-enroll.
- **Matrix homeserver compromise**: chat E2EE protege contenido, pero metadata (quien habla con quien) leak.
Aceptable porque homeserver es tuyo en `organic-machine.com`.
## Notas
(rellenar tras ejecutar fases A/C/B/D)
### Para hablar con un device desde Element (onboarding)
1. Abre Element en movil o web (`element.organic-machine.com`).
2. Entra al room `#dev-<nombre>` (un room por device).
3. Escribe `!help` → bot del room (`agents_and_robots`) responde con capability matrix del device.
4. Escribe comando, ej. `!exec ls /home/lucas` o `!fs.read /var/log/syslog`.
5. Si capability requiere approval, te llega notification a `#operator-approvals` → reaccionas 👍 → ejecuta.
6. Output aparece en el mismo room del device.
### Para hablar con un container docker
1. Si el host del container ya esta en la mesh: room `#dev-<host>` con `!docker exec <container> <cmd>`.
2. Modo deep: room dedicado `#cont-<container>` (solo containers enrolled).
+1
View File
@@ -12,6 +12,7 @@ Tabla de casos de uso multi-app. Mantenida por `/flow create` y `/flow done`.
| [0006](0006-metabase-versioning.md) | metabase-versioning | gitops | auto_metabase, dag_engine | pending | medium | 0% | 2026-05-16 |
| [0007](0007-matrix-telemetry-bot.md) | matrix-telemetry-bot | event-driven | data_factory, dag_engine, call_monitor, agents_and_robots | pending | low | 0% | 2026-05-16 |
| [0008](0008-kanban-cpp-and-agent-workflows.md) | kanban-cpp-and-agent-workflows | realtime-loop | kanban_cpp, kanban, skill_tree, agent_runner_api | pending | medium | 0% | 2026-05-18 |
| [0009](0009-agentes-dispositivos-mesh.md) | agentes-dispositivos-mesh | event-driven | agents_dashboard, agents_and_robots, wg_hub, device_agent | pending | high | 0% | 2026-05-23 |
## Leyenda
+92 -25
View File
@@ -12,49 +12,116 @@ Un flow describe una secuencia de pasos que atraviesa varias apps (`navegator_da
- **Definition of Done OBLIGATORIA** — ver seccion abajo. Sin DoD el flow NO puede crearse.
- Cerrados se mueven a `completed/`.
## Definition of Done (OBLIGATORIA)
## Definition of Done (OBLIGATORIA — triada)
Cada flow al crearse DEBE declarar un bloque `## Definition of Done` distinto de `## Acceptance`. Sin el, `/flow create` rechaza el scaffold y `/flow done` rechaza el cierre.
**Diferencia:**
**Regla absoluta**: DoD no es checkbox que se marca a mano. Cada item lleva **evidencia ejecutable** (comando, e2e_check, dashboard URL con datos frescos, log query, screenshot link). Si no puedes probarlo, no es DoD: es deseo. Ver `.claude/rules/dod_quality.md` para la regla completa.
**Diferencia con `## Acceptance`:**
| `## Acceptance` | `## Definition of Done` |
|---|---|
| Checks task-level del flow (ejecucion concreta) | Contrato global de calidad para considerar el flow CERRADO |
| Pueden quedar `[ ]` mientras iteras | TODOS deben estar `[x]` antes de mover a `completed/` |
| Verifica que el flow CORRE | Verifica que el flow es REPETIBLE, OBSERVABLE y MANTENIBLE |
| Checks task-level del flow (el flow corre una vez) | Contrato global de calidad: el flow sobrevive uso real |
| Pueden quedar `[ ]` mientras iteras | TODAS las capas verdes con evidencia antes de mover a `completed/` |
| Verifica que el flow CORRE | Verifica que el flow es REPETIBLE, OBSERVABLE, MANTENIBLE y USADO |
**Plantilla minima de DoD** (anadir/ajustar segun flow):
### Las 3 capas obligatorias
**1. Mecanica** (pre-requisito, NO es DoD por si misma):
Build verde, tests verdes, `fn index` limpio, `fn doctor` verde, `uses_functions` sin drift. Hacer compilar la cosa NO es haberla terminado.
**2. Cobertura de comportamiento**:
Tabla `escenario | tipo | comando | resultado esperado`. Minimo 1 golden + 2 edge + 1 error path con assert real, no smoke "no peto". Cuando aplique, las pruebas dejan entry en `e2e_runs` de la app afectada.
**3. Vida util validada**:
Tabla `metrica | umbral | dashboard | ventana`. El flow sobrevive **>=7 dias de uso real** sin romperse silenciosamente. Crashes = 0, huecos en audit chains = 0, error_rate < umbral declarado, dashboard observable abierto periodicamente. **El humano usa la cosa en su PC, en su contexto real, >=N veces variadas, no en sandbox aislado**.
### Plantilla obligatoria
Ver `template.md` para el esqueleto completo. Bloques:
```markdown
## Definition of Done
- [ ] **Repetibilidad**: el flow corre N veces consecutivas (N declarado en el flow, default 3) sin intervencion manual.
- [ ] **Observabilidad**: queda trazado en `call_monitor.calls` + `data_factory.runs` + dashboard correspondiente.
- [ ] **Error-path**: al menos 1 modo de fallo probado y manejado (no crash silencioso).
- [ ] **Idempotencia**: re-ejecutar no duplica datos ni rompe estado (clave en sinks).
- [ ] **Secrets**: cero credenciales en disco fuera de `pass`/vaults; cero datos sensibles fuera de `risk` declarado.
- [ ] **Docs**: `## Notas` rellenado con hallazgos reales + comandos para reproducir.
- [ ] **Registry-first**: todas las piezas reutilizables existen como funciones del registry (no inline en apps).
- [ ] **INDEX + status**: `status: done` en frontmatter + fila actualizada en `INDEX.md` + archivo movido a `completed/`.
### Mecanica
- [ ] Build verde (`cmd: ...`)
- [ ] Tests verdes (`cmd: ...`)
- [ ] fn index limpio
- [ ] fn doctor verde
- [ ] uses_functions auditado
### Cobertura de comportamiento
| Escenario | Tipo | Comando | Resultado esperado |
|---|---|---|---|
| Golden: ... | unit/e2e | `cmd` | output concreto |
| Edge 1: ... | unit/e2e | `cmd` | comportamiento concreto |
| Error 1: ... | e2e | `cmd que rompe` | fallo manejado, no crash |
| Error 2: ... | e2e | `cmd` | degradacion graceful + log |
### Vida util validada
| Metrica | Umbral | Donde se observa | Ventana |
|---|---|---|---|
| <metrica> | `>=N` | `<dashboard URL>` | 7 dias |
| crashes | `0` | `journalctl ...` | 7 dias |
### User-facing (reforzado)
- [ ] User-facing surface (lugar concreto, NO BD ni log).
- [ ] User-facing usage real: >=N veces en >=7 dias, en PC real, con inputs reales.
- [ ] User-facing variado: >=3 capabilities/casos distintos.
- [ ] User-facing onboarding (parrafo en `## Notas`).
- [ ] User-facing latencia <X medida.
### Anti-criterios (invalidan DoD aunque checkboxes verdes)
- [ ] solo-en-mi-PC
- [ ] solo-en-sandbox-vacio
- [ ] camino feliz unico (error paths declarados pero nunca ejercitados)
- [ ] dashboard fantasma (no abierto en >30 dias)
- [ ] self-test sin asserts
- [ ] silent-fail
- [ ] approval saltado
```
Cada flow puede anadir DoD especificos al dominio (ej. `bbva-movimientos`: "datos NUNCA cruzan a registry.organic-machine"). El bloque DoD se **versiona con el flow** — un cambio de DoD requiere bump de `updated:` en frontmatter.
### Reglas duras para marcar `status: done`
### User-facing surface (sub-bloque OBLIGATORIO dentro de DoD)
`/flow done` rechaza el cierre si:
"DoD verde" sin valor visible al humano = plumbing limpio sin razon de existir. Cada DoD DEBE incluir, al menos, estos cuatro checks tipo `User-facing`:
1. Falta alguna de las 3 capas (mecanica + cobertura + vida).
2. En Cobertura: <1 golden, <2 edge, <1 error path con evidencia.
3. En Vida util: tabla vacia o sin dashboard observable real.
4. User-facing usage real <7 dias o <N usos declarados.
5. Cualquier anti-criterio marcado como cierto.
6. `## Notas` sin parrafo onboarding.
7. Algun item sin comando/URL/log query — solo texto.
```markdown
- [ ] **User-facing**: <accion concreta del humano + lugar exacto donde ve/usa el output>.
- [ ] **User-facing repeat**: el humano vuelve manana al mismo lugar y ve datos frescos sin conocer el flow.
- [ ] **User-facing onboarding**: parrafo en `## Notas` que explica "para ver/usar esto: hacer X" — sin leer el flow.
- [ ] **User-facing latencia**: el humano percibe el cambio en <X segundos|minutos> tras el evento (X declarado por flow).
```
Cada flow puede anadir DoD especificos al dominio. El bloque DoD se **versiona con el flow** — un cambio de DoD requiere bump de `updated:` en frontmatter.
Regla: si la respuesta a "donde lo ves" es "en una BD" o "en un log" -> NO vale. Tiene que ser una superficie usada por el humano (UI de una app, sala Matrix, dashboard, Metabase card, repo Gitea, archivo en vault abierto a mano). Si el output solo lo consume otra app/flow, esa app/flow es quien debe declarar su propia user-facing surface.
### User-facing surface (regla complementaria)
`/flow done` rechaza el cierre si falta alguno de los 4 user-facing checks o si `## Notas` no contiene parrafo onboarding.
Si la respuesta a "donde lo ves" es "en una BD" o "en un log" -> NO vale. Tiene que ser una superficie usada por el humano (UI de app, sala Matrix, dashboard, Metabase card, repo Gitea, archivo en vault abierto a mano). Si el output solo lo consume otra app/flow, esa app/flow declara su propia user-facing surface.
### Antipatrones documentados
| Antipatron | Por que es malo |
|---|---|
| Marcar `done` porque pasa una vez | Tarea "hecha" se rompe al primer uso real |
| Checkbox sin evidencia ejecutable | DoD se convierte en placebo, no en gate |
| Test que solo verifica camino feliz | El error path es donde se pierden datos en produccion |
| Observabilidad declarada pero dashboard no abierto en 30 dias | Telemetria muerta = ceguera |
| "Repetible 3 veces consecutivas" con BD efimera | No prueba comportamiento sobre datos reales acumulados |
| Aprobacion saltada en algun camino | Security gate roto pero invisible |
| Error path manejado solo "en teoria" | Cuando ocurra en produccion el manejo no existe |
### Validacion programatica de DoD (TBD)
`/flow done` ejecuta checks programaticos:
- Parsea bloques `### Mecanica`, `### Cobertura`, `### Vida util`, `### User-facing`, `### Anti-criterios`.
- Verifica que cada item tiene `cmd:` / URL / log query / e2e_check_id asociado.
- Cuenta filas en Cobertura: >=1 golden + >=2 edge + >=1 error.
- Cruza con `e2e_runs` y `call_monitor.calls` para confirmar evidencias en BDs reales.
- Aborta cierre si falta cobertura o algun anti-criterio esta marcado.
Hoy parte de esto es manual (revision humana). Ver `audit_dod_schema_go_infra` (issue 0114) + `fn doctor dod`.
### DoD evidence schema (issue 0114, opcional)
+60 -14
View File
@@ -68,28 +68,74 @@ Pasos numerados. Cada paso puede ser:
## Acceptance
Checks task-level del flow — verifican que el flow CORRE una vez. Pueden quedar `[ ]` mientras iteras. NO sustituyen a la DoD.
- [ ] Criterio 1.
- [ ] Criterio 2.
## Definition of Done
Contrato global de cierre. TODOS marcados antes de mover a `completed/`. Ver README.md seccion "Definition of Done".
**Filosofia triada (ver `.claude/rules/dod_quality.md`):** DoD no es checkbox que se marca a mano. Cada item debe llevar **evidencia ejecutable** (comando, e2e_check, screenshot link, dashboard URL con datos frescos, log query). Si no puedes probarlo, no es DoD: es deseo. Las 3 capas son obligatorias.
- [ ] **Repetibilidad**: corre 3 veces consecutivas sin intervencion manual.
- [ ] **Observabilidad**: trazado en `call_monitor.calls` + `data_factory.runs` + dashboard relevante.
- [ ] **Error-path**: >=1 modo de fallo probado y manejado.
- [ ] **Idempotencia**: re-ejecucion no duplica ni corrompe sinks.
- [ ] **Secrets**: cero credenciales fuera de `pass`/vaults; risk declarado coincide con datos reales.
- [ ] **Docs**: `## Notas` con hallazgos + comandos reproducibles.
- [ ] **Registry-first**: piezas reutilizables viven como funciones del registry.
- [ ] **INDEX + status**: `status: done` + `INDEX.md` actualizado + movido a `completed/`.
### Mecanica (pre-requisito, NO sustituye al resto)
### User-facing (obligatorio)
Construir verde no es estar hecho. Es la base para empezar a probar.
- [ ] **User-facing**: <accion concreta del humano + lugar exacto donde ve/usa el output>.
- [ ] **User-facing repeat**: humano vuelve manana al mismo lugar, ve datos frescos sin conocer el flow.
- [ ] **User-facing onboarding**: parrafo en `## Notas` explica "para ver/usar esto: hacer X" sin leer el flow.
- [ ] **User-facing latencia**: humano percibe el cambio en <Xs|Xmin> tras el evento (X declarado).
- [ ] **Build verde** (`cmd: <comando build>`).
- [ ] **Tests unitarios verdes** (`cmd: <comando test>`, listar IDs/paths).
- [ ] **`fn index` limpio** (sin warnings nuevos).
- [ ] **`fn doctor` verde** en artefactos tocados (`cmd: ./fn doctor --json | jq ...`).
- [ ] **`uses_functions` auditado** (sin drift en `app.md` vs imports reales).
### Cobertura de comportamiento
Cada escenario debe tener una prueba ejecutable con assert real (no smoke "no peto"). Anadir tantas filas como casos relevantes — golden path + edge cases + error paths.
| Escenario | Tipo de prueba | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden path: <descripcion> | unit / e2e / manual | `<cmd>` | <output concreto, no "ok"> |
| Edge case 1: <input limite> | unit / e2e | `<cmd>` | <comportamiento concreto> |
| Edge case 2: <estado raro> | unit / e2e | `<cmd>` | <comportamiento concreto> |
| Error path 1: <fallo esperado> | e2e | `<cmd que provoca fallo>` | <fallo manejado, no crash> |
| Error path 2: <recursos caidos> | e2e | `<cmd>` | <degradacion graceful + log> |
**Regla**: al menos 1 golden + 2 edge + 1 error path. Tests inscritos en `e2e_runs` de la app correspondiente cuando aplique.
### Vida util validada
El flow no esta hecho hasta que sobrevive **uso real** durante >=7 dias sin romperse silenciosamente. Cada metrica tiene umbral medible y dashboard observable.
| Metrica | Umbral | Donde se observa | Ventana |
|---|---|---|---|
| <metrica 1, ej. handshakes vivos> | `>=N` | `<dashboard URL / app panel>` | 7 dias |
| <metrica 2, ej. error_rate> | `<X%` | `<dashboard URL>` | 7 dias |
| <metrica 3, ej. duracion p95> | `<Xms` | `call_monitor.function_stats` | 30 dias |
| crashes del proceso | `0` | `journalctl -u <unit>` | 7 dias |
| huecos en audit chain | `0` | `cmd: <verify chain>` | continuo |
**Regla**: las metricas NO se autoreportan en el flow; las lee el operador del dashboard real. Si el dashboard no existe, el item se invalida.
### User-facing (reforzado)
"DoD verde" sin uso humano real = plumbing limpio sin razon de existir.
- [ ] **User-facing surface**: <accion concreta del humano + lugar exacto donde ve/usa el output (NO una BD, NO un log)>.
- [ ] **User-facing usage real**: el humano (operador) usa la cosa en **su PC, en su contexto real**, **>=N veces en >=7 dias**, con inputs reales (no demo, no sandbox).
- [ ] **User-facing variado**: cubre >=3 capabilities/casos distintos (no solo "abre dashboard y mira").
- [ ] **User-facing onboarding**: parrafo en `## Notas` que explica "para ver/usar esto: hacer X" sin leer el flow.
- [ ] **User-facing latencia**: percepcion del cambio <Xs|Xmin> tras el evento (X declarado, medido).
### Anti-criterios (invalidan la DoD aunque los checkboxes esten verdes)
Marca el flow como **NO done** si cualquiera de estas condiciones es cierta:
- [ ] **Solo-en-mi-PC**: el flow funciona en `home-wsl` pero falla en `pc-aurgi` u otro PC del operador.
- [ ] **Repetible-en-sandbox-vacio**: solo pasa con BD limpia / cuenta limpia / sin datos historicos.
- [ ] **Camino feliz unico**: los error paths fueron declarados pero NUNCA se ejercitaron (sin entry en `e2e_runs` o logs reales).
- [ ] **Dashboard fantasma**: el dashboard declarado en "Vida util" no se ha abierto en >30 dias.
- [ ] **Self-test que no apesta**: el `e2e_check` retorna `pass` sin verificar nada material (no asserts).
- [ ] **Silent-fail**: el proceso muere/degrada sin alerta visible al operador.
- [ ] **Approval saltado**: alguna capability con `requires_approval=true` fue ejecutada sin el approval flow.
### Custom (opcional, dominio-especifico)