7.4 KiB
7.4 KiB
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:
destinationis altijd het volledige beoogde eindpad (inclusief bestandsnaam).destinationmag in v1 niet geïnterpreteerd worden als "copy/move into existing directory".- Als
destinational 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 datfile.txtautomatisch eronder komt.
Reden:
- voorkomt ambigu gedrag
- eenvoudiger API-contract
- minder regressierisico
2. Scopevoorstel copy v1
Keuze
copyondersteunt 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
renameblijft naamwijziging binnen dezelfde parent directory.moveis 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
moveondersteunt 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 resolve’t naar pad buiten whitelist -> geblokkeerd (
path_outside_whitelist). - als source symlink resolve’t binnen whitelist: in v1 alsnog afwijzen als
type_conflictom semantiek simpel te houden.
Destination
- destination wordt via
path_guardcanoniek 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
tasksenhistoryaparte modellen/tabelrollen:tasks: actuele en afgeronde task-status/progresshistory: audit-log van uitgevoerde operaties
- history kan vanuit task-completion gevuld worden, maar is niet hetzelfde model.
Retentie
- v1 bewaart
completedenfailedtasks persistent (geen automatische cleanup in scope).
Sortering GET /api/tasks
- standaard sortering:
created_at DESC(nieuwste eerst).
6. Taskmodel
Wanneer task verplicht is
copyenmovealtijd task-based.
Statussen
queuedrunningcompletedfailed
Progress
- file-only v1:
done_bytestotal_bytes
done_items/total_itemsoptioneel en kunnennullblijven 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/copyPOST /api/files/moveGET /api/tasks/{task_id}GET /api/tasks
Request/response shapes
POST /api/files/copy
Request:
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_copy.txt"
}
Response (202):
{
"task_id": "<uuid>",
"status": "queued"
}
POST /api/files/move
Request:
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_moved.txt"
}
Response (202):
{
"task_id": "<uuid>",
"status": "queued"
}
GET /api/tasks/{task_id}
Response (200):
{
"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):
{
"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/copysuccess (task create shape)POST /api/files/movesuccess (task create shape)already_existsvoor copy/movetype_conflictals source geen file isinvalid_requestvoor ongeldige payload- traversal/root errors voor source en destination
GET /api/tasks/{task_id}shapes voorqueued/running/completed/failedGET /api/taskslist shape inclusiefsourceendestination
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_ids (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.