Files
wdmUI/backlog/blocao-sprint-backlog.md

33 KiB
Raw Permalink Blame History

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-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 <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>/announce
  • hai/<device_id>/health
  • hai/<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_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/<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:

  1. Generar la sección cameras:<id> 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.localS

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.