Pair by collection and UID with the address as a cache, never rewrite a remote UID, and re-pair a re-added connection instead of duplicating it #7
Labels
No labels
bug
documentation
enhancement
performance
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-dav#7
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The suggestions for contacts, events and tasks all say that an item is "paired" and its data synced "using its URI". Today the plugin pairs cards with contacts and events with interviews through the core's
SyncLink, and knows a twin by its address (remote_href) alone. This issue settles the key for #8, #11 and #12 alike.Reading. "Its URI" is read here as the item's address, its href. Two other readings are possible: the item's
UID, which in vCard 4 is itself a URI by default (RFC 6350 §6.7.6), or a link back to Postulo written into the item. The choice is the first decision below. The standards:UIDtravels with the content and is unique within its collection (RFC 6352 §5.1, RFC 4791 §4.1). A server must refuse aPUTthat changes an item'sUIDor reuses one already there (no-uid-conflict, RFC 6352 §6.3.2.1, RFC 4791 §5.3.2.1), with a 403 or a 409 (RFC 4791 §1.3).What is wrong today
Read from the code; none of it has been reproduced on a real server.
link.uidis set (src/postulo_dav/sync.py:451,545,582), but a twin is found only by its href (:458,:589). A card whose address changes has its link flaggedremote_goneand, in the same run, is adopted again as a second contact (:527-550). The plugin never clearsremote_gone.card_for()always writesUID:contact-{pk}-{owner}@postulo(:136), so the first push of a card adopted from the phone (:503, after an edit here) replaces its UID. A conforming server refuses, which is not the 412 caught at:509, so every run from then on fails before the interviews. A lenient server lets the identity change. Which servers do which is unverified.on_delete=CASCADEin the core). The next one pushes every contact as a new card, under the UID the old one gave it, and adopts every card already there as a new contact (:437-455,:527-550).:442), and the run fails; contacts the old connection had adopted may already have a second card by then.<interview.uid>.ics(If-None-Match: *,:576-577) that nothing catches.tests/test_dav.pydoes not enforceno-uid-conflict, and no test re-adds a connection.What the plugin should do
UID. The last href and ETag are a cache.UID: every push writes the one stored on the link. An event made elsewhere keeps its own through #11.UIDin the chosen collections of that kind, never the whole home: among the unpaired items the run reads anyway, or withaddressbook-queryandmatch-type="equals"(RFC 6352 §8.6, §10.5.4) orcalendar-query(RFC 4791 §7.8), comparing exactly, since CalDAV'stext-matchmatches substrings (§9.7.5). If found, the link follows it,remote_goneis cleared, and a note says so.UID; Postulo's own are recognisable (contact-{pk}-{owner}@postulo, an interview'suid). A match is paired, and today's conflict rule picks the winner (:472,:611). A pair the person undid by hand is not matched again.SyncPlugincontract asks (src/postulo/plugins/base.pyin the core).UID, andDAV:resource-id(RFC 5842) is optional, so #5 keys them by path. Choosing collections by href is #13.Decisions
UID, the href a cache; (c) a link back to Postulo written into the item. Recommended: (b). Reads and writes still go to the href, but it may change and the UID may not. (c) writes into the person's own cards and events, which their clients may drop (unverified).pkcan name a different contact. Recommended: pair on it only when the name matches too; a miss only makes a duplicate.Needs from the core
for_remote, anunbindthat stays undone, and several twins per record for #12.Points 2 to 5 work before either lands, on the
uidthe link already stores and the one configured collection.New notes go through
uv run postulo-messages extract; fr-fr and pt-pt are filled on the commit (the test gates them), the 23 EU catalogues before a tag (uv run pytest -m release). New surface names mean repinning the core (pyproject.tomldev group pins1d6cdd7e0) and extendingtests/test_surface.py.Done when
PUTthat changes or reuses a UID, and each bug above has a test.From the list of DAV plugin improvements of 2026-09-30.