# Staging-Freigabe für irreversible Aktionen

Setzt A2 aus der [Roadmap](Roadmap.md) um (F-A1 + S4). Irreversible Aktionen werden
**gestaged statt ausgeführt**: Vorschlag → Review im Hauptfenster → Freigabe/Ablehnung.
Aufbauend auf dem Taskboard (A1, Fortsetzung nach Freigabe) und dem Audit-Log (A3,
Approval-Records).

Sicherheitswirkung: Eine Prompt-Injection (K2) kann dann nur noch einen **Vorschlag**
erzeugen, keine Ausführung.

## Das Gate wird zum Durchsetzungspunkt (S4)

Das bisher wirkungslose `PermissionGate` (es prüfte nur, ob ein Tool zugewiesen ist)
wird zur zentralen Stelle: Policy-Prüfung, Staging-Entscheidung und Audit-Hook an
**einer** Stelle (`AgentEngine.ExecuteToolCallAsync`), statt ad-hoc in jedem Tool. S4
geht hier auf.

## Policy: `auto | approve | deny` pro Tool/Aktion

Je (Tool, Aktion) eine Entscheidung:

- **`auto`** — läuft wie bisher.
- **`approve`** — wird gestaged; die Ausführung wartet auf eine menschliche Freigabe.
- **`deny`** — wird gar nicht erst vorgeschlagen, sondern abgelehnt.

Auflösung vom Speziellen zum Allgemeinen: `Tool.Aktion` → `Tool` → Standard (`auto`).
Die „Aktion" ist das `action`-Argument des Aufrufs (die meisten Tools haben es).

**Eingebaute Standardregeln** (nach Sichtung der Tools, überschreibbar per Konfiguration)
— genau die in der Roadmap genannten irreversiblen Aktionen:

| Tool.Aktion | Standard |
|---|---|
| `Mail.send` | approve |
| `Telegram.send_message` | approve |
| `Database.insert`, `Database.upsert` | approve |
| `FileRW.delete` | approve |
| `FTP.upload`, `FTP.delete` | approve |
| alles andere | auto |

Lesende Aktionen (`Mail.read_inbox`, `FileRW.read`, `Database.query`, …) bleiben `auto` —
Staging soll schützen, nicht lähmen.

## Plan-Freeze

Freigegeben wird ein **eingefrorener, konkreter Aufruf**: Tool, Aktion und die **exakten
Argumente** zum Zeitpunkt des Stagings. Ausgeführt wird genau das Eingefrorene (der
gespeicherte Argument-JSON), nie eine nachträglich veränderte Fassung. Jede Änderung
wäre eine neue Freigabe.

Das ist auch der Grund, warum der Agent den Aufruf **nicht** nach der Freigabe erneut
formuliert (er könnte etwas anderes bauen) — der eingefrorene JSON wird direkt an das
Tool gegeben.

## Fortsetzung nach Freigabe — kein pausierter Lauf

Die tragende Architektur-Vorgabe (aus der Roadmap-Einstufung): **Kein pausierter, im
Speicher gehaltener Lauf.** Der Ablauf:

1. **Vorschlag.** Der Agent ruft eine `approve`-Aktion auf. Das Gate führt sie nicht aus,
   sondern legt einen **Pending**-Datensatz an (eingefrorener Aufruf) und gibt dem Agenten
   „Zur Freigabe vorgelegt (#id)" als Tool-Ergebnis zurück. Der Lauf endet **regulär** —
   der Agent schließt ab, nichts hängt im Speicher.
2. **Review.** Ein Mensch sieht die offenen Vorschläge im Hauptfenster und entscheidet.
3a. **Freigabe.** Der eingefrorene Aufruf wird **direkt** ausgeführt (standalone, nicht
   über einen Chat-Lauf). Das Ergebnis wird festgehalten, und ein **Folge-Task** (A1)
   weckt den Agenten: „Deine Aktion X wurde freigegeben und ausgeführt, Ergebnis: Y —
   mach weiter." Der Scanner stellt ihn zu (`ChatAsync`, bestehender Kontext).
3b. **Ablehnung.** Ein Folge-Task weckt den Agenten mit der Ablehnung (samt Grund).

Echtes Suspend/Resume eines laufenden `ChatAsync` wäre Fable-Terrain — und ist mit dieser
Vereinfachung unnötig.

## Approval-Records (A3)

Jede Entscheidung — Freigabe wie Ablehnung — wird an zwei Stellen verankert:

- Im **Staging-Datensatz** selbst: Status, `DecidedBy`, `DecidedAt`, Ergebnis-Verweis
  bzw. Ablehnungsgrund.
- Als **Audit-Eintrag** (A3): Tool, Ausgang, `source: approval`, „Freigegeben von …" bzw.
  „Abgelehnt von …". Die Herkunft stempelt auch hier das System, nicht der Agent.

Der Vorschlag selbst wird beim Anlegen als Audit-Eintrag mit Status **`Staged`** notiert —
so ist die ganze Kette (Vorschlag → Entscheidung → Ausführung) im Log nachvollziehbar.

## Nebenläufigkeit

Zwei Reviewer dürfen nicht denselben Vorschlag doppelt freigeben. Der Übergang
`pending → approved/rejected` ist ein **atomares, bedingtes `UPDATE`** (dieselbe
Claim-Technik wie beim Taskboard): Genau einer gewinnt, der zweite Klick läuft ins Leere.
Erst nach gewonnenem Übergang wird der eingefrorene Aufruf ausgeführt.

## Verdrahtung

- `StagingGate` (Policy + Anlegen des Vorschlags) hängt optional an der Engine — ohne es
  läuft alles wie bisher (`auto`).
- Der eingefrorene Aufruf wird über `IFrozenCallExecutor` (von der Engine implementiert)
  ausgeführt: gültiger Tool-Kontext, aber ohne LLM-Schleife.
- `StagingService` (Freigabe/Ablehnung) nutzt Executor, Audit und das Taskboard für den
  Folge-Task. Es ist die API, die die Review-Oberfläche aufruft.

## Offen

- **Review-Oberfläche** im Hauptfenster (Liste der offenen Vorschläge, Freigeben/Ablehnen)
  — die Dienst-API steht bereit; die Freigabe-Ansicht in `src/ClawdDotNet.Desktop` ist
  die verbleibende Integration und der einzige offene Punkt von A2.
- **Output-Scrubbing** greift auch hier auf den gespeicherten Argument-JSON, sobald es
  steht (eigener Roadmap-Punkt).
- **Orders** (Handelsaufträge) reihen sich später als weitere `approve`-Aktionen ein.
