--- type: Modul _organized: true --- # Modul-15 → Modul-09 Control-Anbindung — Implementierung & Live-E2E **Status: FREIGEGEBEN / PRODUKTIV VERIFIZIERT** (20.08.2026, Nutzer-Bestätigung). **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) erfolgte am **20.08.2026** > durch 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. Freigabe-Status - **FREIGEGEBEN / PRODUKTIV VERIFIZIERT** (20.08.2026, Nutzer-Bestätigung). - M15/M09-Control-Anbindung ist **final freigegeben** — keine weiteren technischen Änderungen an M09/M15. - M16: **nicht beginnen**. - Credential-Cleanup: temporäre Forgejo-Tokens/Deploy-Keys → count=0 (siehe Git-Commit). --- *Implementierung + Live-E2E abgeschlossen. Finale Freigabe erteilt (20.08.2026).*