145 lines
5.9 KiB
Markdown
145 lines
5.9 KiB
Markdown
# 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.
|