trading-system-docs/tolaria/c5-sync-service/C5_DELETE_EXECUTION_ARCHITECTURE_DECISION.md
Red Queen 3db1c71b68 C5: Human-Gated DELETE Execution Contract (Todo 7-13)
- DeleteExecutor (rq_c5_delete.py): Pre-Gates, Read-Back, idempotenter replay
- TolariaClient.delete() (rq_c5c.py): kontrollierter DELETE, keine Probes
- Approval-Store + Reason Codes RC_DELETE_APPROVAL_MISSING/MISMATCH (rq_c5a.py)
- CLI: c5-delete-approve/execute/replay/status (rq_c5_cli.py)
- C5E: recover()/replay() DELETE-Integration
- C5D: verify_integrity prueft secret_blocked_objects (FAIL CLOSED)
- Security: Path-Traversal-Block in _normalize_vault_path
- Drift nach DELETE -> FAIL CLOSED zurueck zu HUMAN_REVIEW_REQUIRED
- Tests: test_c5_delete (19), test_c5_delete_integration (22),
  test_c5_delete_fresh_checker (17) — alle gruen
- ADR: C5_DELETE_EXECUTION_ARCHITECTURE_DECISION.md (ACCEPTED)
2026-08-26 19:22:44 +00:00

298 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# C5 DELETE EXECUTION CONTRACT — ARCHITECTURE DECISION RECORD
**Status:** ACCEPTED (Christian, OPTION 1 — CONTRACT-ERWEITERUNG, 2026-08-26)
**Scope:** C5 Sync Service — kontrollierter Tolaria-DELETE-Pfad NACH expliziter Human-Freigabe
**Datei:** `tolaria/c5-sync-service/C5_DELETE_EXECUTION_ARCHITECTURE_DECISION.md`
---
## 1. Ausgangslage / DELETE_EXECUTION_CONTRACT_GAP
STRICT PRE-FLIGHT (read-only) hat den bestehenden DELETE-Contract vollständig analysiert
(`rq_c5a.py`, `rq_c5b.py`, `rq_c5c.py`, `rq_c5d.py`, `rq_c5e.py`, `rq_c5_cli.py`,
`C5AStore`, `TolariaClient`, Tests C5AC5E). Ergebnis:
| Owner | Verantwortlich | Status |
|---|---|---|
| DELETE_DETECTION_OWNER | C5B `ChangeClassifier.classify()` (Z.520523): `path_before && !path_after``OP_DELETE_REQUEST` | vorhanden |
| DELETE_HUMAN_GATE_OWNER | C5A `SyncStateMachine.process_commit()` (Z.794797) + C5C `_propagate_object()` (Z.492496) → `ST_HUMAN_REVIEW_REQUIRED` + `RC_AMBIGUOUS_DELETE` | vorhanden (terminal) |
| DELETE_APPROVAL_STORAGE | **fehlt** — C5AStore hat keine Approval-Repräsentation (nur `meta`, `commits`, `objects`, `bootstrap`, `health`) | GAP |
| DELETE_EXECUTION_OWNER | **fehlt**`TolariaClient` hat keine `delete()`; kein Executor | GAP |
| DELETE_VERIFICATION_OWNER | **fehlt für DELETE**`verify()` nur für Writes (Read-Back+Hash); kein DELETE-absent-Read-Back | GAP |
| DELETE_REPLAY_OWNER | C5E `recover()`/`replay()`: `ST_HUMAN_REVIEW_REQUIRED``REC_HUMAN_REVIEW` (kein Executor) | GAP |
| DELETE_APPLIED_OWNER | C5D `apply_commit()``ST_APPLIED`+`mark_applied()`, startet nur aus `ST_UPDATING_SEARCH` — DELETE-Commit erreicht das nie | GAP |
**Kernbefund:** DELETE wird erkannt und zum Human Gate geführt, aber das Gate ist **terminal**.
Es existiert kein Approval-Storage, kein Executor, kein DELETE-Read-Back, kein Replay- und
kein APPLIED-Pfad. Dies ist der `DELETE_EXECUTION_CONTRACT_GAP`, der durch diese Mission
sauber geschlossen wird.
---
## 2. ZIELARCHITEKTUR (verbindlich)
```
DELETE erkannt (C5B)
→ HUMAN_REVIEW_REQUIRED (C5A/C5C, RC_AMBIGUOUS_DELETE) [bleibt erhalten]
→ explizite Human Approval (CLI approve-delete) [NEU]
→ Approval persistent/auditierbar (C5AStore) [NEU]
→ kontrollierter Tolaria DELETE (DeleteExecutor) [NEU]
→ Read-Back-Verifikation (target absent) [NEU]
→ C5D Source Build (bestehender Pfad) [wiederverwendet]
→ Search Update (bestehender C5D-Pfad) [wiederverwendet]
→ Exact-Set-Verifikation (C5D verify_integrity) [wiederverwendet]
→ APPLIED (C5D mark_applied) [wiederverwendet]
```
**KEIN automatischer DELETE.** Der bestehende Sicherheitsmechanismus
`OP_DELETE_REQUEST → HUMAN_REVIEW_REQUIRED → RC_AMBIGUOUS_DELETE` bleibt unverändert.
---
## 3. Zwingende Invarianten (AN) und ihre Umsetzung
| Invariante | Umsetzung |
|---|---|
| **A) DELETE bleibt HUMAN_GATED** | Kein Pfad führt einen DELETE ohne persistierte, explizite Human-Approval aus. `_HUMAN_GATE_OPS` unverändert. |
| **B) Kein LLM/Agent entscheidet, dass DELETE genehmigt ist** | Approval wird AUSSCHLIESSLICH durch den CLI-Befehl `approve-delete` erzeugt (Human Gate). Kein Code erzeugt Approval automatisch. |
| **C) Approval stammt explizit vom Human Gate** | `approve-delete` persistiert `approved_by` (Human-Gate-Provenance) + `approval_nonce`. |
| **D) Approval commit- und objectchange-spezifisch** | Approval bindet exakt `(workflow_commit, object_change_id, object_id, path)`. Keine globale `DELETE_APPROVED=true`-Semantik. |
| **E) Approval für Objekt A nie Objekt B** | Executor validiert `object_id` + `path` gegen die Approval. Abweichung → FAIL CLOSED. |
| **F) Approval für Commit X nie DELETE aus Commit Y** | Approval bindet `workflow_commit`. Executor validiert Commit-SHA. |
| **G) Approval persistent und crash-/restart-fest** | SQLite-Tabelle `delete_approvals` in C5AStore (atomic, WAL, restart-safe). |
| **H) Execution idempotent** | Pre-Delete-Drift-Check + Read-Back. Wenn Ziel bereits absent → `DELETE_ALREADY_AT_TARGET`, kein zweiter destruktiver Write. |
| **I) DELETE nur exakt freigegebenes Objekt** | Executor nimmt `path`/`object_id` ausschließlich aus dem ObjectChange, das durch die Approval gebunden ist. |
| **J) Kein Pfad nimmt beliebige freie Paths/IDs ohne ObjectChange+Approval** | Executor verlangt existierendes ObjectChange (`operation==DELETE`) + exakt passende Approval. Kein freier Pfad-Parameter. |
| **K) Tolaria DELETE vor und nach Execution verifiziert** | Pre-Delete-Drift-Check (Objekt existiert noch) + Post-Read-Back (target absent + kein anderes Objekt verändert). |
| **L) Keine manuelle SQLite-/SQL-State-Manipulation** | Nur Store-Methoden. Kein direktes SQL im Executor. |
| **M) Keine direkte APPLIED-Markierung** | APPLIED nur durch C5D `apply_commit()` nach vollem Search-PASS. Executor markiert NIE APPLIED. |
| **N) Keine automatische Human-Gate-Deaktivierung** | `_HUMAN_GATE_OPS` unverändert; DELETE bleibt Gate-pflichtig. |
---
## 4. APPROVAL CONTRACT
**Prüfung bestehender State:** C5AStore besitzt KEINE geeignete persistente
Approval-Repräsentation (nur `meta`-KV für source_provenance, `commits`, `objects`,
`bootstrap`, `health`). → **Neuer minimaler auditierbarer Approval-State.**
### 4.1 Schema (neue Tabelle `delete_approvals` in C5AStore)
```sql
CREATE TABLE IF NOT EXISTS delete_approvals (
approval_id TEXT PRIMARY KEY, -- UUID, eindeutig
workflow_commit TEXT NOT NULL, -- Commit-SHA (Invariante F)
object_change_id INTEGER NOT NULL, -- FK auf objects.id (Invariante D/E)
object_id TEXT NOT NULL, -- exakt freigegebenes Objekt (E/I)
path TEXT NOT NULL, -- exakter Vault-Pfad (E/I)
operation TEXT NOT NULL DEFAULT 'DELETE',
approval_status TEXT NOT NULL, -- APPROVED | USED | REVOKED
approved_by TEXT NOT NULL, -- Human-Gate-Provenance (C)
approval_nonce TEXT NOT NULL, -- eindeutig, verhindert Reuse
approved_at INTEGER NOT NULL,
used_at INTEGER,
UNIQUE(workflow_commit, object_change_id, object_id, path)
)
```
**Keine Credentials, keine personenbezogenen Daten.** `approved_by` = Human-Gate-Identifier
(z.B. `human:christian`), kein Secret.
### 4.2 Keying
Approval ist eindeutig gebunden an `(workflow_commit, object_change_id, object_id, path)`.
Keine globale `DELETE_APPROVED=true`-Semantik. Ein Approval autorisiert GENAU EINEN
ObjectChange in GENAU EINEM Commit.
### 4.3 CLI-Modell (kein undifferenzierter approve+delete-Blackbox)
Bevorzugtes Modell (Mission §3):
1. `c5-delete approve --commit <sha> --object-id <id> --path <path>` → persistiert Approval (NUR Approval, KEIN DELETE)
2. `c5-delete execute --commit <sha>` → führt DELETE aus (verlangt persistierte Approval)
3. `c5-delete status --commit <sha>` → read-only Status
4. `c5-delete replay --commit <sha>` → idempotenter Replay nach Crash
Der Human-Gate-Beweis (persistierte Approval) geht dadurch NIE verloren.
---
## 5. TOLARIA DELETE CLIENT
`TolariaClient.delete(vault_path)` — minimal, isoliert:
```python
def delete(self, vault_path: str) -> Dict[str, Any]:
"""Loescht ein Vault-Objekt (POST /delete). NUR DeleteExecutor darf rufen."""
resp = self._post("delete", {"path": vault_path})
if resp is None:
resp = {}
if "error" in resp:
raise TolariaWriteError(f"Tolaria delete fehlgeschlagen: {resp['error']}",
RC_INVALID_SCHEMA)
return resp
```
**Endpoint-/Payload-Contract** wird ausschließlich aus dem vorhandenen Tolaria-Code/Router
und der dokumentierten Incident-Evidence bestimmt (POST `/api/vault/delete` mit
`{"path": ...}`). **KEINE produktive Endpoint-Probe.** Die Incident-Regel gilt verbindlich:
`NO MUTATING HTTP METHOD PROBES DURING PRODUCTION READ-ONLY DISCOVERY`.
---
## 6. DELETE EXECUTION (DeleteExecutor)
### 6.1 Pre-Delete-Gates (alle MÜSSEN passen, sonst FAIL CLOSED)
1. ObjectChange existiert (`get_object_change`)
2. `operation == DELETE`
3. Commit-Status == `ST_DELETE_APPROVED` (definierter approved state)
4. Approval existiert, `approval_status == APPROVED`, gehört exakt zu diesem ObjectChange
5. `object_id` stimmt (Approval ↔ ObjectChange)
6. `path` stimmt (Approval ↔ ObjectChange)
7. Pre-Delete-Drift-Check: Tolaria enthält das Objekt noch (erwarteter aktueller Zustand)
Bei irgendeiner Abweichung → **FAIL CLOSED** (kein DELETE, kein State-Übergang).
### 6.2 Execution
- `client.delete(vault_path)` (nur DeleteExecutor)
- **Read-Back:** `client.read(vault_path) is None` → target absent == TRUE
- **Zusätzlich:** `client.list()` — kein anderes Objekt verändert (Set-Vergleich vor/nach)
### 6.3 Nach erfolgreichem DELETE
- Approval → `USED` (einmalig, verhindert Reuse)
- Commit → `ST_UPDATING_SEARCH` (C5D übernimmt Search-Pfad)
---
## 7. IDEMPOTENZ / CRASH-RECOVERY (Fälle AF)
| Fall | Zustand | Deterministischer Replay-Pfad |
|---|---|---|
| **A) Crash vor Approval** | `ST_HUMAN_REVIEW_REQUIRED`, keine Approval | Nichts zu tun. Kein DELETE. Replay: bleibt Human Gate. |
| **B) Crash nach Approval, vor DELETE** | `ST_DELETE_APPROVED`, Approval persistiert | Replay: Executor erkennt Approval, führt DELETE aus. |
| **C) Crash während DELETE** | `ST_DELETING` | Replay: Read-Back. Objekt noch da → DELETE erneut (Approval gültig). Objekt absent → `DELETE_ALREADY_AT_TARGET`, weiter zu `ST_UPDATING_SEARCH`. |
| **D) Crash nach DELETE, vor Read-Back** | `ST_DELETING` | Replay: Read-Back → absent → weiter zu `ST_UPDATING_SEARCH`. |
| **E) Crash nach Read-Back, vor Search Update** | `ST_UPDATING_SEARCH` | C5D `apply_commit()` übernimmt (bestehender Pfad). |
| **F) Crash nach Search Update, vor APPLIED** | `ST_VERIFYING_SEARCH` | C5D `apply_commit()` übernimmt (bestehender Pfad). |
**DELETE_ALREADY_AT_TARGET:** Wenn Tolaria das freigegebene Objekt bereits nicht mehr
enthält, erkennt der Executor anhand Approval + erwarteter Identität + Read-Back
`DELETE_ALREADY_AT_TARGET` statt blind erneut zu mutieren.
---
## 8. SEARCH CONTRACT
Nach erfolgreichem Tolaria-DELETE wird Search NICHT über einen statischen/improvisierten
Pfad aktualisiert. Der bestehende C5D-Pfad wird wiederverwendet:
```
Tolaria current verified state (ohne gelöschtes Objekt)
→ SearchSourceBuilder.build() (read-only, deterministisch)
→ atomic source + Provenance-Persistenz (persist_search_source_provenance)
→ Search-Rebuild (search.rebuild)
→ Exact-Set-Verifikation (verify_integrity: expected IDs/Paths exakt)
→ APPLIED (mark_applied)
```
**Kein APPLIED bei Search-Mismatch** (C5D `_handle_failure` → Human Gate / DEAD).
---
## 9. STATE MACHINE CHANGES
Neue Zustände (minimal, nur für den DELETE-Execution-Pfad):
- `ST_DELETE_APPROVED = "DELETE_APPROVED"` — nach Approval, vor Execution
- `ST_DELETING = "DELETING"` — während Execution (Crash-Window)
Neue erlaubte Transitionen (NUR durch DeleteExecutor/Approval auslösbar, nie automatisch):
```
(ST_HUMAN_REVIEW_REQUIRED, ST_DELETE_APPROVED) # nur via expliziter Approval
(ST_DELETE_APPROVED, ST_DELETING) # Executor startet
(ST_DELETING, ST_UPDATING_SEARCH) # nach DELETE + Read-Back → C5D
(ST_DELETING, ST_HUMAN_REVIEW_REQUIRED) # Fehler → zurück zum Gate
(ST_DELETE_APPROVED, ST_HUMAN_REVIEW_REQUIRED) # Revoke/Fehler
```
**Keine Änderung** an `_HUMAN_GATE_OPS`, `RC_AMBIGUOUS_DELETE`, oder den bestehenden
Transitions. Der bestehende `OP_DELETE_REQUEST → HUMAN_REVIEW_REQUIRED`-Pfad bleibt
unverändert.
---
## 10. CLI CHANGES
Neue Befehle in `rq_c5_cli.py`:
- `c5-delete approve --commit <sha> --object-id <id> --path <path>` → persistiert Approval (NUR Approval)
- `c5-delete execute --commit <sha>` → führt DELETE aus (verlangt persistierte Approval)
- `c5-delete status --commit <sha>` → read-only Status
- `c5-delete replay --commit <sha>` → idempotenter Replay nach Crash
**Kein undifferenzierter approve+delete-Blackbox-Befehl.**
---
## 11. SECURITY REVIEW (Design-Zusicherungen)
- **Keine arbitrary path deletion:** Executor nimmt `path` nur aus dem ObjectChange, das durch Approval gebunden ist.
- **Keine path traversal:** `path` wird gegen `VAULT_PREFIX` validiert.
- **Keine ungeprüften freien Delete-Targets:** Kein freier Pfad-Parameter im Executor.
- **Keine Approval-Reuse:** `approval_id` eindeutig; nach Nutzung → `USED`.
- **Keine Cross-Commit-Approval:** Approval bindet `workflow_commit`.
- **Keine Cross-Object-Approval:** Approval bindet `object_id` + `path`.
- **Keine automatische Approval-Eskalation:** Nur expliziter CLI-Befehl erzeugt Approval.
- **Keine Secrets:** Approval speichert keine Credentials/personenbezogenen Daten.
- **Keine Credential-Ausgabe:** Kein Secret im Approval-/Status-Output.
- **Keine manuelle DB-Manipulation:** Nur Store-Methoden.
- **Keine allgemeine DELETE-Automatisierung:** Kein Pfad führt DELETE ohne Approval.
---
## 12. TESTPLAN (mindestens)
- DELETE ohne Human Approval → BLOCKED
- DELETE mit Approval für anderes Objekt → BLOCKED
- DELETE mit Approval für anderen Commit → BLOCKED
- manipulierte object_id → BLOCKED
- manipulierter path → BLOCKED
- gültiges Approval → exakt ein DELETE
- anderes Objekt bleibt unverändert
- DELETE_ALREADY_AT_TARGET → kein zweiter destruktiver Write
- Restart nach Approval → Approval erhalten
- Crash nach DELETE → Replay sicher
- Search Exact-Set nach DELETE
- Search-Mismatch → kein APPLIED
- Secret Safety
- keine SQL-/State-Bypässe
- bestehende WRITE-/UPDATE-Flows ohne Regression
- HUMAN_REVIEW_REQUIRED bleibt Default für neue DELETE requests
- keine automatische Approval-Erzeugung
- Multi-Commit-Test: Approval A darf DELETE B niemals autorisieren
---
## 13. INCIDENT LESSON (verbindlich, bleibt)
```
INCIDENT_OCCURRED = TRUE
INCIDENT_RECOVERED = TRUE
PERMANENT_DAMAGE = NONE VERIFIED
ROOT_CAUSE = unsafe mutating endpoint probe during read-only discovery
```
**Neue verbindliche Regel:** `NO MUTATING HTTP METHOD PROBES DURING PRODUCTION READ-ONLY
DISCOVERY`. Diese Regel darf durch die neue DELETE-Implementierung NICHT aufgeweicht werden.
---
## 14. HARD STOP
Nach erfolgreicher Implementation, Tests, Push und Fresh Checker: **STOPP.**
KEIN realer Canary-Delete. KEIN produktiver Search-Rebuild. KEIN C5F-Cleanup. KEIN C5G.
KEINE Netzwerkänderung. KEINE Hermes-Rechte. KEINE Hermes-Autonomie.
Christian erteilt danach separat die Freigabe zur produktiven Canary-Cleanup-Validierung.