chore: auto-commit (17 archivos)

- reports/0001-2026-06-07-unibus-grafana-monitoring.md
- reports/0008-2026-06-07-unibus-admin-users-wired.md
- reports/0008-2026-06-07-unibus-decentralization-audit.md
- reports/0009-2026-06-07-unibus-cluster-hardening.md
- reports/0010-2026-06-07-unibus-android-native.md
- reports/0011-2026-06-07-unibus-cluster-deploy.md
- reports/0012-2026-06-07-unibus-deploy-gaps-closed.md
- reports/0013-2026-06-07-unibus-admin-panel.md
- reports/0014-2026-06-07-unibus-users-http-admin-api.md
- reports/0015-2026-06-07-unibus-web-wired.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 01:57:00 +02:00
parent 29fe688b7a
commit d43ffae3ae
17 changed files with 2028 additions and 0 deletions
@@ -0,0 +1,145 @@
# Report 0001 — Monitorización de unibus en Grafana/VictoriaMetrics
- **Fecha:** 07/06/2026
- **Autor:** agente (Claude Opus 4.8)
- **Ámbito:** `projects/fleet_monitoring/` (exporter + dashboard + deploy) + 1 función nueva en el registry (`functions/infra/`). Solo lectura de `projects/message_bus/apps/unibus/` (endpoint `/healthz` + CA).
- **Estado:** done (métricas de `/healthz`); gap declarado en métricas profundas NATS/JetStream.
## Resumen
Se añadió monitorización del cluster de mensajería **unibus** (NATS+JetStream, 3 nodos: magnus,
homer, datardos) a la stack de Grafana/VictoriaMetrics que ya corre en magnus, **sin instrumentar
el bus**. Un nuevo daemon `unibus_exporter` sondea el endpoint público de salud de cada nodo
(`GET https://<nodo>:8470/healthz`, TLS verificado con la CA del cluster) en un bucle de 15 s,
traduce la respuesta a métricas Prometheus y las empuja a la VictoriaMetrics local de magnus.
Un dashboard dedicado las visualiza. No se tocó nada del código de unibus ni la stack existente
(Caddy/Gitea/VM/Grafana/fleet-agent intactos).
## Qué métricas se recolectan y cómo
El `/healthz` de cada nodo devuelve, verificado en producción:
```json
{"posture":{"enforce":true,"acl":true,"tls":true,"cluster":true,"store":"kv"},"status":"ok"}
```
De ahí se derivan estas series (labels `node` e `instance` = nombre del nodo, label común
`job=unibus_exporter`):
| Serie | Origen | Significado |
|---|---|---|
| `unibus_up` | exporter | 1 si el nodo respondió `/healthz`, 0 si el GET/parseo falló |
| `unibus_status_ok` | healthz `status` | 1 si `status=="ok"` |
| `unibus_posture_enforce` | healthz `posture.enforce` | enforcement de auth (1/0) |
| `unibus_posture_acl` | healthz `posture.acl` | ACL de subjects (1/0) |
| `unibus_posture_tls` | healthz `posture.tls` | TLS del transporte (1/0) |
| `unibus_posture_cluster` | healthz `posture.cluster` | modo cluster activo (1/0) |
| `unibus_store_kv` | healthz `posture.store` | 1 si el store es `kv` (JetStream KV) |
| `unibus_scrape_error` | exporter | 1 si el scrape de ese nodo falló |
| `unibus_scrape_duration_seconds` | exporter | latencia del GET `/healthz` |
| `unibus_cluster_size` | exporter (config) | nº de nodos configurados (los vivos = `sum(unibus_up)`) |
`unibus_up=0` lo emite el exporter (no el parser) cuando el GET falla, para que un nodo caído sea
**visible** en Grafana, no simplemente ausente.
## Componentes entregados
### Función del registry — `parse_unibus_health_go_infra`
- `functions/infra/parse_unibus_health.go` + `.md` + `_test.go` (grupo `fleet-metrics`, tags `unibus`).
- `func ParseUnibusHealth(node string, body []byte) ([]PromSample, error)` — pura de transformación
(clasificada `impure` solo por el error de unmarshal). Tests golden/edge/error.
- **Nota de proceso:** el prompt pedía delegar esta función a `fn-constructor`, pero ese
subagent_type no existe en este entorno. Tras confirmar con el usuario ("crear en el registry"),
se creó a mano siguiendo el flujo (archivos + tests + `fn index`).
### App — `unibus_exporter` (sub-repo Gitea propio)
- `projects/fleet_monitoring/apps/unibus_exporter/`: `main.go`, `config.go`, `unibus.example.json`,
`systemd/unibus-exporter.service`, `app.md`, `.gitignore`.
- Compone `parse_unibus_health` + `format_prom_exposition` + `push_prom_remote` del registry (no
reescribe push ni formato). Config JSON; secretos (CA, basic-auth) fuera de argv. Verifica TLS
siempre contra la CA del cluster (sin `InsecureSkipVerify`).
- `git init -b master` + commit inicial hecho (apps/* está gitignored en el project; sin sub-repo
el código se perdería). Falta crear el repo Gitea remoto: lo hará `/full-git-push`.
### Dashboard — `unibus-cluster.json`
- `projects/fleet_monitoring/hub/dashboards/unibus-cluster.json` (formato de los `fleet-*.json`,
datasource `victoriametrics`, carpeta **Fleet**, uid `unibus-cluster`, 9 paneles): nodos up,
cluster size, nodos caídos, posture homogénea segura, up/down por nodo, matriz de posture por
nodo (state-timeline enforce/acl/tls/cluster/store-kv × 3 nodos), latencia de scrape y tabla de
estado por nodo. Panel "Meta-leader" preparado (muestra n/d sin métricas NATS).
### Deploy — `deploy_unibus_exporter.sh`
- `projects/fleet_monitoring/hub/deploy_unibus_exporter.sh`: compila el binario linux/amd64, sube
binario + CA del cluster a magnus (`/opt/unibus-exporter`, `/etc/unibus-exporter/ca.crt` chmod
600 la config) e instala el servicio systemd apuntando a `http://127.0.0.1:8428/...` (VM local,
sin auth porque corre en el propio hub).
## Verificación (evidencia ejecutable)
**1. Acceso y healthz de los 3 nodos (CA del cluster por path):**
```
$ curl -s --cacert .../deploy/tls/ca.crt https://135.125.201.30:8470/healthz
{"posture":{"enforce":true,"acl":true,"tls":true,"cluster":true,"store":"kv"},"status":"ok"}
(idéntico en homer 141.94.69.66 y datardos 51.91.100.142)
```
**2. Tests de la función:**
```
$ go test -tags fts5 -run ParseUnibusHealth ./functions/infra/
ok fn-registry/functions/infra 0.004s
$ ./fn index # → "Indexed 1450 functions ..."; ./fn show parse_unibus_health_go_infra → OK
```
**3. Exporter build + scrape/push único de prueba (local → VM):**
```
$ ./unibus_exporter -config <test> -once
unibus_exporter starting: nodes=3 hub="https://metrics-…/api/v1/import/prometheus" interval=15s
pushed 28 samples for 3 nodes # 1 cluster_size + 3 nodos × 9 series
```
**4. Daemon systemd en magnus:**
```
$ systemctl is-active unibus-exporter → active
$ systemctl is-enabled unibus-exporter → enabled
$ journalctl -u unibus-exporter → "pushed 28 samples for 3 nodes"
```
**5. Series en VictoriaMetrics (magnus, 127.0.0.1:8428):**
```
sum(unibus_up) = 3
unibus_cluster_size = 3
count(unibus_up==1) = 3
unibus_posture_enforce: magnus=1 homer=1 datardos=1 (job=unibus_exporter)
unibus_store_kv: magnus=1 homer=1 datardos=1
unibus_scrape_duration_seconds: magnus≈4ms homer≈32ms datardos≈19ms
```
**6. Dashboard en Grafana (visto en el navegador):**
- `https://grafana-…/d/unibus-cluster` — carpeta Fleet, 9 paneles renderizando datos reales.
- Nodos up: **3** · Cluster size: **3** · Nodos caídos: **0** · Posture homogénea segura:
**OK (enforce+acl+tls+cluster+kv)** · matriz de posture: 15 celdas en verde · tabla de estado
por nodo con ✓ en up/enforce/acl/tls.
- Captura: `unibus-grafana-monitoring-dashboard.png` (junto a este report).
- API: `GET /api/dashboards/uid/unibus-cluster``dashboard CARGADO: unibus — Cluster | folder: Fleet | paneles: 9`.
## Gaps / pendientes
- **Métricas profundas NATS/JetStream (msgs/s, conexiones, KV bucket msgs, RAFT leader por
stream, `NRestarts`) — NO incluidas, gap recomendado.** La vía es el monitoring embebido de
NATS (puerto 8222), que se confirmó **cerrado** en los 3 nodos en producción
(`curl 127.0.0.1:8222/varz` → CLOSED en magnus/homer/dd). Activarlo (bindeado a 127.0.0.1 +
scrape local, o sacar `/jsz` por SSH) exige tocar la config/unit de los nodos del cluster, que
además están siendo trabajados por otros agentes ahora mismo. Se decidió **no forzarlo** por
riesgo en producción. Para abordarlo después: añadir `UNIBUS_NATS_DEBUG`/equivalente bindeado a
loopback en cada nodo (cambio aditivo, coordinado con `unibus/deploy/cluster/`), un scrape local
del `/jsz`+`/varz` y nuevas series `unibus_jsz_*`. El panel "Meta-leader" del dashboard ya está
preparado para cuando exista `unibus_meta_leader`.
- **`unibus_cluster_size`** refleja el nº de nodos **configurados** en el exporter (3), no un
recuento que el bus reporte (healthz no lo expone). Los nodos vivos se ven con `sum(unibus_up)`.
- **Commit en el repo padre `fn_registry` sin pushear (a propósito):** la función nueva quedó
commiteada en local (`82f1f1bd`, master ahead 1) pero **no se hizo push** del padre, para
respetar el aislamiento pedido. El humano debería revisarlo y pushearlo (junto con `fn index`
para regenerar `registry.db`, que está gitignored).
- **Repo Gitea del exporter:** `apps/unibus_exporter` tiene su `git init` + commit local pero aún
no tiene remoto en Gitea; `/full-git-push` lo creará (`dataforge/unibus_exporter`).
- **Vida útil (DoD capa 3):** validado funcionalmente hoy; falta la ventana de uso real ≥7 días.