Files
ClawdDotNet/docs/umsetzungsplaene/UMSETZUNGSPLAN-FileRW-Papierkorb-Cleanup.md
RichardandClaude Opus 5 740649789e Eine Roadmap statt neun verteilter Listen
Der Stand lag ueber eine Bestandsaufnahme, drei Konzeptpapiere, vier
Umsetzungsplaene und zwei Deploymentcenter-Dokumente verteilt - jedes mit
eigener Reihenfolge, teils widersprueglich. Alle offenen Punkte daraus sind
in docs/Roadmap.md zusammengefuehrt.

Aufbau der neuen Roadmap
- Statusvokabular: erledigt / beschlossen-offen / Entscheidung noetig /
  zurueckgestellt / Idee. Damit steht das Geparkte sichtbar drin, statt in
  einem Konzeptpapier zu verschwinden.
- Herkunft-Spalte traegt das alte Kuerzel (S4, K2, B9, T4, F-A1, C1, DC1),
  damit die archivierten Papiere auffindbar bleiben, ohne sie zu lesen.
- Abschnitt 1 Reihenfolge, 2 offene Entscheidungen, 3 die Vorhaben nach
  Bereich, 4 Ideenspeicher, 5 Chronik des Erledigten, 6 Modell-Einstufung,
  7 Herkunftskarte.

Was dabei sichtbar wurde
- Neun Entscheidungen blockieren Arbeit, ohne dass sie Aufwand kosten -
  allen voran Matrix oder Rocket.Chat. Sie stehen jetzt gesammelt in
  Abschnitt 2 statt verstreut in den Diskussionsteilen der Konzepte.
- B8 (keine Tiefenbegrenzung bei AgentComm) ist unveraendert offen. Das ist
  keine Theorie: A haelt sein Gate, waehrend es auf B wartet - ruft B nun A,
  warten beide bis zum Timeout. Der Testfall A13 dafuer fehlt bis heute.
- Die vier Umsetzungsplaene vom 2026-08-05 sind alle unumgesetzt und waren
  in keiner Roadmap verzeichnet.

Archiv
Sechs Dokumente ziehen nach docs/archiv/: Bestandsaufnahme, Konzepte
Backup/Finanz/Analyse, Linux-Portierung-Analyse, Lizenz-HardwareId-v2
(gegenstandslos - LicenseLabrador ist ersetzt), Deploymentcenter-Review und
der 2.4-Integrationsplan. Sie bleiben als Begruendung lesbar, werden aber
nicht mehr fortgeschrieben; das README ordnet jedes einzeln ein und warnt,
dass ihre Quelltext-Verweise ins Leere gehen koennen.

Bauplan bleibt Bauplan
Taskboard, Audit, Staging, Memory, Agentenkommunikation, RocketChat und
Deploymentcenter-Integration bleiben in docs/ - sie sind die Detailvorgabe
fuer die Umsetzung, nicht Vorhabenlisten. Jedes bekommt oben eine Zeile,
die seine Rolle und den Umsetzungsstand nennt und auf die Roadmap zeigt.
Dasselbe fuer die vier Umsetzungsplaene: die Reihenfolge gilt in der
Roadmap, nicht im Plan.

Nebenbei repariert: Taskboard-Konzept und Teststrategie verwiesen auf
AgentScheduler und ToolJobScheduler, die seit der Scanner-Konsolidierung
geloescht sind. Alle Dokument-Verweise in docs/ sind geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:31:06 +02:00

236 lines
9.1 KiB
Markdown

