Pair any card in the chosen address books with a contact, a company, a department or the account holder, and keep both in step #8

Open
opened 2026-09-30 20:30:02 +00:00 by tiagoagueda · 2 comments
Owner

The plugin pairs cards with contacts only, and nobody chooses: every contact is pushed to the one configured address book, and every unlinked named card there is adopted (src/postulo_dav/sync.py:425-457, :527-550). The ask:

Every contact in the remote CardDAV server can be "paired" and data synced, using its URI, to the user, to a person, to a department, or to a company.

Read here as: any card in the chosen address books (#13) can be paired, by choice, with a contact ("a person"), a company, a department or the account holder ("the user"), and kept in step (decisions 1 and 2).

What the plugin should do

Targets:

  • A contact: today's eight properties (FN, N, ORG, TITLE, EMAIL, primary TEL, URL, NOTE), plus the ORG unit as the department (RFC 6350 §6.6.4), ADR as postal addresses, and numbers and links beyond the primary.
  • A company: a KIND:org card (vCard 4, RFC 6350 §6.1.4) or one marked X-ABShowAs:COMPANY (vCard 3, Apple's; not tried here). Name, website and notes travel; TEL, EMAIL and ADR wait for postulo/postulo#329. Nextcloud's Contacts app has no KIND support (nextcloud/contacts#302); a company card cannot list its people (MEMBER is for KIND:group only, §6.6.5).
  • A department: vCard has no such property and a department here is only a name, so Postulo writes FN and ORG:Company;Unit only.
  • The account holder: the card CARDDAV:principal-address names (RFC 6352 §7.1.2) where the server gives one (unverified anywhere), else one the person picks. Postulo's side: contact_details(owner) in the core's src/postulo/documents/rendering.py.

Rules:

  • Patch, never rebuild, a card the person made elsewhere: change only the properties Postulo owns; keep the rest, KIND and VERSION included.
  • Deleting here unpairs such a card and leaves it (decision 3).
  • Adopt new cards only in a collection marked as Postulo's own (#13).
  • Follow the book's vCard version for cards Postulo writes: read supported-address-data; vCard 3 where 4.0 is not listed (RFC 6352 §6.2.2).

What is wrong today

  • A push replaces the whole card with those eight properties (src/postulo_dav/text.py:155-174, sync.py:498-508): an adopted phone card, once edited here, loses its address, photo, birthday, other numbers and emails, and X- properties, if the server accepts a push that also changes its UID (#7).
  • Every card goes out as VERSION:4.0, and supported-address-data is never read (text.py:156); a book not listing vCard 4.0 must refuse it. Untried on iCloud.
  • Only the first ORG component is read and only the company written (text.py:160-161, :190-191), so departments are lost both ways. KIND is never read: a KIND:org card becomes a contact named after the company.
  • A pull that changes the ORG company leaves the contact in its old company's department (sync.py:152-154), a pair Contact.clean refuses (src/postulo/jobs/models.py:649-655 in the core).

Decisions

  1. The words. "using its URI": the card's identity as #7 keys it, not its href, which changes when a card moves (RFC 6578 §3.5). "the user": the account holder, not other accounts, which a per-person connection cannot reach. "Every contact": any card may be paired, none by default (postulo/postulo#16 kept whole personal books out). Recommended: these readings.
  2. Writing back to the account holder. Two-way lets a phone change the sign-in email (unique), a verified or recovery number, or add a number another account holds. Recommended: Postulo → card only; failing that, fill blanks only (the career-file importer's rule, src/postulo/resume/importing.py), never those.
  3. Deleting a record whose card Postulo created or keeps in its own collection. Unpaired there, it would be adopted again next run. Recommended: delete the card, If-Match guarded as today; unpair any other.
  4. One record per card. SyncLink lets two records share a card. Recommended: one; a contact's company and department follow from its ORG.
  5. NOTE on a card made elsewhere. Postulo's notes are private and gather conflict records (sync.py:472-484); a pushed NOTE exposes them, a pulled one replaces them (sync.py:151). Recommended: leave NOTE out unless the person switches it on.
  6. Departments while employer_structure is off (hidden, not deleted). Recommended: keep the pairing, sync nothing.

Needs from the core

Without them only contacts are paired, by adoption; the rules above can land first. Depends on #7 and #13.

Done when

  • A card from a personal book, paired with a contact and changed here, keeps its UID and every property Postulo does not own; deleting the contact leaves it.
  • A company card paired with a company keeps name, website and notes in step; ORG:Company;Unit sets the contact's department (with employer_structure on).
  • A paired account-holder card never changes the sign-in email or a number here.
  • A book whose supported-address-data lacks vCard 4 gets vCard 3.
  • New strings go through uv run postulo-messages extract, fr-fr and pt-pt 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.toml's dev group pins 1d6cdd7e0) and extending tests/test_surface.py.

From the list of DAV plugin improvements of 2026-09-30.

The plugin pairs cards with contacts only, and nobody chooses: every contact is pushed to the one configured address book, and every unlinked named card there is adopted (`src/postulo_dav/sync.py:425-457`, `:527-550`). The ask: > Every contact in the remote CardDAV server can be "paired" and data synced, using its URI, to the user, to a person, to a department, or to a company. Read here as: any card in the chosen address books (#13) can be paired, by choice, with a contact ("a person"), a company, a department or the account holder ("the user"), and kept in step (decisions 1 and 2). ## What the plugin should do **Targets:** - **A contact:** today's eight properties (`FN`, `N`, `ORG`, `TITLE`, `EMAIL`, primary `TEL`, `URL`, `NOTE`), plus the `ORG` unit as the department (RFC 6350 §6.6.4), `ADR` as postal addresses, and numbers and links beyond the primary. - **A company:** a `KIND:org` card (vCard 4, RFC 6350 §6.1.4) or one marked `X-ABShowAs:COMPANY` (vCard 3, Apple's; not tried here). Name, website and notes travel; `TEL`, `EMAIL` and `ADR` wait for postulo/postulo#329. Nextcloud's Contacts app has no `KIND` support (nextcloud/contacts#302); a company card cannot list its people (`MEMBER` is for `KIND:group` only, §6.6.5). - **A department:** vCard has no such property and a department here is only a name, so Postulo writes `FN` and `ORG:Company;Unit` only. - **The account holder:** the card `CARDDAV:principal-address` names (RFC 6352 §7.1.2) where the server gives one (unverified anywhere), else one the person picks. Postulo's side: `contact_details(owner)` in the core's `src/postulo/documents/rendering.py`. **Rules:** - **Patch, never rebuild,** a card the person made elsewhere: change only the properties Postulo owns; keep the rest, `KIND` and `VERSION` included. - **Deleting here unpairs** such a card and leaves it (decision 3). - **Adopt new cards only** in a collection marked as Postulo's own (#13). - **Follow the book's vCard version** for cards Postulo writes: read `supported-address-data`; vCard 3 where 4.0 is not listed (RFC 6352 §6.2.2). ## What is wrong today - A push replaces the whole card with those eight properties (`src/postulo_dav/text.py:155-174`, `sync.py:498-508`): an adopted phone card, once edited here, loses its address, photo, birthday, other numbers and emails, and `X-` properties, if the server accepts a push that also changes its UID (#7). - Every card goes out as `VERSION:4.0`, and `supported-address-data` is never read (`text.py:156`); a book not listing vCard 4.0 must refuse it. Untried on iCloud. - Only the first `ORG` component is read and only the company written (`text.py:160-161`, `:190-191`), so departments are lost both ways. `KIND` is never read: a `KIND:org` card becomes a contact named after the company. - A pull that changes the `ORG` company leaves the contact in its old company's department (`sync.py:152-154`), a pair `Contact.clean` refuses (`src/postulo/jobs/models.py:649-655` in the core). ## Decisions 1. **The words.** "using its URI": the card's identity as #7 keys it, not its href, which changes when a card moves (RFC 6578 §3.5). "the user": the account holder, not other accounts, which a per-person connection cannot reach. "Every contact": any card may be paired, none by default (postulo/postulo#16 kept whole personal books out). **Recommended:** these readings. 2. **Writing back to the account holder.** Two-way lets a phone change the sign-in email (unique), a verified or recovery number, or add a number another account holds. **Recommended:** Postulo → card only; failing that, fill blanks only (the career-file importer's rule, `src/postulo/resume/importing.py`), never those. 3. **Deleting a record whose card Postulo created or keeps in its own collection.** Unpaired there, it would be adopted again next run. **Recommended:** delete the card, `If-Match` guarded as today; unpair any other. 4. **One record per card.** `SyncLink` lets two records share a card. **Recommended:** one; a contact's company and department follow from its `ORG`. 5. **`NOTE` on a card made elsewhere.** Postulo's notes are private and gather conflict records (`sync.py:472-484`); a pushed `NOTE` exposes them, a pulled one replaces them (`sync.py:151`). **Recommended:** leave `NOTE` out unless the person switches it on. 6. **Departments while `employer_structure` is off** (hidden, not deleted). **Recommended:** keep the pairing, sync nothing. ## Needs from the core - postulo/postulo#328: companies, departments and the account holder; creating and updating a contact; postal addresses; several numbers and links. - postulo/postulo#329: numbers, email and addresses for a company, and a contact's addresses on its form. - postulo/postulo#327: where a person pairs and unpairs. - postulo/postulo#326: the collection on a link, and unbinding. Without them only contacts are paired, by adoption; the rules above can land first. Depends on #7 and #13. ## Done when - A card from a personal book, paired with a contact and changed here, keeps its UID and every property Postulo does not own; deleting the contact leaves it. - A company card paired with a company keeps name, website and notes in step; `ORG:Company;Unit` sets the contact's department (with `employer_structure` on). - A paired account-holder card never changes the sign-in email or a number here. - A book whose `supported-address-data` lacks vCard 4 gets vCard 3. - New strings go through `uv run postulo-messages extract`, fr-fr and pt-pt 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.toml`'s dev group pins `1d6cdd7e0`) and extending `tests/test_surface.py`. From the list of DAV plugin improvements of 2026-09-30.
Author
Owner

From postulo/postulo-dav#14, which closed with 32cca53: one thing it asked for can only be done here.

A LANGUAGE parameter found on a card made elsewhere survives a push. The parameter is a BCP 47 language tag (RFC 6350 §5.1), and this issue is where a card is patched instead of rebuilt, so it is where one that is already there must be kept as it was written. If Postulo ever writes one of its own, it writes the tag in its canonical form, as the core does since postulo/postulo#337 (pt-BR, not pt-br).

Worth a test of its own here: a card carrying LANGUAGE=pt-BR on a property comes back from a push with it.

From postulo/postulo-dav#14, which closed with `32cca53`: one thing it asked for can only be done here. **A `LANGUAGE` parameter found on a card made elsewhere survives a push.** The parameter is a BCP 47 language tag (RFC 6350 §5.1), and this issue is where a card is patched instead of rebuilt, so it is where one that is already there must be kept as it was written. If Postulo ever writes one of its own, it writes the tag in its canonical form, as the core does since postulo/postulo#337 (`pt-BR`, not `pt-br`). Worth a test of its own here: a card carrying `LANGUAGE=pt-BR` on a property comes back from a push with it.
Author
Owner

The maintainer's list of 2026-10-02 asks for vCard 4 as the format for contact details, “a step closer to official DAV plugin”. Core issue postulo/postulo#660 puts one vCard 4 mapping in the core, offered on the plugin surface so this plugin can drop the vCard half of text.py.

Shared would be the contact properties (FN, ORG:Company;Unit, TITLE, EMAIL, TEL, ADR, URL), KIND:org for companies, the person's own card, and phone numbers in international form through postulo/postulo#304. The core omits N instead of splitting FN as card_to_text does (src/postulo_dav/text.py:158-159).

This issue keeps its own scope: choosing what is paired, patching cards, the UID on the link, and the vCard 3 step down.

From the list of improvements of 2026-10-02.

The maintainer's list of 2026-10-02 asks for vCard 4 as the format for contact details, “a step closer to official DAV plugin”. Core issue postulo/postulo#660 puts one vCard 4 mapping in the core, offered on the plugin surface so this plugin can drop the vCard half of `text.py`. Shared would be the contact properties (`FN`, `ORG:Company;Unit`, `TITLE`, `EMAIL`, `TEL`, `ADR`, `URL`), `KIND:org` for companies, the person's own card, and phone numbers in international form through postulo/postulo#304. The core omits `N` instead of splitting `FN` as `card_to_text` does (`src/postulo_dav/text.py:158-159`). This issue keeps its own scope: choosing what is paired, patching cards, the UID on the link, and the vCard 3 step down. From the list of improvements of 2026-10-02.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo-dav#8
No description provided.