diff --git a/modul-15-monitoring-control.md b/modul-15-monitoring-control.md new file mode 100644 index 0000000..d51ba06 --- /dev/null +++ b/modul-15-monitoring-control.md @@ -0,0 +1,140 @@ +# Modul-15: Monitoring-Control + +**Status: IMPLEMENTIERT & E2E-verifiziert (noch NICHT freigegeben)** · Container `Modul-15-Monitoring-Control` · Image `monitoring-control:0.1.0` + +## Zweck +Zentrale **technische Überwachung und Sicherheits-/Kontrollebene** des Trading-Systems. +Überwacht die Dienste M03–M14 (Health/Readiness/Erreichbarkeit) plus RabbitMQ-Queues/Consumer +und PostgreSQL und leitet daraus deterministisch einen **globalen Trading-Status** +(`ENABLED` / `PAUSED` / `HALTED` / `UNKNOWN`) ab. + +**Restriktionen (Nutzer-Vorgabe):** +- **Keine KI/ML**, **keine Trading-Entscheidungen**, **keine Brokerorders**, **keine Shell-/Docker-Control-Rechte**. +- **FAIL-CLOSED:** Bei unklarem/kritischem Zustand wird Trading sicher blockiert (`HALTED`). +- M15 unerreichbar → M09 behandelt den Zustand als `HALTED` (siehe Design-Vorschlag M15→M09, noch offen). +- **Keine Event-Schleifen:** Events nur bei Status-Änderung, dedupliziert. +- Container wird **nicht** automatisch neu gestartet/gekillt — M15 beobachtet und entscheidet Status. +- Port 55015 **nur Docker-intern** (`expose`, kein Host-Port). + +## Architektur / Datenfluss +``` +M03–M14 (health/ready) RabbitMQ (Queues) PostgreSQL + │ │ │ + └──────────┬────────────┴──────────────┘ + ▼ + Modul-15-Monitoring-Control + ├── probe/ MonitoringProbe (HTTP-health + RMQ passive declare + PG) + ├── rules/ MonitoringRules (deterministisch, FAIL-CLOSED, versioniert) + ├── core/ MonitorService (Status-Engine, Single Source of Truth) + ├── storage/ Persistenz (PostgreSQL: system_health/service_health/control_event) + ├── publisher/ ControlPublisher (market.control, nur bei Status-Änderung) + └── api/main.py REST-API (intern, Port 55015) +``` +Status → `system_health` (append), Services → `service_health` (upsert), Änderungen → `control_event` (append-only Audit). + +## API (Docker-intern, Port 55015, kein öffentliches Port-Mapping) +``` +GET /health liveness +GET /health/ready readiness (postgresql) +GET /monitoring/status aktueller System-Status + Trading-Status (internes State-Modell) +GET /monitoring/services aktueller Zustand aller überwachten Services/Queues +GET /control/status Control-Status (manual_override, control_api_enabled) +GET /control/trading-state autoritativer Trading-Status für M09 +POST /control manuelle Control PAUSE/HALT/RESUME (nur wenn CONTROL_API_ENABLED=true) +``` + +## Deterministische Regeln (Rules-Engine, Version 0.1.0) +Präzedenz strikt von oben nach unten (FAIL-CLOSED): +1. **UNKNOWN** — PostgreSQL nicht erreichbar → nicht verlässlich → `HALTED`. +2. **UNHEALTHY/HALTED** — kritische Infrastruktur (PG/RabbitMQ) oder Execution-Pfad down. +3. **DEGRADED/PAUSED** — nicht-kritische Dienste down / stale. +4. **HEALTHY/ENABLED** — alles gut. + +| Bedingung | System | Trading | Reason-Code | +|---|---|---|---| +| PostgreSQL nicht erreichbar | UNHEALTHY | HALTED | `POSTGRESQL_UNREACHABLE` (+ `PERSISTENCE_FAILURE`) | +| RabbitMQ nicht erreichbar | UNHEALTHY | HALTED | `RABBITMQ_UNREACHABLE` | +| Kritischer Service down (M03–M09) | UNHEALTHY | HALTED | `CRITICAL_SERVICE_DOWN:` | +| Market Data stale (> max_age) | UNHEALTHY | HALTED | `MARKET_DATA_STALE` | +| Kritische Queue Consumer fehlt | — | HALTED | `NO_CONSUMER_` | +| Kritischer Queue-Backlog ≥1000 | — | HALTED | `QUEUE_BACKLOG_CRIT::` | +| Nicht-kritische Unhealthy (Analytics/Notification) | DEGRADED | PAUSED | `CRITICAL_SERVICE_DOWN`/`NO_CONSUMER_…` | +| Queue-Backlog ≥100 / Warning | DEGRADED | PAUSED | `QUEUE_BACKLOG::` | +| Alles healthy | HEALTHY | ENABLED | — | + +**FAIL-CLOSED-Hinweis:** `UNKNOWN` Trading-Status wird in der API auf `HALTED` gemappt (nie ENABLED aus unklarem Zustand). + +## Events (market.control Exchange, keine Event-Schleife) +Events werden **nur bei Status-Änderung** publiziert (Idempotenz) — nie bei jedem Poll: + +| Event-Typ | Routing-Key | Anlass | +|---|---|---| +| `system.degraded` | `system.degraded` | System → DEGRADED | +| `system.halted` | `system.halted` | System → UNHEALTHY (HALT) | +| `system.recovered` | `system.recovered` | System → HEALTHY | +| `trading.state` | `trading.state` | Trading-Status-Änderung | +| `service.unhealthy` | `service.unhealthy` | Einzelner Service UNHEALTHY | + +## Persistenz / Audit (PostgreSQL Modul-01) +- `system_health` — append-only Zeile pro Beobachtung (aktueller Status + reason_codes). +- `service_health` — Upsert pro `service_id` (aktueller Zustand jedes Services/Queue). +- `control_event` — **append-only Audit-Trail** jeder Status-/Trading-Änderung (`STATE_CHANGE`) und manuellen Control-Aktion (`MANUAL_*`). Idempotent — eine Änderung wird **genau einmal** auditiert. + +## Manuelle Control (PAUSE/HALT/RESUME) +- **Nur wenn `CONTROL_API_ENABLED=true`** (Default `false` → manuelle Control über API deaktiviert, nur Regel-getrieben). +- **RESUME wird verweigert** (`RESUME_BLOCKED_CRITICAL_CAUSE`), solange eine kritische Ursache besteht. +- Jede manuelle Aktion wird append-only in `control_event` auditiert. + +## Sicherheit +- **Keine Secrets in DB/Logs/Doku/Commits** — Zugangsdaten ausschließlich via Environment (Compose). +- **Kein Docker-Socket**, **keine Shell-/Docker-Control-Rechte** (CMD=`python main.py`). +- Port 55015 nur intern (`expose`), `restart: unless-stopped`, non-root (uid 1001). +- Env: `POSTGRES_*`, `RABBITMQ_*`, `CONTROL_API_ENABLED`. + +## Compose / Betrieb +- Netzwerk `trading-modules`, DB-Host `Modul-01-PostgreSQL`, RabbitMQ `Modul-02-RabbitMQ`. +- Env per Compose, Zugangsdaten nur via ENV (`POSTGRES_*`, `RABBITMQ_*`), nie hart kodiert. + +## Unit-Tests +- `tests/test_monitoring.py`: **18/18 grün** — deterministische Rules, FAIL-CLOSED (PG/RabbitMQ/Critical), stale, Queue-Consumer, Idempotenz, Audit genau einmal, Recovery, Reconnect, keine Secrets. + +## E2E-Verifikation (VPS, 20.08.2026) — 17 Szenarien +Alle 17 verifiziert (System-/Trading-Status via Loop, API `/control/trading-state` + `/monitoring/status` konsistent): +1. **Normalzustand:** `HEALTHY/ENABLED` — API, State-Modell, Loop identisch. +2. **PG-Ausfall:** `HALTED`, `POSTGRESQL_UNREACHABLE`+`PERSISTENCE_FAILURE`, Audit genau einmal. +3. **RabbitMQ-Ausfall:** `HALTED`, `RABBITMQ_UNREACHABLE`. +4. **Risk-Ausfall:** `HALTED`, `CRITICAL_SERVICE_DOWN:modul-07-risk-manager`+`NO_CONSUMER_risk.input`. +5. **Portfolio-Ausfall:** `HALTED`, `CRITICAL_SERVICE_DOWN:modul-08-portfolio-manager`. +6. **Execution-Ausfall:** `HALTED`, `CRITICAL_SERVICE_DOWN:modul-09-execution-service`. +7. **Analytics-Ausfall:** `DEGRADED` + `PAUSED` (Trading nicht vollständig kill). +8. **Notification-Ausfall:** `DEGRADED` + `PAUSED`. +9. **Market Data stale:** `HALTED`, `MARKET_DATA_STALE`; Recovery nach frischen Daten. +10. **Kritischer Consumer fehlt:** HALT-Severity korrekt (NO_CONSUMER auf kritischer Queue). +11. **Queue-Backlog:** Warning/HALT gemäß Schwelle (100/1000, Unit-getestet). +12. **Recovery:** Ursache erneut geprüft, kein Auto-RESUME solange Ursache, Status konsistent, kein Event-Spam. +13. **Manuelle Controls:** mit `CONTROL_API_ENABLED=false` sauber abgelehnt (`control_api_disabled`); Trading unangetastet. +14. **Idempotenz/Event-Flut:** stabiler Zustand → keine wiederholten SYSTEM_HALTED/SERVICE_UNHEALTHY (nur 1 Audit je Änderung). +15. **RabbitMQ-Reconnect:** automatisch (alle Recovery-Zyklen), keine Zombies, keine Eventverluste. +16. **Audit:** `control_event`/`system_health`/`service_health` vollständig + zeitlich nachvollziehbar. +17. **Security:** kein öffentlicher Port (PortBindings `{}`), kein Docker-Socket, keine Secrets im Compose/Logs. + +## Bekannte Design-Hinweise (für separaten M15→M09-Vorschlag) +- **M03–M06 (Market-Data-Pipeline) sind in `CRITICAL_SERVICES`** klassifiziert. Ausfall eines einzelnen Daten-Moduls führt so zu `HALTED` (über `CRITICAL_SERVICE_DOWN`), nicht ausschließlich über `MARKET_DATA_STALE`. Das ist fachlich konservativ aber zu klären (nur Data-Pipeline vs. Execution-Pfad). +- **DEGRADED → `PAUSED`** (nicht `ENABLED`): bei Analytics/Notification-Ausfall wird Trading vorsichtig gestoppt, nicht vollständig killed. Design-Entscheidung, im Vorschlag benannt. +- **PG-Ausfall ⇒ `PERSISTENCE_FAILURE`**: ohne PG kann `control_event`-Audit nicht geschrieben werden (fachlich korrekt FAIL-CLOSED), aber Audit-Lücke im Fehlerfenster. + +## Freigabe +- **Noch NICHT freigegeben.** Implementierung + E2E (17 Szenarien) grün. Freigabe ausstehend. +- M09-Anbindung **nicht** umgesetzt (Design-Vorschlag separat). + +## Nächste Module +- **Modul-16 ff.** — noch Platzhalter (alpine), NICHT gestartet. + +--- +## Geändert +``` +Geändert von: Rain Ocampo +Datum: 20.08.2026 +Grund: Modul-15-Monitoring-Control dokumentiert — implementiert + E2E-verifiziert (17 Szenarien grün), + Rules/API/Events/Audit/Security dokumentiert, NICHT FREIGEGEBEN. +```