Every sync with a contact fails on Postulo 0.3.0: Contact.phone became phone_numbers #1

Closed
opened 2026-09-10 12:18:16 +00:00 by tiagoagueda · 0 comments
Owner

Observation

Against the core's 0.3.0 branch (postulo@a22e7a09), 12 tests pass, 1 fails and 8 error:

TypeError: Contact() got unexpected keyword arguments: 'phone'
AttributeError: 'Contact' object has no attribute 'phone'

The plugin loads, so this happens during a sync. card_for() reads contact.phone for every linked contact (sync.py:71), and apply_card() writes it (sync.py:81). sync() does contacts before interviews with nothing between them, so any sync with a contact in it fails before it reaches the calendar. Neither half syncs.

It cannot pass against the core uv.lock pins either. postulo@86664ae6 predates the sync links this plugin is built on (bcf82e46, 6 September), so the lock names a core older than the plugin, and CI has never tested dav against a core it can run on.

Cause

postulo#90 (1a0f6906, 8 September) replaced Contact.phone with several numbers per holder: phone_numbers, a relation to core.PhoneNumber rows, one of which is_primary. The core reads and writes them through postulo.core.phone_numbers rather than the rows, because of two rules the table does not show:

  • the feature can be switched off for a person, and then only the primary number exists as far as the interface, a CV or the API is concerned;
  • a number is unique across the whole instance, so saving one can be refused because another account already holds it.

What fixing it is

  • Push: build the card's TEL from the numbers the person can see, meaning the primary alone when the feature is off. vCard allows several TEL lines, so several numbers can now travel as themselves instead of being squeezed into one.
  • Pull: write numbers through the core's own path, not by creating rows. A number refused as taken elsewhere is a note in the report, never a failed sync.
  • Keep a failure in one half from sinking the other. Contacts and interviews are independent, and an error in one should be reported rather than abort both.
  • Move the pin: uv lock --upgrade-package postulo, so the lock names a core dav runs on.

Worth being careful about

  • None of this is on the plugin surface yet. dav imports Contact, Interview, SyncLink, ical and the application services directly, and postulo.plugins.api says anything reached that way "may move without warning". This is the first time it has. What a sync plugin may touch deserves a promise in the core. Meanwhile, SyncPlugin, SyncReport, FieldSpec, TestResult and client are on the surface and should come from there, not from postulo.plugins.base or postulo.plugins.http.
  • Deletion stays one-way. The core keeps phone numbers behind a plugin that never deletes them, and dav already never deletes here because of the phone. A number removed from a card must not remove the number in Postulo.
  • Uniqueness discloses, deliberately: the core tells whoever typed a taken number that somebody else may hold it. dav's report should say no more than that.

Classification

Bug. Every sync with a contact fails on Postulo 0.3.0.

## Observation Against the core's `0.3.0` branch (`postulo@a22e7a09`), 12 tests pass, 1 fails and 8 error: ``` TypeError: Contact() got unexpected keyword arguments: 'phone' AttributeError: 'Contact' object has no attribute 'phone' ``` The plugin loads, so this happens during a sync. `card_for()` reads `contact.phone` for every linked contact (`sync.py:71`), and `apply_card()` writes it (`sync.py:81`). `sync()` does contacts before interviews with nothing between them, so **any sync with a contact in it fails before it reaches the calendar**. Neither half syncs. It cannot pass against the core `uv.lock` pins either. `postulo@86664ae6` predates the sync links this plugin is built on (`bcf82e46`, 6 September), so the lock names a core older than the plugin, and CI has never tested dav against a core it can run on. ## Cause postulo#90 (`1a0f6906`, 8 September) replaced `Contact.phone` with several numbers per holder: `phone_numbers`, a relation to `core.PhoneNumber` rows, one of which `is_primary`. The core reads and writes them through `postulo.core.phone_numbers` rather than the rows, because of two rules the table does not show: - **the feature can be switched off for a person**, and then only the primary number exists as far as the interface, a CV or the API is concerned; - **a number is unique across the whole instance**, so saving one can be refused because another account already holds it. ## What fixing it is - **Push**: build the card's `TEL` from the numbers the person can see, meaning the primary alone when the feature is off. vCard allows several `TEL` lines, so several numbers can now travel as themselves instead of being squeezed into one. - **Pull**: write numbers through the core's own path, not by creating rows. **A number refused as taken elsewhere is a note in the report, never a failed sync.** - **Keep a failure in one half from sinking the other.** Contacts and interviews are independent, and an error in one should be reported rather than abort both. - **Move the pin**: `uv lock --upgrade-package postulo`, so the lock names a core dav runs on. ## Worth being careful about - **None of this is on the plugin surface yet.** dav imports `Contact`, `Interview`, `SyncLink`, `ical` and the application services directly, and `postulo.plugins.api` says anything reached that way *"may move without warning"*. This is the first time it has. What a sync plugin may touch deserves a promise in the core. Meanwhile, `SyncPlugin`, `SyncReport`, `FieldSpec`, `TestResult` and `client` are on the surface and should come from there, not from `postulo.plugins.base` or `postulo.plugins.http`. - **Deletion stays one-way.** The core keeps phone numbers behind a plugin that never deletes them, and dav already never deletes here because of the phone. A number removed from a card must not remove the number in Postulo. - **Uniqueness discloses**, deliberately: the core tells whoever typed a taken number that somebody else may hold it. dav's report should say no more than that. ## Classification Bug. Every sync with a contact fails on Postulo 0.3.0.
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#1
No description provided.