# Red Queen — A3: Deterministic Safety Layer (V1) Der **Deterministic Safety Layer** sitzt zwischen Mission/WP-State (A2 `missions.db`) und dem **späteren** Orchestrator/Action/Retry/Delegation. Er entscheidet **deterministisch** (Zähler, Limits, Tabellen — nicht „LLM-Lust"): ``` CONTINUE / RETRY / DEBUG / SECOND_OPINION / BLOCK / ESCALATE / CIRCUIT_BREAK ``` Ziel: Red Queen erhält die Safety-Logik, **BEVOR** spätere autonome Mission-Loops existieren. Dieser Build enthält **KEINE** autonome Orchestrierung — kein dispatcher, kein loop, kein heartbeat, kein cron, kein self-improvement. A3 ist eine reine **Library / Safety-Capability**. --- ## DB-Entscheidung (Maker-Entscheidung, §29) A3 nutzt eine **eigene `safety.db`** (separate SQLite-Datei), **nicht** die A2 `missions.db`: - **Rollback-sicher für A2:** Die bestehende A2-Datenbank wird überhaupt nicht angefasst — kein ALTER, kein neues Schema in `missions.db`, keine Gefahr für Bestandsdaten (`CREATE TABLE IF NOT EXISTS` auf den A3-Tabellen in `safety.db`). - **Entkopplung:** Safety-State (Circuit, Attempts, Events, Evidence) ist unabhängig vom Mission-State. Ein beschädigter Mission-State kann den Safety-Layer nicht mitreißen und umgekehrt. - **Testbar:** A3-DB wird immer über temp-Pfade getestet, nie produktiv berührt. Die A2-Integration erfolgt sauber: `SafetyStore` verweist Missions-/WP-IDs als Fremdschlüssel-Namen (mission_id/wp_id) und ruft A2-Regeln nicht auf; A2-APIs `mission_block`/`mission_transition` bleiben unberührt. Mission-`BLOCKED` kann in A4 durch den Orchestrator auf Basis einer `BLOCK`-Safety-Entscheidung gesetzt werden — A3 selbst mutiert A2 nicht direkt. --- ## Module | Datei | Inhalt | |-------|--------| | `rq_safety.py` | Kernmodul: `SafetyStore`, `SafetyError`, Error-Signature, Strategy-Fingerprint, Redaction, Circuit Breaker, Retry Controller, Oscillation, Fail-Closed, Safety Events, Evidence, `evaluate_next_action` (§21) | | `rq_safety_telegram.py` | Telegram-Notification-Interface (§19): `format_alert`, `build_payload`, `should_notify` (Anti-Spam). KEIN Daemon. | | `rq_safety_cli.py` | Dünne, deterministische CLI (`--json`, Exit-Code 2 bei `SafetyError`), A2-CLI-Muster spiegelnd | | `test_a3.py` | Isolierte Testsuite (temp-DB), Exit-Code 0 = PASS | --- ## Kern-API (`SafetyStore`) ```python from rq_safety import SafetyStore store = SafetyStore("path/to/safety.db") # Attempt Ledger (idempotent via idempotency_key, append-only, redacted) store.record_attempt("M1", "W1", actor="maker", result="FAIL", error="boom pid=123", strategy="s1", idempotency_key="k-1") # Retry / Oscillation / Circuit / Fail-closed store.evaluate_next_action("M1", "W1", error="boom", strategy_label="s2", is_mutating=True) # -> DECISION/REASON_CODE/ALLOWED_ACTION # Circuit Breaker store.open_circuit("MISSION", "M1", trigger="REG", severity="HIGH") store.circuit_state("MISSION", "M1") store.request_circuit_reset("MISSION", "M1", cause="...", recovery_evidence="...") store.close_circuit("MISSION", "M1", approved_by="human", gate="human_gate", cause="...", recovery_evidence="...") # Events / Evidence store.safety_event("OSCILLATION_DETECTED", severity="CRITICAL", mission_id="M1") store.safety_evidence("test_results", {"failing": 2}) store.evidence() ``` --- ## Entscheidungen (Reason Codes, §22) | Code | Bedeutung | |------|-----------| | `RETRY_AVAILABLE` | nächste Aktion erlaubt | | `RETRY_LIMIT` | Maker/Checker-Repair MAX 3 erreicht → SECOND_OPINION | | `SAME_ERROR_LIMIT` | gleiche Error-Signatur MAX 2 → DEBUG/Strategiewechsel | | `FAILED_STRATEGY_REPEAT` | bereits gescheiterte Strategie → keine blinde Wiederholung | | `NO_MEASURABLE_PROGRESS` | FAIL ohne messbaren Fortschritt (UNKNOWN ≠ Progress) | | `OSCILLATION_ABAB` | A-B-A-B-Muster → Circuit-Breaker-Kandidat | | `CIRCUIT_ALREADY_OPEN` | Circuit OPEN → BLOCK (read-only erlaubt) | | `STATE_INCONSISTENT` | Fail-Closed: Safety-State korrupt → keine Mutation | | `CRITICAL_TRIGGER` | kritischer GLOBAL-Trigger | | `HUMAN_GATE_REQUIRED` | Circuit-Reset braucht Human Gate bei HIGH/CRITICAL/GLOBAL | --- ## Limite (A1 SAFETY_CONTRACT, konservativ V1) - Maker→Checker-Repair: **MAX 3** - Gleiche Error-Signatur: **MAX 2** - Gleiche bereits gescheiterte Strategie: **keine blinde Wiederholung** - Oscillation A-B-A-B: Circuit-Breaker-Kandidat - `MAX_ITERATIONS=50`: äußerste Runtime-Notbremse, **nicht** operatives Retry-Limit --- ## Fail-Closed (§16) Bei unbekanntem/inkonsistentem Safety-State → `SAFETY_STATE_ERROR` → **STOP** → **Evidence sichern** → **KEINE Mutation** (read-only Diagnose ggf. erlaubt). --- ## Secret-Safety (§28) - Alle credential-artigen Werte werden **vor** Persistenz UND vor Fingerprint/Signatur- Ableitung **redacted** (`redact_secret`). - Keine Tokens/Passwörter/private Keys/Authorization-Header in `safety.db`/Events/Telegram. - Secret Exposure wird nur als `SECRET_EXPOSURE_DETECTED` + LOCATION/TYPE + `VALUE=REDACTED` erfasst. --- ## Telegram Interface (§19) Kein Daemon. Nur Formatter/Payload. **Kein Spam** — nur signifikante Events (`CIRCUIT_OPENED`, `CRITICAL`, `ESCALATION_REQUIRED`, `HUMAN_DECISION_REQUIRED`, `RETRY_LIMIT_REACHED`, `OSCILLATION_DETECTED`, `SAFETY_STATE_ERROR`). Payload ist deterministisch und **redacted**. ```python from rq_safety_telegram import build_payload payload = build_payload("CIRCUIT_OPENED", "CRITICAL", reason="...", mission_id="M") # payload["notify"] == True, payload["text"] fertig formatiert ``` --- ## Tests ```bash python3 test_a3.py # Exit 0 = PASS; isoliert (temp-DB), produktive DBs unberührt ``` Abgedeckt: Attempt Ledger + Idempotenz, Error-Signature-Normalisierung, Strategy- Fingerprint, Retry-Limits, Progress, Oscillation A-B-A-B, False-Positives, Circuit-Breaker (+Restart-Persistenz + Negativ-Test), Fail-Closed, Events, Evidence, Secret-Safety, Telegram-Interface, Loop-Simulation (A–E), A2-DB-Isolation.