Files
ClawdDotNet/docs/umsetzungsplaene/UMSETZUNGSPLAN-FileRW-Papierkorb-Cleanup.md
T
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

9.1 KiB

Umsetzungsplan: FileRW-Papierkorb + Cleanup-Job

Stand 2026-08-23: beschlossen, noch nicht gebaut. Eingeordnet als Punkt 3 der Roadmap — 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:

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:

{
  "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.

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:

"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:

["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.