Implement destructive CalDAV move saga

This commit is contained in:
2026-08-02 17:30:24 +02:00
parent 56d7b2108d
commit a125b666da
16 changed files with 2384 additions and 36 deletions
+22 -6
View File
@@ -159,12 +159,28 @@ Collection deletion accepts two explicit, non-destructive external actions for
rolling back or hiding the local move.
Local-to-local moves retain their existing behavior and do not accept an
`external_action`. Implicit or mismatched actions, inbound-only destinations,
and synchronized-to-synchronized moves are rejected. `remote_move` is a known
but deliberately unsupported action: implementing it requires a separately
approved saga for destination reconciliation, conditional source deletion,
collision handling, and concurrent-edit policy. The WebUI does not offer that
destructive mode.
`external_action`. Implicit or mismatched actions and inbound-only destinations
are rejected. `remote_move` applies only between active, enabled, two-way
CalDAV sources. It is an administrator-authorized durable migration saga:
- the request requires the exact `MOVE REMOTE EVENTS` confirmation and a
retained authorization-evidence note;
- all destination resources must complete or reconcile their conditional PUTs
before any source DELETE can be leased;
- every source DELETE uses the ETag captured when the batch started, so a
concurrent remote edit becomes an explicit conflict rather than data loss;
- UIDs are preserved and target UID collisions stop the batch before mutation;
- both calendars, their sources, and moved events reject ordinary edits and
synchronization while the batch is active;
- progress, resource states, conflicts, authorization evidence, and actor
provenance remain queryable; and
- cancellation is available only before the first source DELETE attempt. It
finishes safe destination copies, retains the source resources, and restores
source synchronization. Once deletion starts, the batch must be reconciled.
The source collection is retired only after every source resource is confirmed
absent. A crash after a destination write is reconciled by semantic ICS content
before retry, preserving the outbox's no-blind-repeat guarantee.
Resolved terminal rows (`succeeded`, `superseded`, and `cancelled`) are removed
in bounded batches after `CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS` (90 days by