Give SyncLink the twin's collection and a slot for several twins per record, and add unbind and a lookup by UID #326
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#326
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?
SyncLink(src/postulo/plugins/models.py) ties a local record to its twin on a sync connection. Besides its owner, it holds:content_typeplus a requiredobject_id;remote_href(500),uid(200),etag,local_hash,last_synced_atandremote_gone.It is unique on (connection, record) (
synclink_one_per_record).remote_hrefis indexed with the connection but not unique, and there is no collection field. The API isfor_record,of_modelandbind, anupdate_or_createwith the owner taken from the connection; Writing a plugin calls these three "the whole API". Plugins setremote_gone, and nothing documents clearing it. Pairings are in neither the personal export nor the import (src/postulo/core/export.py,src/postulo/core/importer.py).The DAV plugin's improvements need three things this cannot say: which collection a twin lives in, that one record has several twins, and that a pairing was undone. A plugin cannot keep a link table of its own, because a plugin cannot own tables (#228), so they belong here.
Proposal
collection: the address of the collection the twin lives in (an address book, a calendar, a task list), blank where a plugin has none.slot: a short key the plugin chooses, naming which of a record's twins this is. Blank means the only one, which is every row today.for_recordandbindtakeslot="";of_modelreturns every slot.slotrather thanrole, because a contact'sroleis already its job title.bindrefuses such a binding with an exception naming the record already paired, not a bareIntegrityError.unbind(connection, record, slot="")undoes a pairing and leaves the record and the remote item alone. What it leaves behind is a decision below.for_remote(connection, collection, uid)finds the link from the remote side.bindclearsremote_goneunless the caller passes it: a record bound again is paired again. The DAV plugin binds only records that have no link, so nothing changes for it.remote_goneis the plugin's job.SyncLinkAdminlists the collection and the slot.Decisions
What
unbindleaves behind. A plugin that pairs by UID (postulo/postulo-dav#7) would pair the two again on its next run, and today's DAV plugin would push the record as a new item and adopt the item as a new record.Recommended: (b).
Pairings in the personal export. Connections are not exported, nor their secrets, and a pairing without its connection points to a server the importing instance has no credentials for. Recommended: leave pairings out and let a re-added connection re-pair by UID (postulo/postulo-dav#7). The archive format stays at 25.
Unique or only indexed on (connection, collection, uid). Pairing one card with a contact and a department at once would write one card into two records. Recommended: unique, with #327 saying which record an item is already paired with.
The slot vocabulary. Free text per plugin, or a list kept by the core. Recommended: free text, because one plugin's slots mean nothing to another.
Needed by
for_remote,bindclearingremote_gone, and an unpairing that stays.unbind.unbind.unbindandfor_remote.Done when
tests/test_syncs.pycovers two slots on one record, one UID in two collections, a second record refused for one item,unbindand what it leaves,bindclearingremote_gone, andfor_remote.slot,unbindandfor_remoteinstead of "the whole API", with the rule to key a twin on its collection and UID and keep the address as a cache (RFC 4791 §5.3.2).SyncLinkis already onpostulo.plugins.api, sotests/test_wiki_surface.pyandtests/test_plugin_surface.pyhave no new name to find. The surface promise covers the additions all the same; they are additive, so no deprecation is needed.From the list of DAV plugin improvements of 2026-09-30.