Files
webmanager-mvp/project_docs/COPY_MOVE_TASKS_DESIGN.md
T
2026-03-11 09:39:41 +01:00

275 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# COPY_MOVE_TASKS_DESIGN.md
## Doel
Ontwerpvoorstel voor de volgende implementatieslice: `copy`, `move` en `tasks`.
Dit document bevat alleen ontwerpkeuzes. Er is geen code-implementatie in deze stap.
## 1. Destination-semantiek (expliciet)
V1 kiest een strikte semantiek:
- `destination` is altijd het volledige beoogde eindpad (inclusief bestandsnaam).
- `destination` mag in v1 **niet** geïnterpreteerd worden als "copy/move into existing directory".
- Als `destination` al bestaat (file of directory) -> `already_exists` (409).
Voorbeeld v1:
- source: `storage1/a/file.txt`
- destination: `storage2/b/file_copy.txt`
Niet toegestaan als impliciete directory-target in v1:
- destination: `storage2/b/` met verwachting dat `file.txt` automatisch eronder komt.
Reden:
- voorkomt ambigu gedrag
- eenvoudiger API-contract
- minder regressierisico
## 2. Scopevoorstel `copy` v1
### Keuze
- `copy` ondersteunt in v1 **alleen files**.
- directory-recursie schuift door naar een latere fase.
### Motivatie
- Complexiteit: recursieve copy voegt veel edge-cases toe (diepe bomen, symlinks, partial failures).
- Testbaarheid: file-only maakt golden/regressie tests kleiner en deterministischer.
- Regressierisico: lagere kans op onverwachte performance/security regressies.
### Conflictgedrag
- destination bestaat al -> `already_exists` (409).
- geen overwrite/merge in v1.
### Uitvoering
- altijd task-based (async).
- create response: `task_id` + `queued`.
### Foutmodel copy
- `invalid_request` (400)
- `path_traversal_detected` (403)
- `path_outside_whitelist` (403)
- `invalid_root_alias` (403)
- `path_not_found` (404)
- `type_conflict` (409) (source is geen file)
- `already_exists` (409)
- `io_error` (500)
## 3. Scopevoorstel `move` v1
### Rename vs move
- `rename` blijft naamwijziging binnen dezelfde parent directory.
- `move` is padwijziging naar een ander eindpad (zelfde root of cross-root).
### Cross-root gedrag
- toegestaan als source en destination beide binnen whitelist vallen.
- zelfde root: native rename/move waar mogelijk.
- cross-root: copy+delete binnen dezelfde task.
### Keuze
- `move` ondersteunt in v1 **alleen files**.
- directory-move buiten scope in v1.
### Conflictgedrag
- destination bestaat al -> `already_exists` (409).
- geen overwrite in v1.
### Foutmodel move
- `invalid_request` (400)
- `path_traversal_detected` (403)
- `path_outside_whitelist` (403)
- `invalid_root_alias` (403)
- `path_not_found` (404)
- `type_conflict` (409) (source is geen file)
- `already_exists` (409)
- `io_error` (500)
## 4. Symlinkbeleid (copy/move)
### Source
- v1 file-only: source mag geen symlink zijn.
- als source symlink resolvet naar pad buiten whitelist -> geblokkeerd (`path_outside_whitelist`).
- als source symlink resolvet binnen whitelist: in v1 alsnog afwijzen als `type_conflict` om semantiek simpel te houden.
### Destination
- destination wordt via `path_guard` canoniek gevalideerd.
- destination parent moet binnen whitelist liggen.
- destination parent die via symlink buiten whitelist valt -> blokkeren (`path_outside_whitelist`).
### Recursieve escapes
- Niet van toepassing in v1 (geen directory-recursie).
- Voor latere directory-copy geldt: elk bezocht pad moet per entry containment-check krijgen.
## 5. Task persistence en history
### Relatie tasks vs history
- In v1 zijn `tasks` en `history` aparte modellen/tabelrollen:
- `tasks`: actuele en afgeronde task-status/progress
- `history`: audit-log van uitgevoerde operaties
- history kan vanuit task-completion gevuld worden, maar is niet hetzelfde model.
### Retentie
- v1 bewaart `completed` en `failed` tasks persistent (geen automatische cleanup in scope).
### Sortering GET /api/tasks
- standaard sortering: `created_at DESC` (nieuwste eerst).
## 6. Taskmodel
### Wanneer task verplicht is
- `copy` en `move` altijd task-based.
### Statussen
- `queued`
- `running`
- `completed`
- `failed`
### Progress
- file-only v1:
- `done_bytes`
- `total_bytes`
- `done_items`/`total_items` optioneel en kunnen `null` blijven in v1.
### Partial failure
- fail-fast.
- eerste fatale fout -> `failed`.
- geen rollback in v1.
- taskdetail bevat `failed_item`, `error_code`, `error_message`.
### Duplicate create requests (correctie)
- v1 gedrag is **niet idempotent**.
- twee identieke requests mogen twee verschillende tasks creëren.
## 7. API-voorstel
### Endpoints
- `POST /api/files/copy`
- `POST /api/files/move`
- `GET /api/tasks/{task_id}`
- `GET /api/tasks`
### Request/response shapes
#### POST /api/files/copy
Request:
```json
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_copy.txt"
}
```
Response (202):
```json
{
"task_id": "<uuid>",
"status": "queued"
}
```
#### POST /api/files/move
Request:
```json
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_moved.txt"
}
```
Response (202):
```json
{
"task_id": "<uuid>",
"status": "queued"
}
```
#### GET /api/tasks/{task_id}
Response (200):
```json
{
"id": "<uuid>",
"operation": "copy",
"status": "running",
"source": "storage1/a/file.txt",
"destination": "storage2/b/file.txt",
"done_bytes": 1024,
"total_bytes": 4096,
"done_items": null,
"total_items": null,
"current_item": "storage1/a/file.txt",
"failed_item": null,
"error_code": null,
"error_message": null,
"created_at": "2026-03-10T00:00:00Z",
"started_at": "2026-03-10T00:00:01Z",
"finished_at": null
}
```
#### GET /api/tasks
Response (200):
```json
{
"items": [
{
"id": "<uuid>",
"operation": "move",
"status": "completed",
"source": "storage1/a/file.txt",
"destination": "storage2/b/file.txt",
"created_at": "2026-03-10T00:00:00Z",
"finished_at": "2026-03-10T00:00:05Z"
}
]
}
```
### Waarom `source` en `destination` ook in task-list
- Ja, opnemen in `GET /api/tasks`.
- Reden: operator kan zonder extra detail-calls direct zien wat elke task doet.
- Dit maakt UI en troubleshooting eenvoudiger.
### Error codes (versmald)
- `invalid_request` (400)
- `path_traversal_detected` (403)
- `path_outside_whitelist` (403)
- `invalid_root_alias` (403)
- `path_not_found` (404)
- `task_not_found` (404)
- `type_conflict` (409)
- `already_exists` (409)
- `io_error` (500)
`internal_error` wordt in v1 niet als aparte publieke code gebruikt; onverwachte fouten worden genormaliseerd naar `io_error` of framework-500.
## 8. Teststrategie
### Golden tests
- `POST /api/files/copy` success (task create shape)
- `POST /api/files/move` success (task create shape)
- `already_exists` voor copy/move
- `type_conflict` als source geen file is
- `invalid_request` voor ongeldige payload
- traversal/root errors voor source en destination
- `GET /api/tasks/{task_id}` shapes voor `queued/running/completed/failed`
- `GET /api/tasks` list shape inclusief `source` en `destination`
### Regressietests
- cross-root move gebruikt copy+delete pad correct
- file copy/move met unicode namen
- grote files progress updates blijven monotone stijging
- duplicate create requests leveren 2 verschillende `task_id`s (niet-idempotent)
### Securitytests
- traversal blokkade op source/destination
- destination outside whitelist blokkade
- invalid root alias blokkade
- symlink source wordt afgewezen
- symlinked destination parent buiten whitelist wordt afgewezen
## Implementatiegrenzen voor volgende stap
- Geen wijziging aan bestaande endpoints:
- browse
- mkdir
- rename
- delete
- Geen nieuwe dependencies zonder expliciete motivatie.