- 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)
14 KiB
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)
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):
c5-delete approve --commit <sha> --object-id <id> --path <path>→ persistiert Approval (NUR Approval, KEIN DELETE)c5-delete execute --commit <sha>→ führt DELETE aus (verlangt persistierte Approval)c5-delete status --commit <sha>→ read-only Statusc5-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:
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)
- ObjectChange existiert (
get_object_change) operation == DELETE- Commit-Status ==
ST_DELETE_APPROVED(definierter approved state) - Approval existiert,
approval_status == APPROVED, gehört exakt zu diesem ObjectChange object_idstimmt (Approval ↔ ObjectChange)pathstimmt (Approval ↔ ObjectChange)- 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 ExecutionST_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 Statusc5-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
pathnur aus dem ObjectChange, das durch Approval gebunden ist. - Keine path traversal:
pathwird gegenVAULT_PREFIXvalidiert. - Keine ungeprüften freien Delete-Targets: Kein freier Pfad-Parameter im Executor.
- Keine Approval-Reuse:
approval_ideindeutig; 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.