33 KiB
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<rol>, cuando<situación>, veo / puedo / espero<resultado>. 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-bridgeschronycon NTStailscalemwan3para WAN failovergit,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 <router> 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/<device_id>/announcehai/<device_id>/healthhai/<cam_id>/events/<event_type>hai/<cam_id>/state/<key>hai/_registry/heartbeat/<id>_cmd/<command>- ...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_fetchhai.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/<cell-id>/* 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 '<filter>' -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:
- Generar la sección
cameras:<id>del frigate.yml. - Commit local al repo blocao-config.
- Push a hub si configurado.
- Trigger reconcile del Cell.
- 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
- Frigate custom build con RKNN puede ser inestable. Mitigación: tener fallback CPU-only en parallel image.
- 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.
- Frigate API breaking changes entre versiones (pasamos de 0.13 a 0.14 ya hubo cambios). Mitigación: pin de version en GitOps repo.
- GL.iNet / Banana Pi disponibilidad del hardware del router — comprobar segundo proveedor.
- GDPR/AAIP compliance review antes del primer cliente real — workstream legal aparte, no bloquea técnico.
- 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.