Skip to content

feat(common-contacts): stop copying members into every instance - #4970

Merged
shepilov merged 6 commits into
masterfrom
feat/common-contacts-stop-copying-members
Oct 27, 2026
Merged

shepilov merged 6 commits into
masterfrom
feat/common-contacts-stop-copying-members

Conversation

@MontaGhanmy

@MontaGhanmy MontaGhanmy commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Separate two context flags, both defaulting to false: common_contacts enables the contacts service; centralized_org_directory controls directory replication.
  • With centralization disabled, user.created keeps copying contacts and historical groups even when the contacts service is enabled.
  • With centralization enabled, B2B lifecycle events populate the organization instance without copying the directory into new member instances. This works with common_contacts: false; a missing organization instance returns an error.
  • User creation still sets the passphrase, vault keys, Matrix ID and email. User deletion leaves feed-owned contacts to the feed only while the service is enabled.
  • Organization sharing trust also works with centralized storage when the service is disabled, subject to the existing auto-accept settings.
  • Keep centralized_org_directory: false for the first backend rollout. Existing copies remain in place; the sharing UI and group-change propagation must support centralization before enabling it.
  • Depends on feat: write the contacts of twake:contacts:common #4935 and feat(common-contacts): resolve share emails on org first #4939.

Part of linagora/twake-workplace-private#1778.

Base automatically changed from feat/share-by-email to master October 13, 2026 17:45
@shepilov

shepilov commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

The check is made on each instance, since the instance of the deleted user may already be gone.

what check if according to PR name we just suppose to stop copy contacts

@shepilov

Copy link
Copy Markdown
Member

also name of the PR doesn't sound like 22 files and 1k lines of code)

@MontaGhanmy
MontaGhanmy force-pushed the feat/common-contacts-stop-copying-members branch from 96cd221 to 1c3fd5f Compare October 14, 2026 15:40
@shepilov

Copy link
Copy Markdown
Member

@MontaGhanmy just understood that it won't work without groups. If the groups are synced but contacts no, it doesn't make any sense, because what will be in groups then?

@MontaGhanmy

Copy link
Copy Markdown
Contributor Author

Right, since membership is stored on the contacts, the groups have to move to the org instance along with them, which is what #4976 does further up this stack (#4970 → #4975 → #4976), so they're meant to be merged together.

Comment thread pkg/rabbitmq/handlers.go Outdated

// The feed writes the members.
if inst.HasCommonContacts() {
return nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This return also skips copyManagedGroups, since CopyOrgDirectoryFromOrgInstance copies the groups before the contacts. A member onboarded under common_contacts has no io.cozy.contacts.groups db.

The next b2b.group.member.added fails in ensureGroupExists with not_found, gets nacked until the DLQ, and because forEachOrgInstance joins errors it fails for every later member of that group too.

Could SyncGroupMembersAdded upsert the group when it is missing? That also heals instances onboarded while this was deployed. Copying the groups here alone would not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#4976 already covers this (group events stay on the org instance), the PRs were meant to go together. However, I still took your fix so #4970 is safe on its own.

Comment thread pkg/rabbitmq/org_contacts.go Outdated
}
// The feed deletes the member.
if inst.HasCommonContacts() {
continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This skips every common_contacts instance, but the feed DELETE (domain audience) only reaches the org instance.

Copies written on member instances by b2b.group.* events (trustedForSharing, no carddavPath) are never removed: alice deleted, bob keeps her.

Can we skip only contacts that have a carddavPath here?

Comment thread pkg/rabbitmq/handlers.go Outdated
return fmt.Errorf("user.created: update settings: %w", err)
}

// The feed writes the members.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creation is decided by the new instance's context, deletion by each target instance's context. If an org spans a common_contacts context and one without it, a legacy member gets copied into cc instances and is never removed. If an org cannot span contexts, a comment saying so is enough.

Otherwise decide per target instance in syncCreatedOrgContact as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered by the user.deleted change: it now skips only the feed's contacts, whatever the instance's context, so a legacy copy on a common_contacts instance gets removed too.

Comment thread pkg/rabbitmq/handlers_test.go Outdated
}
}

func TestCommonContactsStopsMemberCopies(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you assert that settings.email and matrix_id are still set under common_contacts? The PR promises it but the test only checks nothing was copied.

Comment thread docs/rabbitmq.md Outdated

- `user.password.updated` → updates an instance passphrase when a `user.password.updated` routing key is received.
- `user.created` → validates and processes user creation events.
- `user.created` → validates and processes user creation events. Copies no organization contact when the context sets `common_contacts`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It now also skips the groups. The group paragraph further down still says groups are in every instance.

@rezk2ll rezk2ll left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small change, and doing the check per target instance on user.deleted is the right call.

One thing blocks for me though: under common_contacts, user.created now also skips the group copy. So the next b2b.group.member.added for that group ends up in the DLQ, for the whole org.

I reproduced it on a local stack.

@MontaGhanmy
MontaGhanmy requested a review from rezk2ll October 20, 2026 09:47
@shepilov
shepilov added this pull request to stack #5114 October 27, 2026 16:40
@shepilov
shepilov force-pushed the feat/common-contacts-stop-copying-members branch from 56dfd2d to fad56b9 Compare October 27, 2026 16:55
MontaGhanmy and others added 6 commits October 27, 2026 17:56
The feed DELETE only reaches the org instance, so skipping every
common_contacts instance left the copies written by the b2b.group.*
events on the member instances. Skip only the contacts the feed wrote,
the ones with a carddavPath.
A member onboarded under common_contacts gets no copy of the groups, so
b2b.group.member.added failed on their instance and the whole event went
to the dead letter queue. Create the group when it is missing.
@shepilov
shepilov force-pushed the feat/common-contacts-stop-copying-members branch from fad56b9 to 6c944e5 Compare October 27, 2026 16:56
@shepilov
shepilov merged commit 43d607a into master Oct 27, 2026
5 of 6 checks passed
@shepilov
shepilov deleted the feat/common-contacts-stop-copying-members branch October 27, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants