Issue Description
Moving a calendar event from calendar A to calendar B through CalendarEvent/set does not record the event’s previous CalDAV href as removed.
A subsequent DAV:sync-collection reports the new B href but omits the old A href. Incremental CalDAV clients therefore have no indication that the old resource should be removed and may retain a stale copy of the event.
Reproducer: https://gist.github.com/ripdog/0af690d8ccc32a63f3716587deeeb865
The reproducer will directly call JMAP and CalDAV APIs to demonstrate the bug. I’m not sure if it’s useful, but if you’re interested, reading repro_move_tombstone.py is the most worthwhile part, probably.
Expected Behavior
After moving an event from A to B, synchronization from a token obtained before the move should report:
- the old A href with
404 Not Found; - the new B href as new or changed.
If the event is moved to B and then deleted before the next sync, both the removed A href and the added-then-removed B href should be reported with 404 Not Found.
RFC 6578 §3.2 requires removed member URLs to be returned with 404 Not Found. §3.5.2 requires removed mappings to be reported and explicitly says that a member added and removed between synchronization requests must still be reported as removed. ([RFC Editor][1])
Actual Behavior
After A → B:
Move-only sync changes:
live https://mail.example.com/dav/cal/.../calendar-b/...ics
The old A href is absent.
After A → B → delete:
Move-then-delete sync changes:
404 https://mail.example.com/dav/cal/.../calendar-b/...ics
The B deletion is reported, but the historical A href is again absent.
Reproduction Steps
-
Create calendars A and B through JMAP.
-
Create event E in calendar A:
"calendarIds": {
"A_ID": true
}
- Perform a CalDAV
DAV:sync-collectionREPORT against the calendar home and save the returned sync token. At this point E has a CalDAV resource href under A, for example:
/dav/cal/user/calendar-a/event.ics
- Move E from A to B through JMAP
CalendarEvent/set:
{
"update": {
"EVENT_ID": {
"calendarIds/A_ID": false,
"calendarIds/B_ID": true
}
}
}
The operation succeeds. E is no longer present under A and now has a CalDAV href under B.
- Perform another
DAV:sync-collectionusing the token saved before the move.
Expected delta:
404 /dav/cal/user/calendar-a/event.ics
200 /dav/cal/user/calendar-b/event.ics
The exact successful status representation may vary depending on the requested properties, but the removed A href must be present with 404 Not Found.
Actual delta:
200 /dav/cal/user/calendar-b/event.ics
The old A href is omitted completely.
- The omission is also visible if E is deleted from B before the second DAV sync. Starting again with a token obtained while E is in A:
A --JMAP move--> B --JMAP destroy--> deleted
A subsequent sync-collection reports:
404 /dav/cal/user/calendar-b/event.ics
but still does not report:
404 /dav/cal/user/calendar-a/event.ics
Thus the JMAP destroy correctly records the current B href as vanished, while the earlier removal of the A mapping through calendarIds does not.
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on