You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
31
31
32
32
### Fixed
33
33
34
+
- Fixed the pimdir backend accepting a collection of any kind as an addressbook. A sync engine caches mail, calendars and contacts in one store, and only the listing narrowed them to `text/vcard`, so `card list -k imap/INBOX` printed a mailbox's messages as blank contacts and a create would have staged a vCard into it. An addressbook id now names an addressbook or nothing, and a wrong one is refused naming the addressbooks the account holds.
34
35
- Fixed a card vanishing from the pimdir backend when another card of the same addressbook carried its `UID`. RFC 6352 requires that `UID` to be unique and servers hand over duplicates anyway, most often after a repeated import; the store keys the second copy apart now, so both cards list, read and act as ordinary cards under their own ids.
35
36
- Fixed `card list` leaving the `TEL` column empty for any card writing its phone above its mail. The preview chained its three property reads, so a line that was not an `EMAIL` never reached the `TEL` read. Every backend was affected.
36
37
- Fixed the pimdir backend linking a card it stages under `uid:<UID>` where the sync engine uses the bare `UID`, which would have stored the card twice and synced it as a duplicate contact. The derivations now come from io-pimdir's own conventions.
### Requirement: A pimdir addressbook is its collection id
9
+
The pimdir backend SHALL show and accept an address book as the store's collection id, verbatim: the collection `carddav/default` is the address book `carddav/default`. It SHALL NOT derive, strip or accept a shortened spelling, and no configuration SHALL offer one.
10
+
11
+
A sync engine binds a source's collections under a namespace, so an id carries one; the store is opaque to it, neither parsing nor validating an id (pimdir SPEC 9.2) and modelling hierarchy through `parent` rather than through a separator. Shortening is therefore a guess at the producer's convention, and one that makes a single address book answer to two spellings.
12
+
13
+
An id SHALL name an address book or name nothing: one store holds the collections of every kind a sync caches, so the kind SHALL narrow them at the one seam both the listing and the id check read, never at the listing alone. A kind-less collection counts, a sync having created one before kinds were declared.
14
+
15
+
An id naming no address book of the account SHALL be refused naming the ones it holds. Ids carry the sync engine's namespace and are not guessable, so an error asking for one that shows none leaves the user nothing to act on.
16
+
17
+
#### Scenario: A mailbox is not an address book
18
+
- GIVEN a store a sync fills with mail, calendar and contact collections alike
19
+
- WHEN a card command addresses a `message/rfc822` collection
20
+
- THEN it is refused naming the address books the account holds, rather than listing the mailbox's messages as blank contacts
The sync engine binds a source's collections under a namespace, so an address book it caches is keyed `carddav/default` rather than `default`. Cardamum already addresses it by that id and shows it whole, which is the right shape: a collection id is opaque to the store, which neither parses nor validates it (pimdir SPEC 9.2) and models hierarchy through `parent` rather than through a separator, so shortening one would be a guess at the producer's convention rather than a lookup.
13
+
14
+
Himalaya learnt this the long way, having shipped a derived short name and a `pimdir.namespace` key to rescue the cases the derivation could not decide, and has just removed both. Cardamum never grew either, so the alignment is to say so before someone adds one, and to make the rule usable.
15
+
16
+
Usable is where two gaps showed. An id that names no collection is refused, but the refusal does not say what the store does hold, and an id carrying a namespace nobody can guess is one the user has to be shown. Worse, `known_collection` read the store's collections unfiltered while the listing filtered them by kind, so an id naming a collection of another kind passed the check: against a store Neverest fills with mail, calendars and contacts alike, `card list -k imap/INBOX` listed a mailbox's messages as blank contacts, and a create would have staged a vCard into it.
17
+
18
+
## What
19
+
20
+
The requirement is stated: a pimdir addressbook is its collection id, verbatim, with no derived spelling and no configuration offering one.
21
+
22
+
The kind filter moves into `collections()`, the one seam both the listing and the check read, so an id names an address book or it names nothing. The refusal then names the address books the account holds, so the id to type is in the error that asks for it.
Neverest binds a source's collections under a namespace, so the address book it caches is keyed `carddav/default`. Cardamum already addressed it by that whole id, which is the right shape and was never written down: a collection id is opaque to the store, which neither parses nor validates it (pimdir SPEC 9.2) and models hierarchy through `parent` rather than through a separator, so shortening one is a guess at the producer's convention rather than a lookup. Himalaya had shipped such a guess, with a `pimdir.namespace` key to rescue the stores it could not decide, and removed both the same day. Stating the rule here is what stops it being added.
10
+
11
+
Writing it down found the two things that made the rule hard to live with.
12
+
13
+
## What landed
14
+
15
+
**The kind narrows at one seam, in src/pimdir/backend.rs.**`collections()` returned the account's collections of every kind, and only `list_addressbooks` filtered them down to `text/vcard`. `known_collection` read the same unfiltered list, so an id naming a collection of another kind passed the check. Against a store Neverest fills with mail, calendars and contacts alike, `card list -k imap/INBOX` listed a mailbox's messages as blank contacts, and a create would have staged a vCard into it. The filter moved into `collections()`, which both callers read, so an id names an address book or it names nothing.
16
+
17
+
**The refusal names what the account holds.**`Addressbook \`default\` not found` became `Addressbook \`default\` not found; this account holds: carddav/default`. An id carrying a namespace is not guessable, so an error asking for one has to show the choices, and the same message now answers a wrong-kind id and a mistyped one.
18
+
19
+
## Capabilities moved
20
+
21
+
- backends: added *A pimdir addressbook is its collection id*
22
+
23
+
## Verification
24
+
25
+
Built clean and run against the live Neverest store at `~/.local/state/neverest/posteo`, which holds eighteen collections across three kinds under one account. Before the fix, `card list -k imap/INBOX` printed sixteen blank contacts; after it, that id and `default` are both refused naming `carddav/default`, `card list -k carddav/default` lists the contacts, and `addressbook list` is unchanged.
Copy file name to clipboardExpand all lines: cairn/spec/backends.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,6 +34,15 @@ vdir and pimdir SHALL adapt io-vdir and io-pimdir. vdir stores each addressbook
34
34
### Requirement: A cache renames nothing it cannot push
35
35
The pimdir backend SHALL refuse every `addressbook update` field. Its collection row (id, display name, description, colour) is written by the sync from the server, and the backend stages item actions only, so any local edit of it is a change no sync would carry. A missing body SHALL fall back to the summary preview in a listing, as an unhydrated card does, rather than render as a blank row.
36
36
37
+
### Requirement: A pimdir addressbook is its collection id
38
+
The pimdir backend SHALL show and accept an address book as the store's collection id, verbatim: the collection `carddav/default` is the address book `carddav/default`. It SHALL NOT derive, strip or accept a shortened spelling, and no configuration SHALL offer one.
39
+
40
+
A sync engine binds a source's collections under a namespace, so an id carries one; the store is opaque to it, neither parsing nor validating an id (pimdir SPEC 9.2) and modelling hierarchy through `parent` rather than through a separator. Shortening is therefore a guess at the producer's convention, and one that makes a single address book answer to two spellings.
41
+
42
+
An id SHALL name an address book or name nothing: one store holds the collections of every kind a sync caches, so the kind SHALL narrow them at the one seam both the listing and the id check read, never at the listing alone. A kind-less collection counts, a sync having created one before kinds were declared.
43
+
44
+
An id naming no address book of the account SHALL be refused naming the ones it holds. Ids carry the sync engine's namespace and are not guessable, so an error asking for one that shows none leaves the user nothing to act on.
45
+
37
46
### Requirement: pimdir is a cache, not a server
38
47
The pimdir backend SHALL treat the store as a possibly-partial cache. `get_card` on a card whose body is not local (`level < Full`, no stored object) SHALL report a clear "body not fetched" state, the cue to sync, rather than a data-loss error. The card still lists: `list_cards` SHALL project the stored `v: 1` summary into a minimal preview vCard (`UID`, `FN`, `EMAIL`) so a contact list reads correctly before a full sync, while `get_card` refuses outright so a preview can never be mistaken for the document of record.
0 commit comments