docs(dev): add ops runbook for matrix-bridge-dagi (H4)

Made-with: Cursor
This commit is contained in:
Apple
2026-03-05 01:12:49 -08:00
parent a24dae8e18
commit 70dd2a97dc
2 changed files with 378 additions and 0 deletions

View File

@@ -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 (I1I6), rate limit, queue drops, token rotation, release checklist
## Інші runbooks
- [Secrets Rotation](./secrets-rotation.md)

View File

@@ -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 (23 хв)
### 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` | 15s | >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'
```