# 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 C5A–C5E). Ergebnis: | Owner | Verantwortlich | Status | |---|---|---| | DELETE_DETECTION_OWNER | C5B `ChangeClassifier.classify()` (Z.520–523): `path_before && !path_after` → `OP_DELETE_REQUEST` | vorhanden | | DELETE_HUMAN_GATE_OWNER | C5A `SyncStateMachine.process_commit()` (Z.794–797) + C5C `_propagate_object()` (Z.492–496) → `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 (A–N) 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 --object-id --path ` → persistiert Approval (NUR Approval, KEIN DELETE) 2. `c5-delete execute --commit ` → führt DELETE aus (verlangt persistierte Approval) 3. `c5-delete status --commit ` → read-only Status 4. `c5-delete replay --commit ` → 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 A–F) | 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 --object-id --path ` → persistiert Approval (NUR Approval) - `c5-delete execute --commit ` → führt DELETE aus (verlangt persistierte Approval) - `c5-delete status --commit ` → read-only Status - `c5-delete replay --commit ` → 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.