diff --git a/docs/runbook/README.md b/docs/runbook/README.md index 85b43f8a..6258c148 100644 --- a/docs/runbook/README.md +++ b/docs/runbook/README.md @@ -12,6 +12,10 @@ - Release Evidence Generator: [ops/generate_release_evidence.sh](../../ops/generate_release_evidence.sh) - [Audit Retention Runbook](./audit-retention.md) +## Messaging / Matrix + +- [Matrix Bridge DAGI — Ops](./matrix-bridge-dagi-ops.md) — smoke, incidents (I1–I6), rate limit, queue drops, token rotation, release checklist + ## Інші runbooks - [Secrets Rotation](./secrets-rotation.md) diff --git a/docs/runbook/matrix-bridge-dagi-ops.md b/docs/runbook/matrix-bridge-dagi-ops.md new file mode 100644 index 00000000..334cefd8 --- /dev/null +++ b/docs/runbook/matrix-bridge-dagi-ops.md @@ -0,0 +1,374 @@ +# Matrix Bridge DAGI — Ops Runbook (H4) + +**Сервіс:** `matrix-bridge-dagi` | **Нода:** NODA1 | **Порт:** 7030 (localhost) +**Stack:** Matrix (Synapse) → bridge → Router `/v1/agents/{id}/infer` → Matrix reply +**Фаза:** M1 (1 room = Sofiia), H1/H2/H3 hardening активний + +--- + +## 0. Purpose + +Операційні перевірки та troubleshooting для `matrix-bridge-dagi` на NODA1: + +- Matrix ↔ Bridge ↔ Router ↔ Agent ↔ Matrix +- Audit events в sofiia-console (`POST /api/audit/internal`) +- Rate limit (room/sender RPM) +- Backpressure queue (drops/queue wait) + +--- + +## 1. Quick Status (30 секунд) + +### 1.1 Health + +```bash +curl -sS http://127.0.0.1:7030/health | python3 -m json.tool +``` + +Очікування: + +```json +{ + "ok": true, + "matrix_reachable": true, + "gateway_reachable": true, + "mappings_count": 1, + "queue": {"size": 0, "max": 100, "workers": 2}, + "rate_limiter": {"room_rpm_limit": 20, "sender_rpm_limit": 10, "active_rooms": 0, "active_senders": 0} +} +``` + +| Поле | Норма | Тривога | +|------|-------|---------| +| `ok` | `true` | `false` → дивитись `error` | +| `matrix_reachable` | `true` | `false` → I4 | +| `gateway_reachable` | `true` | `false` → I1 | +| `queue.size` | ≈0 idle | >50 → I3 | +| `mappings_count` | ≥1 | 0 → перевірити `BRIDGE_ROOM_MAP` | + +### 1.2 Mappings + +```bash +curl -sS http://127.0.0.1:7030/bridge/mappings | python3 -m json.tool +``` + +### 1.3 Logs (останні 30 рядків) + +```bash +docker logs matrix-bridge-dagi-node1 --tail 30 +``` + +Шукати: `ERROR`, `Rate limited`, `Queue full`, `Invoke ok`, `Reply sent` + +--- + +## 2. Smoke Test (2–3 хв) + +### 2.1 E2E через Element (рекомендовано) + +1. Element → "Change homeserver" → `matrix.daarion.space` +2. Логін: `test_user` / `TestUser_2026!` +3. Room: **DAGI — Sofiia** +4. Відправити: `ping` +5. Очікування: reply ≤ 5s (Mistral/DeepSeek залежний) + +### 2.2 Smoke через curl (без Element) + +```bash +# Логін як test_user +TOKEN=$(curl -sS -X POST http://localhost:8008/_matrix/client/v3/login \ + -H 'Content-Type: application/json' \ + -d '{"type":"m.login.password","identifier":{"type":"m.id.user","user":"test_user"},"password":"TestUser_2026!"}' \ + | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])') + +# Відправити повідомлення +ROOM_ID=$(grep 'SOFIIA_ROOM_ID' /opt/microdao-daarion/.env | cut -d= -f2) +TXN_ID="smoke-$(date +%s)" +curl -sS -X PUT "http://localhost:8008/_matrix/client/v3/rooms/$ROOM_ID/send/m.room.message/$TXN_ID" \ + -H "Authorization: Bearer $TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"msgtype":"m.text","body":"smoke test ping"}' | python3 -c 'import sys,json; print("sent:", json.load(sys.stdin).get("event_id","ERROR"))' + +# Чекати reply (~15-30s залежить від sync cycle) +sleep 30 + +# Перевірити health metrics +curl -sS http://127.0.0.1:7030/metrics | grep -E 'matrix_bridge_(messages_received|messages_replied)_total' +``` + +### 2.3 Перевірити .well-known (для Element auto-discovery) + +```bash +curl -sS https://matrix.daarion.space/.well-known/matrix/client +# Очікування: {"m.homeserver":{"base_url":"https://matrix.daarion.space"}} +``` + +--- + +## 3. Операційні Метрики + +### 3.1 Traffic counters + +```bash +curl -sS http://127.0.0.1:7030/metrics | grep -E \ + "matrix_bridge_messages_(received|replied)_total|matrix_bridge_rate_limited_total|matrix_bridge_queue_dropped_total" +``` + +| Метрика | Значення | Що означає | +|---------|----------|------------| +| `messages_received_total` | росте | повідомлення надходять | +| `messages_replied_total{status="ok"}` | росте | агент відповідає | +| `messages_replied_total{status="error"}` | >0 | проблема send_text → I1 | +| `rate_limited_total` | ≈0 | норма; >0 → I2 | +| `queue_dropped_total` | 0 | норма; >0 → I3 | + +### 3.2 Latency histograms + +```bash +curl -sS http://127.0.0.1:7030/metrics | grep -E \ + "(invoke_duration|send_duration|queue_wait)_seconds_(bucket|count|sum)" +``` + +Орієнтири: + +| Histogram | Норма (p95) | Тривога | +|-----------|-------------|---------| +| `invoke_duration_seconds` | 1–5s | >20s → LLM деградація | +| `send_duration_seconds` | <200ms | >2s → Synapse проблема | +| `queue_wait_seconds` | <50ms idle | >5s → I3 (workers перевантажені) | + +### 3.3 Queue state + +```bash +curl -sS http://127.0.0.1:7030/health | python3 -c \ + 'import sys,json; d=json.load(sys.stdin); print("queue:", d.get("queue"))' +``` + +--- + +## 4. Audit Events (sofiia-console) + +### 4.1 Перевірити audit ingest (internal endpoint) + +```bash +INT_TOKEN=$(grep 'SOFIIA_INTERNAL_TOKEN' /opt/microdao-daarion/.env | cut -d= -f2) +curl -sS -X POST http://127.0.0.1:8002/api/audit/internal \ + -H 'Content-Type: application/json' \ + -H "X-Internal-Service-Token: $INT_TOKEN" \ + -d '{"event":"matrix.smoke_check","agent_id":"sofiia","node_id":"NODA1","status":"ok"}' | python3 -m json.tool +# Очікування: {"ok": true, "event": "matrix.smoke_check"} +``` + +### 4.2 Події для пошуку в audit log + +| Event | Коли | Норма | +|-------|------|-------| +| `matrix.message.received` | при кожному вхідному | завжди | +| `matrix.agent.replied` | при успішній відповіді | завжди | +| `matrix.rate_limited` | при перевищенні RPM | рідко/ніколи | +| `matrix.queue_full` | при переповненні черги | ніколи | +| `matrix.error` | при помилці invoke/send | ніколи | + +--- + +## 5. Common Incidents + +### I1: "Element підключений, але Sofiia не відповідає" + +```bash +# Step 1: health check +curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print("matrix:", d.get("matrix_reachable"), "gw:", d.get("gateway_reachable"), "maps:", d.get("mappings_count"))' + +# Step 2: metrics — received vs replied +curl -sS http://127.0.0.1:7030/metrics | grep -E 'matrix_bridge_messages_(received|replied)_total' + +# Step 3: logs +docker logs matrix-bridge-dagi-node1 --tail 50 | grep -E 'ERROR|invoke|reply|error' + +# Step 4: router direct test +curl -sS -X POST http://127.0.0.1:9102/v1/agents/sofiia/infer \ + -H 'Content-Type: application/json' \ + -d '{"prompt":"ping","session_id":"smoke-check","user_id":"ops"}' | python3 -c 'import sys,json; d=json.load(sys.stdin); print("router:", d.get("response","ERROR")[:80])' +``` + +**Фікс:** +- `gateway_reachable: false` → перевірити `DAGI_GATEWAY_URL=http://dagi-router-node1:8000` +- `mappings_count: 0` → перевірити `BRIDGE_ROOM_MAP` в env +- received росте, replied ні → router або send_text проблема (Step 4) + +--- + +### I2: "Bagato rate_limited в metrics" + +```bash +curl -sS http://127.0.0.1:7030/metrics | grep rate_limited_total +curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("rate_limiter"))' +``` + +**Причина:** RPM занадто низький або room flood (bot/скрипт). + +**Фікс** (тимчасово, через env): +```bash +# На NODA1: +cd /opt/microdao-daarion +# Відредагувати .env: +# RATE_LIMIT_ROOM_RPM=40 +# RATE_LIMIT_SENDER_RPM=20 +docker compose -f docker-compose.matrix-bridge-node1.yml up -d +``` + +--- + +### I3: "Queue drops ростуть / queue.size велике" + +```bash +curl -sS http://127.0.0.1:7030/metrics | grep queue_dropped_total +curl -sS http://127.0.0.1:7030/metrics | grep 'invoke_duration_seconds_bucket' +``` + +**Причина:** модель/Router повільні або `WORKER_CONCURRENCY` малий. + +**Фікс:** +```bash +# Збільшити workers і queue (в .env): +# WORKER_CONCURRENCY=4 +# QUEUE_MAX_EVENTS=300 +docker compose -f docker-compose.matrix-bridge-node1.yml up -d +``` + +Паралельно перевірити latency histogram — якщо `invoke_duration` >20s, проблема на стороні LLM. + +--- + +### I4: "matrix_reachable: false у /health" + +```bash +# Synapse публічний ендпоінт +curl -sS https://matrix.daarion.space/_matrix/client/versions | python3 -c 'import sys,json; d=json.load(sys.stdin); print("ok, versions:", list(d.get("versions",[]))[:3])' + +# Synapse внутрішній (з NODA1) +curl -sS http://localhost:8008/_matrix/client/versions + +# Synapse контейнер +docker ps | grep synapse +docker logs dagi-synapse-node1 --tail 20 + +# Nginx +curl -sI https://matrix.daarion.space/_matrix/client/versions | head -3 +``` + +**Фікс:** +- Synapse контейнер впав → `docker compose -f docker-compose.synapse-node1.yml up -d` +- TLS / .well-known → перевірити nginx (`nginx -t && nginx -s reload`) +- Токен невалідний → I5 (rotation) + +--- + +### I5: "Замінити MATRIX_ACCESS_TOKEN" + +```bash +# 1. Отримати новий токен (з NODA1) +NEW_TOKEN=$(curl -sS -X POST http://localhost:8008/_matrix/client/v3/login \ + -H 'Content-Type: application/json' \ + -d '{"type":"m.login.password","identifier":{"type":"m.id.user","user":"dagi_bridge"},"password":"DAGIbr1dge_M4tr1x_2026!"}' \ + | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])') +echo "New token: ${#NEW_TOKEN} chars" + +# 2. Оновити в .env +sed -i "s/^MATRIX_ACCESS_TOKEN=.*/MATRIX_ACCESS_TOKEN=$NEW_TOKEN/" /opt/microdao-daarion/.env + +# 3. Restart bridge +cd /opt/microdao-daarion +docker compose -f docker-compose.matrix-bridge-node1.yml up -d + +# 4. Verify +sleep 10 +curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print("matrix:", d.get("matrix_reachable"))' +``` + +--- + +### I6: "Замінити SOFIIA_INTERNAL_TOKEN" + +```bash +# 1. Генерувати новий токен +NEW_INT_TOKEN=$(openssl rand -base64 32 | tr -d '/+=') + +# 2. Оновити в .env +sed -i "s/^SOFIIA_INTERNAL_TOKEN=.*/SOFIIA_INTERNAL_TOKEN=$NEW_INT_TOKEN/" /opt/microdao-daarion/.env + +# 3. Restart обох сервісів +cd /opt/microdao-daarion +docker compose -f docker-compose.node1.yml up -d dagi-sofiia-console-node1 +docker compose -f docker-compose.matrix-bridge-node1.yml up -d +sleep 15 + +# 4. Verify audit ingest +INT_TOKEN=$(grep 'SOFIIA_INTERNAL_TOKEN' .env | cut -d= -f2) +curl -sS -X POST http://127.0.0.1:8002/api/audit/internal \ + -H 'Content-Type: application/json' \ + -H "X-Internal-Service-Token: $INT_TOKEN" \ + -d '{"event":"matrix.token_rotated","agent_id":"sofiia","node_id":"NODA1","status":"ok"}' | python3 -c 'import sys,json; print("audit ok:", json.load(sys.stdin).get("ok"))' +``` + +--- + +## 6. Restart / Rollback + +### Restart bridge + +```bash +cd /opt/microdao-daarion +docker compose -f docker-compose.matrix-bridge-node1.yml up -d --force-recreate +sleep 15 +curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print("ok:", d["ok"])' +``` + +### Restart Synapse + +```bash +docker compose -f docker-compose.synapse-node1.yml up -d +sleep 20 +curl -sS http://localhost:8008/_matrix/client/versions | python3 -c 'import sys,json; print("synapse ok:", "versions" in json.load(sys.stdin))' +``` + +### Мінімальний smoke після restart + +```bash +curl -sS http://127.0.0.1:7030/health | python3 -c \ + 'import sys,json; d=json.load(sys.stdin); print("bridge ok:", d["ok"], "| matrix:", d.get("matrix_reachable"), "| gw:", d.get("gateway_reachable"))' +# Потім: 1 повідомлення в Element → reply +``` + +--- + +## 7. Release Checklist (перед деплоєм bridge) + +``` +[ ] health ok (ok: true, matrix: true, gw: true) +[ ] queue drops == 0 (curl /metrics | grep queue_dropped_total → 0) +[ ] rate_limited == 0 (curl /metrics | grep rate_limited_total → 0) +[ ] smoke test: Element → ping → reply ≤ 5s +[ ] audit events з'являються (matrix.message.received, matrix.agent.replied) +[ ] .well-known → JSON з base_url +[ ] TLS cert valid (не <7 days до expiry) +``` + +### Перевірка TLS cert (термін дії) + +```bash +echo | openssl s_client -connect matrix.daarion.space:443 -servername matrix.daarion.space 2>/dev/null \ + | openssl x509 -noout -dates +# notAfter — запас не менше 7 днів (auto-renew certbot) +``` + +--- + +## 8. Що прикріпити до інциденту + +```bash +echo "=== /health ===" && curl -sS http://127.0.0.1:7030/health | python3 -m json.tool +echo "=== /metrics traffic ===" && curl -sS http://127.0.0.1:7030/metrics | grep -E 'matrix_bridge_(messages|rate_limited|queue_dropped|gateway_errors)' +echo "=== /metrics latency ===" && curl -sS http://127.0.0.1:7030/metrics | grep -E '(invoke|send|queue_wait)_duration_seconds_(count|sum)' +echo "=== logs ===" && docker logs matrix-bridge-dagi-node1 --tail 50 2>&1 | grep -E 'ERROR|WARN|rate_limited|queue_full|Reply sent|invoke ok' +```