diff --git a/modul-15-m09-anbindung-implementierung.md b/modul-15-m09-anbindung-implementierung.md new file mode 100644 index 0000000..96fd2e7 --- /dev/null +++ b/modul-15-m09-anbindung-implementierung.md @@ -0,0 +1,150 @@ +# Modul-15 → Modul-09 Control-Anbindung — Implementierung & Live-E2E + +**Status: IMPLEMENTIERT + LIVE-E2E VERIFIZIERT (alle Szenarien grün).** +**Datum:** 20.08.2026 · **Autor:** Rain Ocampo (Hermes) +**Gilt für:** Modul-09-Execution-Service ↔ Modul-15-Monitoring-Control + +> **Freigabe-Hinweis:** Diese Doku dokumentiert den Implementierungs- und E2E-Stand. +> Die **finale Freigabe** (M15/M09 als FREIGEGEBEN markieren) erfolgt erst nach +> Nutzer-Bestätigung. M16 wird **nicht** begonnen. + +--- + +## 1. Umgesetzte Architektur (Variante C) + +**Event-Cache als nicht-kritischer Vorfilter + synchroner HTTP-Final-Check als Autorität.** + +- **M15** = autoritative Safety-/Trading-Control-Instanz. +- **M09** sendet neue Orders (OPEN/INCREASE) **nur**, wenn M15 eindeutig `ENABLED` + frisch bestätigt. +- **REDUCE/CLOSE/CANCEL** (Risikoabbau) sind **immer** erlaubt — unabhängig vom M15-Status. + +### Kernregel +| State | OPEN/INCREASE | REDUCE/CLOSE/CANCEL | +|-------|---------------|---------------------| +| `ENABLED` | **ERLAUBT** | erlaubt | +| `PAUSED` | **VERBOTEN** | erlaubt | +| `HALTED` | **VERBOTEN** | erlaubt | +| `UNKNOWN` / M15-down / stale | **VERBOTEN (FAIL-CLOSED)** | erlaubt | + +--- + +## 2. API-Vertrag M15 → M09 (additiv, kein Breaking Change) + +### `GET /control/trading-state` +```json +{ + "trading_status": "ENABLED", // ENABLED|PAUSED|HALTED|UNKNOWN + "system_status": "HEALTHY", + "state_version": 3, // monoton, je Status-Änderung inkrementiert + "timestamp": "2026-08-20T15:30:00Z", + "expires_at": "2026-08-20T15:30:00.5Z", // TTL-Horizont (now + 500ms) + "ttl_seconds": 0.5, + "audit_id": "ctl-..." // M15-interne Audit-Kennung +} +``` +Nur `trading_status=ENABLED` **und** `expires_at` in der Zukunft = eindeutige Erlaubnis. +Timeout/5xx/503 → **FAIL-CLOSED** (als UNKNOWN behandeln). + +### Event-Kanal (Event-Cache) +- Exchange `market.control`, Routing-Key `trading.state` (nur bei Status-Änderung). +- M09-Consumer `ControlCacheConsumer` speist den Event-Cache-Vorfilter. +- Cache-TTL: 5 s. Stale-Cache → als UNKNOWN behandeln → OPEN/INCREASE blockiert. + +--- + +## 3. M09-Implementierung + +### Neue Dateien +- `app/control/client.py` — **ControlClient** (Event-Cache-Vorfilter + HTTP-Final-Check, FAIL-CLOSED) +- `app/control/__init__.py` +- `app/consumer/control_consumer.py` — **ControlCacheConsumer** (`market.control`/`trading.state`) + +### Geänderte Dateien +- `app/core/service.py` — Control-Check zwischen Schritt 4 (Order anlegen) und Schritt 5 (`_submit_and_track`); `_apply_control_audit`; `action_type`-Ableitung; Consumer-Start/Stop +- `app/core/models.py` — `ExecutionOrder` + Audit-Felder +- `app/config.py` — M15-URL/Timeout/TTL/Cache-TTL + Control-Exchange/Routing-Key +- `app/storage/storage.py` — INSERT/UPDATE um control_*-Felder; `update_control_audit` +- `migrations/001_execution.sql` — Audit-Spalten (idempotent via `ADD COLUMN IF NOT EXISTS`) + +### Audit-Felder je Order +`action_type`, `control_status`, `control_state_version`, `control_expires_at`, +`control_audit_id`, `control_check_source` (`http`|`cache`|`bypass`|`none`). + +### Parameter +- Final-Check-HTTP-Timeout: **500 ms**, Retry: **max 1** sofort. +- `expires_at`-TTL (M15): **500 ms**. +- Cache-TTL (M09): **5 s**. +- Kein Auto-Resume; kein "Senden ohne Check". + +--- + +## 4. Live-E2E-Ergebnisse (VPS, 20.08.2026) + +Alle Szenarien auf dem VPS (187.124.31.123) live ausgeführt. **Alle grün.** + +| # | Szenario | Ergebnis | +|---|----------|----------| +| S1 | **ENABLED-Pfad**: OPEN-Order durch, Auditfelder in DB | **9/9 PASS** | +| S2 | **HALTED-Pfad**: M03 gestoppt → OPEN blockiert, kein Broker-Send, reason `CONTROL_HALTED` | **9/9 PASS** | +| S3 | **PAUSED-Pfad**: nicht-krit. Service down → OPEN blockiert, reason `CONTROL_PAUSED` | **4/4 PASS** | +| S4 | **Risikoabbau bei HALTED**: REDUCE/CLOSE/CANCEL erlaubt (bypass) | **4/4 PASS** | +| S5 | **M15-down**: OPEN FAIL-CLOSED (`CONTROL_UNREACHABLE`), REDUCE/CLOSE/CANCEL erlaubt | **7/7 PASS** | +| S6 | **Cache-vs-Final-Check**: HTTP HALTED gewinnt über Cache | **2/2 PASS** | +| S7 | **Race/TTL**: Doppel-Publish → keine Doppelorder (Idempotenz) | **2/2 PASS** | +| S8 | **Event-Cache**: 1 Consumer, 0 Backlog, Reconnect nach RabbitMQ-Restart | **verifiziert** | +| S9 | **Recovery**: nach Behebung wieder ENABLED, OPEN erlaubt | **3/3 PASS** | +| S10 | **Health/Security**: health/ready 200, keine öffentl. Ports, keine Secrets, keine echten Tracebacks | **verifiziert** | +| S11 | **Regression**: bestehender M09-Paper-E2E (Kette 03→09) | **ALLE CHECKS BESTANDEN** | + +### S1-Detail (ENABLED) +``` +control_status=ENABLED +control_state_version=1 +control_expires_at=1787241066.98 +control_audit_id=ctl-2073194e651040db +control_check_source=http +action_type=OPEN +broker_order_id=PAPER-EXEC-... (Broker-Send erfolgt) +status=FILLED +``` + +### S2-Detail (HALTED) +``` +control_status=HALTED +control_state_version=2 +control_audit_id=ctl-b2cd6303f8754328 +control_check_source=http +action_type=OPEN +broker_order_id=None (KEIN Broker-Send) +reason_codes=['CONTROL_HALTED'] +status=FAILED +``` + +### S5-Detail (M15-down) +``` +reason_codes=['CONTROL_UNREACHABLE'] +broker_order_id=None (FAIL-CLOSED, kein Broker-Send) +REDUCE/CLOSE/CANCEL → control_check_source=bypass (erlaubt) +``` + +--- + +## 5. Verifikation Health/Security + +- M09 + M15 `health` und `health/ready` → **200**. +- Keine öffentlichen Ports (nur Docker-intern via `expose:`). +- Keine Secrets in Logs/API/DB. +- Tracebacks in Logs: ausschließlich pika `AMQPConnectionError` während des + kontrollierten RabbitMQ-Restarts (erwartete Reconnect-Logs, keine echten Fehler). + +--- + +## 6. Offene Punkte / Freigabe + +- **Finale Freigabe** M15/M09 als FREIGEGEBEN markieren: **ausstehend** (Nutzer-Bestätigung). +- M16: **nicht beginnen**. +- Credential-Cleanup: temporäre Forgejo-Tokens/Deploy-Keys → count=0 (siehe Git-Commit). + +--- + +*Implementierung + Live-E2E abgeschlossen. Finale Freigabe steht aus.*