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>
236 lines
9.1 KiB
Markdown
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.
|