From 36eb3dc3a731f0b7e5164b0b05a687798b4408cd Mon Sep 17 00:00:00 2001 From: Eratostenes de Gitjabia Date: Sat, 9 May 2026 18:52:52 +0000 Subject: [PATCH] docs(backlog): Blocao sprint backlog (8 epics, 60+ stories) --- backlog/blocao-sprint-backlog.md | 779 +++++++++++++++++++++++++++++++ 1 file changed, 779 insertions(+) create mode 100644 backlog/blocao-sprint-backlog.md diff --git a/backlog/blocao-sprint-backlog.md b/backlog/blocao-sprint-backlog.md new file mode 100644 index 0000000..0a35c23 --- /dev/null +++ b/backlog/blocao-sprint-backlog.md @@ -0,0 +1,779 @@ +# Blocao · Sprint backlog + +**Producto**: Blocao +**Empresa**: Blocao Labs +**Stack**: OpenWrt (router) + Balena/RK3588 (cell) + EU sovereign hub (futuro) +**Estado del documento**: backlog inicial post-mockups · pre-sprint 0 + +--- + +## Cómo leer este documento + +Cada user story tiene la forma: + +> `[BL-NN]` **Como** ``, **cuando** ``, **veo / puedo / espero** ``. +> *Backend*: contra qué APIs habla la UI. +> *Tests de aceptación*: qué tiene que pasar para considerarla cerrada. +> *Estimación*: S (≤1 día), M (2-3 días), L (4-7 días), XL (>1 semana). +> *Depende de*: otras stories que tienen que estar antes. + +Roles: +- **Operator** — usuario final del sistema (vigilancia, forensics, casos). +- **Installer** — quien despliega un sitio físicamente. +- **Ops/SRE** — quien mantiene la flota desde el hub. +- **Investigator** — operador con caso abierto, foco forense. +- **End-customer** — propietario del sitio, no toca la consola pero pide informes. + +--- + +## Épicas / sprints sugeridos + +| Sprint | Épica | Foco | Duración | +|---|---|---|---| +| 0 | Foundation | repositorio, CI, image builder OpenWrt, balena-compose Cell | 2 sem | +| 1 | First boot | wizard funcional + provisioning end-to-end | 2 sem | +| 2 | Engineering plane | router APIs, paneles WAN/VLAN/MQTT/GitOps reales | 3 sem | +| 3 | Camera onboarding | scan + auth + Frigate config GitOps | 2 sem | +| 4 | Operator plane | SYNOPSIS + Forensics MVP con un solo edge AI | 3 sem | +| 5 | Health & ops | selftest + observability + Cell↔Router orchestration | 2 sem | +| 6 | Hub & fleet | sites overview + bridge MQTT + remote auth | 3 sem | +| 7 | Hardening | evidence chain capa 1 + auditoría + rebranding final | 2 sem | + +Total: **~19 semanas** hasta producto demoable a clientes con flota. + +--- + +# ÉPICA 0 — FOUNDATION + +Antes de UI: la infraestructura sobre la que todo se monta. + +### `[BL-00.1]` Repo monorepo con estructura clara — *L* + +**Como** team, **necesito** un monorepo con paquetes separados pero con tooling compartido (lint, format, CI). + +Estructura propuesta: +``` +blocao/ +├── apps/ +│ ├── console/ # SPA (este index.html eventualmente migrado a React/Svelte) +│ ├── router-luci-app/ # paquete OpenWrt luci-app-blocao-console +│ ├── cell-services/ # docker-compose Balena del Cell +│ └── hub/ # backend del hub (Go o Rust) +├── packages/ +│ ├── design-tokens/ # paleta complementary, tipos, espacios +│ ├── ui-components/ # web components reutilizables +│ └── proto-mqtt/ # esquemas de topics/payloads en JSON Schema +├── infra/ +│ ├── image-builder/ # OpenWrt Image Builder config +│ ├── balena/ # balenaCloud manifests +│ └── hub-deploy/ # terraform/ansible para Hetzner +├── docs/ +│ └── ... +└── tools/ +``` + +**Tests de aceptación**: clone, `make bootstrap`, `make build` produce artefactos de los 4 apps en CI. +**Depende de**: nada. + +### `[BL-00.2]` CI/CD pipeline base — *M* + +**Como** team, **quiero** que cada PR pase lint + tests + build de los apps que toca, y que tags publiquen artefactos. + +**Tests de aceptación**: PR con cambio en `apps/console/` solo dispara su pipeline; tag `v0.1.0` publica imagen Balena, paquete OpenWrt, y artefacto del hub. +**Depende de**: BL-00.1. + +### `[BL-00.3]` Image Builder OpenWrt con paquete blocao base — *L* + +**Como** installer, **quiero** poder flashear un router con firmware Blocao listo (sin necesidad de instalar paquetes manualmente al primer arranque). + +Incluye: +- `luci-app-blocao-console` (vacío al inicio, irá creciendo) +- `mosquitto`, `mosquitto-bridges` +- `chrony` con NTS +- `tailscale` +- `mwan3` para WAN failover +- `git`, `ucode`, `rpcd`, `uhttpd-mod-ubus` +- defaults sensatos en `uci-defaults` + +**Tests de aceptación**: imagen flashea en GL.iNet GL-MT6000 o equivalente; arranque inicial llega a `http://blocao-router.local/` y se ve el wizard. +**Depende de**: BL-00.1. + +### `[BL-00.4]` Balena fleet provisioning para Cell — *M* + +**Como** ops, **quiero** que un RK3588 nuevo enrolado a la fleet de Balena descargue y arranque el stack Blocao Cell automáticamente. + +Stack `docker-compose.yml`: +- `frigate` (custom build con RKNN) +- `enricher` (genera embeddings CLIP, reID, hashing) +- `forensic-engine` (API de query natural language → fusion) +- `healthd` (selftest + report al MQTT) +- `console-static` (sirve SPA) +- `caddy` (reverse proxy + TLS) + +**Tests de aceptación**: device nuevo en fleet recibe los containers en 10min, `mosquitto_sub -t 'hai/_registry/announce' -h ` muestra anuncio del nuevo Cell. +**Depende de**: BL-00.1, BL-00.3. + +### `[BL-00.5]` Esquemas MQTT de topics y payloads — *M* + +**Como** team, **necesito** un contrato versionado entre Cell, Router, Hub para que no haya ambigüedad sobre qué se publica y bajo qué topic. + +Documentado como JSON Schema en `packages/proto-mqtt/`: +- `hai//announce` +- `hai//health` +- `hai//events/` +- `hai//state/` +- `hai/_registry/heartbeat/` +- `_cmd/` +- ...todos los demás del panel MQTT + +**Tests de aceptación**: contract tests en CI que validan que los publishers cumplen el schema. +**Depende de**: BL-00.1. + +--- + +# ÉPICA 1 — FIRST BOOT + +El wizard que ya tienes prototipado, pero funcional. + +### `[BL-01.1]` Wizard step 1 · Region — *S* + +**Como** installer, **al primer arranque** del router, **veo** el wizard y selecciono país. + +Backend: `POST /api/router/setup/region {country, retention_days, timezone, nts_servers}` → escribe a UCI y al repo GitOps local. +**Tests de aceptación**: seleccionar Argentina configura `chrony` con `time.cloudflare.com` + `ntp.argentina.gob.ar`, timezone UTC-3, retention default 30d en Frigate config plantilla. +**Estimación**: S (1d). +**Depende de**: BL-00.3. + +### `[BL-01.2]` Wizard step 2 · Site identity — *S* + +**Como** installer, **introduzco** site ID, label humano, dirección, coordenadas. + +Backend: `POST /api/router/setup/site` → escribe `/etc/blocao/site.yml`, comitea al GitOps repo, propaga a hostname y MQTT topic prefix. +**Tests de aceptación**: site ID `BL-LAB` aparece como prefix en `mosquitto_sub`, hostname del router es `bl-lab-router.local`. +**Estimación**: S (1d). +**Depende de**: BL-01.1. + +### `[BL-01.3]` Wizard step 3 · WAN con tests inline — *M* + +**Como** installer, **selecciono** modo WAN y veo tests en directo (DHCP, DNS, ping, NTS, hub reachability). + +Backend: `POST /api/router/setup/wan` aplica config; `GET /api/router/setup/wan/test` corre los chequeos en streaming SSE. +**Tests de aceptación**: cambiar de DHCP a IP estática y volver no rompe la sesión actual del wizard (confirma rollback de seguridad). +**Estimación**: M (2-3d). +**Depende de**: BL-01.2. + +### `[BL-01.4]` Wizard step 4 · Camera VLAN quarantine — *M* + +**Como** installer, **confirmo** la VLAN-10 de cámaras, **veo** las 5 reglas de cuarentena ya aplicadas como verde-en-vivo. + +Backend: aplica firewall rules a VLAN-10; ejecuta `egress test` desde un namespace network simulando una cámara. +**Tests de aceptación**: el egress test desde dentro del namespace VLAN-10 falla cuando intenta resolver `google.com`; pasa cuando intenta llegar a la IP del Cell por puerto 554. +**Estimación**: M (2d). +**Depende de**: BL-01.3. + +### `[BL-01.5]` Wizard step 5 · Hub & operator auth — *M* + +**Como** installer, **pego** el fleet token del hub y **creo** el usuario operador inicial. + +Backend: `POST /api/router/setup/hub` valida token contra hub, intercambia por mTLS cert; `POST /api/router/setup/operator` crea bcrypt + escribe a `/etc/blocao/auth.db`. +**Tests de aceptación**: tras el step, `mosquitto_sub` desde el hub recibe el `_registry/announce` del nuevo router con su site_id. +**Estimación**: M (3d). +**Depende de**: BL-01.4 + un hub mínimo (ver BL-06.1). + +### `[BL-01.6]` Wizard step 6 · Apply provisioning — *M* + +**Como** installer, **veo** el progreso de los 8 pasos de provisioning, **confirmo** que termina, y soy redirigido al Synopsis. + +Backend: orquestador `apply.sh` que ejecuta los 8 pasos (UCI generate, git commit, firewall apply, dnsmasq, hub register, tailscale up, mosquitto, uhttpd reload), reporta progreso por SSE. +**Tests de aceptación**: el provisioning es idempotente (correrlo dos veces no rompe nada); si falla en el paso 5 (hub register), los 1-4 quedan aplicados pero el 6-8 no. +**Estimación**: M (3d). +**Depende de**: BL-01.5. + +### `[BL-01.7]` Standalone mode (sin hub) — *S* + +**Como** installer en sitio air-gapped, **selecciono** "standalone" y el wizard salta el step 5 de hub. + +Backend: skip hub registration, configurar mosquitto sin bridge, permitir auth solo local. +**Tests de aceptación**: provisioning completa sin internet activo; sistema funcional para single-site forensics. +**Estimación**: S (1d). +**Depende de**: BL-01.5. + +--- + +# ÉPICA 2 — ENGINEERING PLANE + +Las vistas que mapean al router. Foco: APIs reales detrás de cada panel. + +### `[BL-02.1]` `hai.uc` — RPCs custom del router en ucode — *L* + +**Como** developer, **necesito** un fichero `/usr/libexec/rpcd/hai.uc` que exponga los métodos que la consola consume sin pasar por LuCI. + +Métodos: +- `hai.overview` (agrega WAN, VLAN, services, drift en una sola respuesta) +- `hai.git_status` (sha applied/HEAD/remote, drift report) +- `hai.git_log` (últimos N commits) +- `hai.git_apply`, `hai.git_rollback`, `hai.git_fetch` +- `hai.cells_list` (registry de Cells descubiertas) +- `hai.cell_proxy` (reverse proxy a Cell por su ID) +- `hai.bridge_status` (mosquitto bridge state) +- `hai.healthcheck` (selftest agregado) +- `hai.cam_scan_vlan10` (devuelve descubrimiento) + +**Tests de aceptación**: `ubus call hai overview` devuelve JSON válido bajo schema. +**Estimación**: L (5-7d). +**Depende de**: BL-00.3. + +### `[BL-02.2]` Reverse proxy `/api/cell/*` y `/api/router/*` — *M* + +**Como** developer, **quiero** que el navegador hable solo con el router (mismo origen) y este reenvíe a la Cell según haga falta. + +Implementación: nginx o Caddy embebido en `luci-app-blocao-console`. `/api/router/*` → `/ubus`. `/api/cell/*` → IP/puerto de la Cell descubierta. `/api/cell//*` para multi-Cell. +**Tests de aceptación**: navegador no hace ninguna petición cross-origin; CORS no aparece nunca. +**Estimación**: M (2-3d). +**Depende de**: BL-02.1. + +### `[BL-02.3]` Vista WAN funcional — *M* + +**Como** ops, **veo** estado WAN en directo y puedo cambiar tipo de conexión. + +UI: la vista WAN actual del index.html (todavía stub). +Backend: `network.interface dump` + `uci.get/set/commit/apply` con timeout rollback de 90s. +**Tests de aceptación**: cambiar DHCP→IP estática y rolllback automático si pierdes la conectividad de la propia consola. +**Estimación**: M (3d). +**Depende de**: BL-02.1, BL-02.2. + +### `[BL-02.4]` Vista VLAN-FW como matriz interactiva — *L* + +**Como** ops, **veo** la matriz de zones×forwardings y puedo tocar reglas con visual feedback. + +UI: implementar la matriz que dibujamos en mockup engineering. Cada celda click → editor de regla. +Backend: parsear `/etc/config/firewall` (UCI), exponer como matriz, escribir cambios via `uci.set + commit + apply`. +**Tests de aceptación**: cambiar `cam→hai` de DENY a ALLOW genera un commit en GitOps; revertir el commit revierte la regla en próximo reconcile. +**Estimación**: L (5d). +**Depende de**: BL-02.3. + +### `[BL-02.5]` Vista MQTT — broker stats + bridge — *L* + +**Como** ops, **veo** estado del broker, clientes conectados, estado del bridge, throughput. + +UI: la vista MQTT prototipada. +Backend: `$SYS/#` topics del propio mosquitto + `mosquitto_ctrl` para clientes; `bridge` info de `mosquitto.conf` parseada. +**Tests de aceptación**: matar un cliente con `kill -9` aparece como disconnected en <2s. +**Estimación**: L (4d). +**Depende de**: BL-02.2. + +### `[BL-02.6]` Vista MQTT — live tail real — *M* + +**Como** ops, **veo** los mensajes pasando por el broker en tiempo real con filtro glob. + +UI: la zona del live tail con SSE/WebSocket en lugar de simulación. +Backend: bridge ws→MQTT, `mosquitto_sub -t '' -v` streaming via WebSocket. Auth: token de la sesión operator. +**Tests de aceptación**: filtro `hai/+/events/#` muestra solo eventos de cámaras; pausa real congela el feed. +**Estimación**: M (3d). +**Depende de**: BL-02.5. + +### `[BL-02.7]` Vista MQTT — topic tree + ACL viewer — *M* + +**Como** ops, **veo** el árbol de topics activos con sus ACLs y políticas de bridge. + +UI: la zona derecha del MQTT panel. +Backend: parsear `mosquitto.conf` ACLs + bridge sections; combinar con stats de mensajes en últimos N minutos. +**Tests de aceptación**: cada topic listado tiene una entrada en el ACL configurado; cualquier topic no listado se considera implícitamente denied. +**Estimación**: M (3d). +**Depende de**: BL-02.5. + +### `[BL-02.8]` Vista GITOPS — triplet + drift detection — *L* + +**Como** ops, **veo** Applied/HEAD/Remote y drift en tiempo real, puedo lanzar fetch/reconcile/rollback. + +UI: el panel GitOps prototipado. +Backend: cron cada 5min ejecuta `git fetch` + comparación de SHAs + comparación SHA256 de ficheros vivos vs aplicados; resultado en `/var/lib/blocao/state-applied.json`. +**Tests de aceptación**: hacer `vi /etc/config/firewall` y guardar (sin git commit) genera drift visible en <30s. +**Estimación**: L (5d). +**Depende de**: BL-02.1. + +### `[BL-02.9]` Vista TAILSCALE — peers, ACLs, status — *S* + +**Como** ops, **veo** peers de Tailscale activos con su última fecha de handshake. + +Backend: `tailscale status --json`. +**Estimación**: S (1d). +**Depende de**: BL-02.2. + +### `[BL-02.10]` Vista SERVICES — lista unificada router+cell — *M* + +**Como** ops, **veo** todos los daemons (router init.d + cell containers) en una sola tabla. + +Backend: `service list` (router) + Cell expone `/api/cell/services` que envuelve `balena-engine ps`. +**Estimación**: M (2d). +**Depende de**: BL-02.2, BL-00.4. + +### `[BL-02.11]` Vista LOGS unificada — *L* + +**Como** ops, **veo** logs de router (logread) + Cell (journalctl/balena logs) en streaming, filtrable por servicio. + +Backend: agregador en Cell que se suscribe a logs del router via SSH/MQTT y los une con sus locales; expone via SSE. +**Tests de aceptación**: filtrar por `frigate` muestra solo líneas del container; filtrar por `firewall` muestra solo del router. +**Estimación**: L (5d). +**Depende de**: BL-02.10. + +--- + +# ÉPICA 3 — CAMERA ONBOARDING + +Los pasos del flow de añadir cámara. + +### `[BL-03.1]` Discovery — escaneo VLAN-10 — *L* + +**Como** installer, **lanzo** el escaneo y **veo** los dispositivos detectados con vendor/modelo/capacidades. + +Backend en router: +- DHCP lease enumeration (`uci show dhcp`). +- ARP table walk (`ip neigh`). +- ONVIF probe via multicast WS-Discovery. +- nmap puertos 554, 80, 443, 8080. +- HTTP fingerprint para identificar vendor (Hikvision, Dahua, Reolink, Axis). +- Match contra BD local de modelos conocidos + CVEs. + +**Tests de aceptación**: 4 cámaras en VLAN-10 son detectadas en <15s con vendor identificado en al menos 3. +**Estimación**: L (5-7d). +**Depende de**: BL-02.1. + +### `[BL-03.2]` Default credentials probe — *M* + +**Como** installer, **veo** automáticamente si la cámara responde a credenciales de fábrica. + +Backend: lista de combinaciones `(user, pass)` típicas por vendor; intenta cada una con timeout corto; reporta resultado. +**Tests de aceptación**: una cámara Hikvision con admin/12345 es marcada como "DEFAULT CREDS" en discovery list. +**Estimación**: M (2d). +**Depende de**: BL-03.1. + +### `[BL-03.3]` Authenticate + force change password — *M* + +**Como** installer, **fuerzo** rotación de password de la cámara antes de añadirla. + +Backend: para cada vendor soportado, implementar API call de change password (Hikvision `/ISAPI/Security/users`, Dahua RPC, Reolink `cmd=Login` + `cmd=SetUser`, ONVIF `SetUser`). +**Tests de aceptación**: tras "force change" la cámara no responde más a la password vieja. +**Estimación**: M (3d). +**Depende de**: BL-03.2. + +### `[BL-03.4]` Test stream + preview — *M* + +**Como** installer, **veo** un frame en directo con detecciones de Frigate antes de guardar. + +Backend: el Cell recibe orden de probar el stream temporalmente, devuelve un frame al router para visualización; ejecuta inferencia ad-hoc para preview. +**Tests de aceptación**: el preview muestra el frame con boxes en menos de 5s desde click. +**Estimación**: M (3d). +**Depende de**: BL-03.3, BL-00.4. + +### `[BL-03.5]` Configure form — name, location, retention, objects — *S* + +**Como** installer, **introduzco** datos humanos de la cámara y la política. + +UI: el form que prototipamos. +Backend: validación de inputs. +**Estimación**: S (1d). +**Depende de**: BL-03.4. + +### `[BL-03.6]` Save — generate Frigate config patch + GitOps commit — *L* + +**Como** installer, **al guardar** la cámara, genera el patch de `frigate.yml`, comitea, y verifica end-to-end. + +Backend: +1. Generar la sección `cameras:` del frigate.yml. +2. Commit local al repo blocao-config. +3. Push a hub si configurado. +4. Trigger reconcile del Cell. +5. Esperar primer evento end-to-end (cámara → Frigate → MQTT → router) para confirmar. + +**Tests de aceptación**: tras "FINISH" del wizard, el primer evento de detección llega al MQTT en <60s. +**Estimación**: L (5d). +**Depende de**: BL-03.5, BL-02.8. + +### `[BL-03.7]` Security checklist — verificación contínua — *M* + +**Como** installer, **veo** el checklist lateral con cada item validado en tiempo real durante el flow. + +Backend: cada check es una función pequeña que el router ejecuta en paralelo: +- VLAN check (siempre OK por design). +- DNS sinkhole (verifica iptables count). +- Default creds (resultado de BL-03.2). +- Telnet open (port scan). +- HTTP/HTTPS (port scan). +- Firmware version vs CVE database (lookup local). +- UPnP (intento + check). +- Phone-home (count durante el periodo de scan). +- MAC binding (check de DHCP static leases). + +**Tests de aceptación**: cada check se actualiza independientemente; un check rojo no bloquea el flow pero lo marca. +**Estimación**: M (3d). +**Depende de**: BL-03.1, BL-03.2. + +### `[BL-03.8]` Add camera manually — *S* + +**Como** installer con cámara rara (sin ONVIF, RTSP path no estándar), **introduzco** datos a mano. + +UI: form alternativo desde el "+ ADD MANUALLY" del discovery. +**Estimación**: S (1d). +**Depende de**: BL-03.5. + +### `[BL-03.9]` CVE database actualizable — *M* + +**Como** ops, **quiero** que la BD de CVEs de cámaras se actualice periódicamente sin firmware update. + +Backend: BD versionada en GitOps repo; `cron` semanal `git pull` la actualiza. Si standalone, advierte de "stale" tras 30 días. +**Tests de aceptación**: añadir un CVE nuevo al repo y `git pull` lo hace disponible para discovery sin restart. +**Estimación**: M (2d). +**Depende de**: BL-03.7. + +--- + +# ÉPICA 4 — OPERATOR PLANE + +SYNOPSIS y FORENSICS, las vistas del operador. + +### `[BL-04.1]` SYNOPSIS — diagrama SCADA estático funcional — *L* + +**Como** operator, **al abrir la consola**, **veo** el estado completo del sitio en una sola pantalla. + +UI: la vista que acabamos de hacer, ya con datos reales. +Backend: `hai.synopsis` agrega: cámaras (estado, fps), router (interfaces, services), cell (containers, NPU, disk), VLANs activas, gauges de capacidad. +**Tests de aceptación**: cargar la vista completa en <500ms; auto-refresh cada 5s. +**Estimación**: L (5d). +**Depende de**: BL-02.1. + +### `[BL-04.2]` SYNOPSIS — animación de flujos en tiempo real — *M* + +**Como** operator, **veo** los flujos animados (RTSP, MQTT, bridge) reflejando actividad real. + +Backend: WebSocket que publica throughput por flow cada 1s; la velocidad de la animación CSS se ajusta en función del throughput. +**Tests de aceptación**: cuando MQTT publica más, las partículas van más rápido visiblemente. +**Estimación**: M (3d). +**Depende de**: BL-04.1, BL-02.6. + +### `[BL-04.3]` SYNOPSIS — alertas activas en panel lateral — *M* + +**Como** operator, **veo** las alertas más relevantes ordenadas por severidad/recencia. + +Backend: agregador de alertas que combina: +- Selftest fails (BL-05.x). +- Drift detected. +- Camera offline. +- Storage warnings. +- Bridge disconnect. + +Cada alerta es clickable → lleva a la vista relevante con el contexto preseleccionado. +**Tests de aceptación**: 5 alertas simultáneas se ordenan correctamente; click en cam-04 offline lleva a vista CAMS con cam-04 destacada. +**Estimación**: M (3d). +**Depende de**: BL-04.1. + +### `[BL-04.4]` Forensic engine MVP con un edge AI — *XL* + +**Como** investigator, **escribo** una query natural y **recibo** resultados de Frigate. + +Backend en Cell: +- Endpoint `POST /api/forensic/query {nl_text, time_range}`. +- LLM pequeño local (o llamada a hub) para parsear NL → query estructurada. +- Por ahora solo Frigate como backend: convertir a `/api/events?after=...&label=person&zone=...`. +- Devolver hits con score, snapshots, eventos. + +**Tests de aceptación**: query "personas en la entrada anoche entre 2 y 4 AM" devuelve hits relevantes con timestamps en el rango. +**Estimación**: XL (2 sem). +**Depende de**: BL-03.6. + +### `[BL-04.5]` Forensic chat UI con autocomplete — *L* + +**Como** investigator, **escribo** y veo sugerencias inline (ghost text) basadas en vocabulario conocido. + +Backend: API de autocomplete que sabe de cámaras existentes, zonas, objetos detectables, rangos típicos. +**Tests de aceptación**: empezar a escribir "muestra personas en la entr..." sugiere "...en la entrada hall durante los últimos N minutos". +**Estimación**: L (4d). +**Depende de**: BL-04.4. + +### `[BL-04.6]` Forensic timeline con swimlanes — *L* + +**Como** investigator, **veo** los hits como markers en un timeline con swimlane por edge AI. + +UI: el componente que prototipamos. +Backend: el resultado de la query incluye un array de eventos con `(ai_source, timestamp, type, payload)`. +**Tests de aceptación**: 50 eventos se renderizan en <300ms; click en marker carga clip relacionado. +**Estimación**: L (5d). +**Depende de**: BL-04.4. + +### `[BL-04.7]` Evidence player con clip + metadatos — *M* + +**Como** investigator, **abro** un clip y veo el video con detecciones, audio waveform si aplica, evidencia relacionada. + +Backend: streaming HLS desde Frigate envuelto por el reverse proxy del router; metadatos del evento; lista de "evidence within ±60s". +**Tests de aceptación**: clip carga en <2s; scrubbing funciona suave. +**Estimación**: M (3d). +**Depende de**: BL-04.4. + +### `[BL-04.8]` Pin to case — *M* + +**Como** investigator, **pino** un clip a un caso abierto. + +Backend: SQLite local en Cell con tabla `cases`, `case_evidence`, `case_annotations`. +**Tests de aceptación**: pinear 3 clips a un caso, verlos en lista, descripción persistente. +**Estimación**: M (3d). +**Depende de**: BL-04.7. + +### `[BL-04.9]` Vista CASES — listado y detalle — *M* + +**Como** investigator, **veo** mis casos abiertos, click → detalle con todos los pinned y notas. + +UI: vista nueva (actualmente stub). +Backend: CRUD sobre la BD de cases. +**Estimación**: M (3d). +**Depende de**: BL-04.8. + +### `[BL-04.10]` Vista EXPORTS — generar bundle ZIP — *L* + +**Como** investigator, **exporto** un caso completo como ZIP con clips + manifest JSON. + +Backend: zip de: +- Clips MP4 originales. +- Manifest con SHA256 de cada clip. +- Eventos JSON. +- Anotaciones. +- Custody log: quién accedió cuándo. + +**Tests de aceptación**: bundle de 1GB se genera en <30s; abrir ZIP en otro ordenador permite reproducir clips. +**Estimación**: L (5d). +**Depende de**: BL-04.9. + +--- + +# ÉPICA 5 — HEALTH & OPS + +### `[BL-05.1]` Selftest engine en Cell — *L* + +**Como** ops, **quiero** que el Cell ejecute un selftest cada 5min y publique resultados a MQTT. + +Backend: `healthd` container con todos los chequeos de la vista Health, persiste último estado, publica a `hai/healthd/health`. +**Tests de aceptación**: matar Frigate y healthd reporta `frigate: DOWN` en <30s. +**Estimación**: L (5d). +**Depende de**: BL-00.4. + +### `[BL-05.2]` Selftest engine en Router — *M* + +**Como** ops, **lo mismo pero del lado router**: NTS, WAN, VLAN segregation, Tailscale. + +Backend: ucode script en cron de 5min. +**Estimación**: M (3d). +**Depende de**: BL-02.1. + +### `[BL-05.3]` Vista HEALTH agregada — *M* + +**Como** ops, **veo** el resultado consolidado de los dos selftests con los 6 grupos. + +UI: la vista Health prototipada. +Backend: `hai.healthcheck` une los dos. +**Estimación**: M (3d). +**Depende de**: BL-05.1, BL-05.2. + +### `[BL-05.4]` Run all tests en directo — *M* + +**Como** ops, **al pulsar el botón**, ejecuto un selftest fresh con animación de progreso. + +Backend: SSE de progreso por test individual. +**Estimación**: M (2d). +**Depende de**: BL-05.3. + +### `[BL-05.5]` Health history 24h — *M* + +**Como** ops, **veo** la barra de histórico para entender patrones temporales. + +Backend: persistencia de cada selftest run en SQLite del Cell con retention de 30 días. +**Estimación**: M (3d). +**Depende de**: BL-05.3. + +### `[BL-05.6]` Quick-fix actions desde test failure — *L* + +**Como** ops, **al ver un test rojo**, **pulso** un botón de acción contextual (PING X, RECONNECT BRIDGE, ...). + +Backend: cada tipo de test tiene sus actions definidas, ejecutadas via RPC. +**Estimación**: L (4d). +**Depende de**: BL-05.4. + +--- + +# ÉPICA 6 — HUB & FLEET + +### `[BL-06.1]` Hub mínimo — broker MQTT + auth — *L* + +**Como** ops, **necesito** un hub mínimo donde los routers se registren y publiquen estado. + +Stack: +- Mosquitto en Hetzner bare-metal (Falkenstein). +- Postgres para fleet metadata. +- Keycloak para auth de operadores. +- mTLS con CA propia. +- Endpoint enrollment para tokens de fleet. + +**Tests de aceptación**: dos routers de prueba pueden registrarse en el mismo hub y publicar en namespaces separados (`sites/X/...`, `sites/Y/...`). +**Estimación**: L (1 sem). +**Depende de**: BL-00.5. + +### `[BL-06.2]` MQTT bridge router → hub — *M* + +**Como** ops, **el router** publica selectivamente al hub según la policy de bridge. + +UI: ya en MQTT panel. +Backend: `mosquitto.conf` con bridge sections; testing con cliente que verifica que `hai/+/snapshots/#` no llega al hub. +**Tests de aceptación**: matar el bridge, generar 100 eventos, restaurar, los 100 llegan al hub eventualmente. +**Estimación**: M (3d). +**Depende de**: BL-06.1. + +### `[BL-06.3]` Sites Overview — fleet map — *L* + +**Como** ops, **abro la consola del hub** y **veo** todos los sitios en un mapa con estado agregado. + +UI: vista nueva, **distinta del index.html del router** — esto es la consola del hub. Probablemente comparte design tokens y componentes. +Backend: `GET /api/hub/sites` lista sitios + estado consolidado de health/alerts/cams. +**Tests de aceptación**: 10 sitios con estados variados se ven correctamente; click en uno lleva al index.html de ese site via Tailscale. +**Estimación**: L (1 sem). +**Depende de**: BL-06.1. + +### `[BL-06.4]` Cross-site forensic search — *XL* + +**Como** investigator, **lanzo** una query desde el hub que busca **en varios sitios** simultáneamente y agrega resultados. + +Backend: hub mantiene índice consolidado de embeddings (Qdrant); cada sitio publica embeddings con su site_id; query se distribuye a sites relevantes via MQTT request-response, hub re-ranquea. +**Tests de aceptación**: query "vehículo placa L-7234 en cualquier sitio" devuelve hits de 2 sitios diferentes con timestamps correctos. +**Estimación**: XL (2 sem). +**Depende de**: BL-06.3, BL-04.4. + +### `[BL-06.5]` Remote operate via Tailscale — *M* + +**Como** ops, **desde el hub**, **abro** la consola de un sitio remoto con un click. + +Backend: Tailscale ACLs configuradas para que el operador del hub tenga acceso al `:80` del router de cada sitio. +**Tests de aceptación**: click en site → tab abre `https://bl-lab-router.ts.net/` con auth ya pasada. +**Estimación**: M (3d). +**Depende de**: BL-06.3. + +### `[BL-06.6]` Fleet provisioning de configuración — *L* + +**Como** ops, **edito** la configuración base de la flota en un único sitio (el repo `fleet-config`) y **se propaga** a todos los routers. + +Mecánica: cada router clona dos repos: el `site-config` (específico) y el `fleet-config` (común). Reconcile aplica primero fleet, luego site (overrides). Cambios en fleet-config llegan a todos en el siguiente fetch (5min). +**Tests de aceptación**: añadir una regla firewall en fleet-config se aplica a 10 sitios en <10min. +**Estimación**: L (5d). +**Depende de**: BL-02.8, BL-06.1. + +--- + +# ÉPICA 7 — HARDENING + +### `[BL-07.1]` Evidence chain — capa 1: hash + manifest + NTS — *L* + +**Como** investigator, **cada clip generado** tiene un manifest firmado por el Cell con hash del fichero, timestamp NTS, modelo usado. + +Backend: +- Frigate hook on_clip_done. +- Hash SHA256 stream. +- Manifest JSON con cam_id, sha256_clip, sha256_keyframe, ts_nts, model_sha, container_sha, prev_manifest_hash. +- Firma con clave del Cell (LUKS-protected file por ahora, TPM en BL-07.x). + +**Tests de aceptación**: editar un clip cambia su SHA, manifest queda inválido, herramienta `blocao verify` lo detecta. +**Estimación**: L (5d). +**Depende de**: BL-04.4. + +### `[BL-07.2]` `blocao verify` — herramienta CLI de verificación — *M* + +**Como** investigator, **paso** un clip + manifest a un tercero y este puede verificar autenticidad sin necesitar acceso a Blocao. + +Backend: CLI Go o Rust que toma clip + manifest + clave pública, valida SHA, firma, encadenamiento. Distribuible como single-binary. +**Tests de aceptación**: terceros sin acceso al sistema pueden ejecutar `blocao verify clip.mp4 manifest.json` y obtener "VALID" o "INVALID con razón". +**Estimación**: M (3d). +**Depende de**: BL-07.1. + +### `[BL-07.3]` Hash chain de manifests + replicación al hub — *M* + +**Como** investigator, **cada manifest** referencia el SHA del anterior, formando una cadena verificable; los manifests se replican al hub. + +Backend: persistencia local + topic `hai/_evidence/manifest` que el bridge publica al hub para tener segunda copia. +**Estimación**: M (3d). +**Depende de**: BL-07.1, BL-06.2. + +### `[BL-07.4]` Audit log de operador — *M* + +**Como** investigator, **cada acción mía** queda registrada inmutablemente: query lanzada, clip visualizado, exportación, escalación. + +Backend: append-only log en BD del Cell con firma del operador (FIDO2 para acciones críticas). +**Estimación**: M (3d). +**Depende de**: BL-04.8. + +### `[BL-07.5]` Rebranding final + design tokens estables — *S* + +**Como** team, **consolido** los tokens (paleta complementaria, tipos, espacios, radios) en un paquete versionado para reusar en el hub UI, web corporativa, decks. + +Backend: `packages/design-tokens/` con `tokens.css`, `tokens.json`, `tokens.figma.json`. +**Estimación**: S (1d). +**Depende de**: nada. + +### `[BL-07.6]` Documentación de patrones UI — *S* + +**Como** team, **documento** las decisiones de diseño (rail+topbar+ctx-strip, asistentes full-screen, modos de color por dominio). + +`docs/ui-patterns.md`. +**Estimación**: S (1d). +**Depende de**: BL-07.5. + +--- + +# Stories transversales (no bloquean sprints) + +### `[BL-X.1]` mDNS publishing del router como `bl-{site}-router.local` — *S* +**Depende de**: BL-01.2. + +### `[BL-X.2]` Cell discovery via mDNS + MQTT announce — *M* +**Depende de**: BL-00.4, BL-02.1. + +### `[BL-X.3]` First-time TLS cert: self-signed → Let's Encrypt vía Tailscale — *M* +**Depende de**: BL-01.5. + +### `[BL-X.4]` HaLow radio support en Image Builder (cuando llegue hardware) — *L* +**Depende de**: BL-00.3. + +### `[BL-X.5]` Multi-Cell coordination (preparación para Core) — *L* +**Depende de**: BL-04.4. + +### `[BL-X.6]` Backup & Restore de configuración (los repos GitOps + BD operator) — *M* +**Depende de**: BL-02.8. + +### `[BL-X.7]` Multi-operator + roles (RBAC) — *L* +**Depende de**: BL-06.1. + +### `[BL-X.8]` Telegram/email/webhook notifications — *M* +**Depende de**: BL-05.1. + +--- + +# Riesgos identificados + +1. **Frigate custom build con RKNN** puede ser inestable. Mitigación: tener fallback CPU-only en parallel image. +2. **mTLS cert lifecycle** en routers desplegados — si el cert expira sin renovarse, el sitio queda desconectado del hub. Mitigación: renovación automática con margen de 90 días. +3. **Frigate API breaking changes** entre versiones (pasamos de 0.13 a 0.14 ya hubo cambios). Mitigación: pin de version en GitOps repo. +4. **GL.iNet / Banana Pi disponibilidad** del hardware del router — comprobar segundo proveedor. +5. **GDPR/AAIP compliance review** antes del primer cliente real — workstream legal aparte, no bloquea técnico. +6. **Coste hub** crece con número de sitios — definir tier de pricing antes de tener 50 sitios para que no se coma el margen. + +--- + +# Notas para el sprint planning + +**Sprint 0** debería ser puramente foundation (épica 0). Sin UI nueva. + +**Sprint 1** mete el wizard funcional y un sitio se puede provisionar end-to-end aunque sea muy básico. + +**Demo viable de cliente**: tras sprint 4 (épica 0+1+2+3+4) ya tienes una historia completa: desplegar router, descubrir cámaras, ver SCADA, hacer query forense. Sin hub, sin evidence chain. **Es la demo MVP**. + +**Demo de cliente con flota**: tras sprint 6, ya tienes hub + multi-site. + +**Demo legal-grade**: tras sprint 7, ya tienes evidence chain básica para defender el caso. + +--- + +*Documento generado · pendiente de revisión por el team antes de carga en Linear / Jira.*