--- 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/` — 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 `) | 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-` 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 `. ## 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-` (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-` con `!docker exec `. 2. Modo deep: room dedicado `#cont-` (solo containers enrolled).