# Umsetzungsplan: FileRW-Papierkorb + Cleanup-Job
> **Stand 2026-08-23: beschlossen, noch nicht gebaut.** Eingeordnet als Punkt 3 der
> [Roadmap](../Roadmap.md) — die Reihenfolge gilt dort, dieses Dokument ist der Bauplan.
> Stand: 2026-08-05
> Ziel: `FileRW.delete` löscht nicht mehr endgültig, sondern verschiebt in einen
> Papierkorb je Workspace. Ein Cron-Job räumt den Papierkorb nach X Tagen auf.
> Reihenfolge: **Zuerst umsetzen** — kleinster Eingriff, entschärft ein reales
> Risiko und ist Voraussetzung dafür, `FileRW.delete` im Staging von
> `Approve` auf `Auto` herunterzustufen (siehe Abschnitt 6).
---
## 1. Ausgangslage
`HandleDeleteAsync` in `src/ClawdDotNet.Tools.FileRW/FileRWTool.cs:318` löscht
sofort und endgültig:
```csharp
if (File.Exists(path)) { File.Delete(path); ... }
else if (Directory.Exists(path)) { Directory.Delete(path, true); ... }
```
Ein einziger Tool-Call mit `path: "."` räumt damit den kompletten Workspace
eines Agenten ab — ohne Rückweg. Gleichzeitig fehlt dem Agenten jede Möglichkeit,
gefahrlos aufzuräumen: Jede Aufräumaktion ist irreversibel.
Vorhandene Schutzmechanismen, die erhalten bleiben müssen:
| Mechanismus | Ort | Verhalten |
|---|---|---|
| Workspace-Einsperrung | `WorkspacePath.Resolve` | absolute Pfade, `..`, ADS (`:`) verboten |
| Zugriffsstufen | `IsActionAllowed` | `delete` im Shared nur bei `sharedAccessLevel = Admin` |
| Geschützte Pfade | `IsPathProtected` | `protectedPaths` sind append-only, kein Löschen |
| Datei-Locks | `GetFileLock` | pro Pfad ein `SemaphoreSlim` |
---
## 2. Zielverhalten
```
delete → verschiebt nach .trash/<yyyyMMdd_HHmmss>/<originalpfad>
list_trash → listet Papierkorb-Einträge mit Originalpfad und Löschzeitpunkt
restore → holt einen Eintrag an seinen Originalpfad zurück
purge → löscht endgültig (nur Admin, ausdrücklicher Opt-in)
```
Papierkorb-Layout (im jeweiligen Workspace-Root):
```
.trash/
├── 20260805_142233/
│ ├── _meta.json ← Originalpfad, Zeitpunkt, Agent, Typ
│ └── notizen.md ← der gelöschte Inhalt (Datei oder Verzeichnis)
└── 20260805_151002/
├── _meta.json
└── alte-recherche/
```
`_meta.json`:
```json
{
"originalPath": "recherche/notizen.md",
"workspace": "personal",
"deletedAt": "2026-08-05T14:22:33Z",
"deletedBy": "senior_developer",
"type": "file"
}
```
Der Zeitstempel ist der Ordnername — damit braucht der Cleanup-Job keine
Metadatei zu lesen, um das Alter zu bestimmen (`_meta.json` ist Komfort,
keine Voraussetzung). Bei Kollision wird `_1`, `_2` … angehängt, analog
`HandleStockAddAsync`.
---
## 3. Sicherheitsanforderungen
Diese Punkte sind **nicht optional** — ohne sie öffnet der Papierkorb neue Lücken:
1. **`.trash` ist für alle direkten Aktionen gesperrt.**
`read`, `write`, `append`, `delete`, `copy` mit einem Pfad in `.trash/`
werden abgelehnt. Sonst wäre der Papierkorb ein Ablageort, über den die
Endungsprüfung (`personalAllowedExtensions`) umgangen werden kann: eine
`.exe` „löschen" und aus dem Papierkorb an beliebiger Stelle wieder
herausholen. Zugriff ausschließlich über `list_trash` / `restore` / `purge`.
2. **`list` blendet `.trash` aus.** Sonst verschmutzt der Papierkorb jede
Verzeichnisauflistung und damit den Kontext des Agenten.
3. **Geschützte Pfade bleiben unantastbar.** Die bestehende Prüfung in
`HandleDeleteAsync` greift **vor** dem Verschieben — ein `protectedPath`
wandert auch nicht in den Papierkorb.
4. **`restore` prüft das Ziel erneut vollständig**: `WorkspacePath.Resolve`
auf den Originalpfad, Endungsprüfung, `IsPathProtected`, `IsActionAllowed`.
Der Originalpfad aus `_meta.json` ist **Eingabedatum, keine Wahrheit**
eine manipulierte Metadatei darf keinen Ausbruch ermöglichen.
5. **Shared Workspace: ein Papierkorb je Agent**
`.trash/<agentId>/<zeitstempel>/`. Sonst sieht und restauriert Agent A die
gelöschten Dateien von Agent B, und `purge` eines Agenten trifft alle.
Im Personal Workspace entfällt die Ebene (dort ist nur ein Agent).
6. **`purge` nur bei `sharedAccessLevel = Admin`** (Shared) bzw. explizit im
Personal Workspace. Der Regelweg zum endgültigen Löschen ist der
Cleanup-Job, nicht der Agent.
7. **Kein Verschieben über Laufwerksgrenzen annehmen.** `Directory.Move`
scheitert, wenn Workspace und `.trash` auf verschiedenen Volumes lägen —
das ist per Konstruktion nicht der Fall (`.trash` liegt im Workspace-Root),
aber der Fehlerfall wird sauber gemeldet statt zu einer Teilkopie zu führen.
---
## 4. Cleanup-Job
`FileRWTool` implementiert zusätzlich `IToolJobProvider`
(`src/ClawdDotNet.Core/Tools/IToolJobProvider.cs`). Die Infrastruktur ist
vollständig vorhanden — `ToolJobScheduler` kennt Cron, `RunOnStart` und
manuelles Auslösen; es braucht **keinen neuen Scheduler**.
```csharp
public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() =>
[
new("filerw_trash_cleanup", "Papierkorb aufräumen",
"Löscht Papierkorb-Einträge, die älter als retentionDays sind")
];
```
`ExecuteJobAsync` bekommt `workspacePath` und `agentId` bereits übergeben
(siehe Signatur des Interfaces). Der Job:
1. liest `retentionDays` aus `toolConfig` (Default **14**, Minimum 1),
2. durchläuft `.trash/*` in Personal- **und** Shared-Workspace,
3. löscht Ordner, deren Zeitstempel älter als die Aufbewahrungsfrist ist,
4. gibt **immer** `ToolJobResult.NoAction(logSummary)` zurück — der Agent wird
nie geweckt. Aufräumen ist kein Ereignis, das ein LLM-Run wert wäre
(und kein Token kosten soll).
Der Shared-Papierkorb wird nur bereinigt, wenn der Job-Agent dort
Admin-Rechte hat; andernfalls nur der eigene Unterordner. Damit räumt nicht
jeder Agent bei jedem Tick fremde Einträge ab.
Beispielkonfiguration im Agenten:
```json
"toolJobs": [
{
"jobId": "trash-daily",
"toolName": "FileRW",
"jobTypeId": "filerw_trash_cleanup",
"cron": "0 3 * * *",
"enabled": true,
"runOnStart": false
}
]
```
**Achtung Zeitzone:** `ToolJobScheduler.RunToolJobAsync` rechnet mit
`DateTime.Now` (lokal), die Papierkorb-Zeitstempel oben sind UTC. Das
Altersvergleich im Job daher konsequent in UTC durchführen
(`DateTime.UtcNow`), nicht mischen. Dieselbe Falle ist bereits aus der
Watchdog-Integration bekannt.
---
## 5. Umsetzungsschritte
### Slice 1 — Papierkorb im Tool (Kern)
- [ ] `TrashPath`-Helfer analog `WorkspacePath`: baut und validiert
`.trash`-Pfade, kapselt die Agent-Ebene im Shared Workspace
- [ ] `HandleDeleteAsync` auf Verschieben umstellen (Datei **und** Verzeichnis)
- [ ] `.trash`-Sperre in `GetAndValidatePath` (greift für alle direkten Aktionen)
- [ ] `.trash` aus `HandleListAsync` filtern
- [ ] `_meta.json` schreiben (über `AtomicFile`, wie beim Stock-Index)
- [ ] Tests: `tests/ClawdDotNet.Tools.Tests/FileRW/TrashTests.cs`
### Slice 2 — list_trash / restore / purge
- [ ] Drei Aktionen im `InputSchema` und im `switch` ergänzen
- [ ] `restore` mit vollständiger Zielprüfung (siehe 3.4)
- [ ] `purge` mit Admin-Prüfung
- [ ] `Description` des Tools ergänzen, damit das LLM den Papierkorb kennt
- [ ] Tests: Restore an geschützten Pfad, Restore mit manipulierter `_meta.json`,
Restore mit inzwischen belegtem Zielpfad
### Slice 3 — Cleanup-Job
- [ ] `IToolJobProvider` an `FileRWTool`
- [ ] `retentionDays` in `FileRWToolSettings`
(`Models/ToolSettingsViewModels.cs:68`) + Designer-Property
- [ ] Job-Definition in der UI auswählbar (läuft über die bestehende
Job-Verwaltung, kein neuer Dialog)
- [ ] Tests mit `FakeTimeProvider`
(`tests/ClawdDotNet.Core.Tests/Infrastructure/FakeTimeProvider.cs`)
### Slice 4 — Dokumentation
- [ ] `docs/ToolDevelopmentGuide.md`: FileRW-Abschnitt (Zeile ~221) auf die
neuen Aktionen aktualisieren
- [ ] Prompt-Hinweis für Agenten: „Löschen ist reversibel, der Papierkorb wird
nach X Tagen geleert" — sonst bleibt der Agent unnötig vorsichtig
---
## 6. Folgeentscheidung: Staging herunterstufen
`StagingPolicy.DefaultRules` (`src/ClawdDotNet.Core/Staging/StagingPolicy.cs`)
führt `FileRW.delete` heute als `Approve` — jede Aufräumaktion braucht eine
menschliche Freigabe. Das ist richtig, **solange Löschen endgültig ist**.
Mit dem Papierkorb ist es das nicht mehr. Empfehlung nach Slice 2:
```csharp
["FileRW.delete"] = StagingDecision.Auto, // reversibel über .trash
["FileRW.purge"] = StagingDecision.Approve // endgültig → Freigabe
```
Damit kann der Agent selbstständig aufräumen (genau der Wunsch aus der
Ideensammlung), ohne dass irreversible Aktionen ungefragt durchgehen.
---
## 7. Abnahmekriterien
- Ein gelöschter Ordner liegt vollständig im Papierkorb und ist per `restore`
wiederherstellbar.
- `read`/`write` auf einen `.trash`-Pfad wird abgelehnt.
- `restore` einer `.exe` in einen Workspace mit `allowedExtensions` ohne `.exe`
wird abgelehnt.
- Ein `protectedPath` lässt sich weiterhin nicht löschen.
- Agent B sieht im Shared-Papierkorb nicht die Einträge von Agent A.
- Nach Ablauf von `retentionDays` ist der Eintrag beim nächsten Job-Tick weg,
ohne dass ein LLM-Run stattgefunden hat.