Skip to content

feat(common-contacts): remove the member copies once the org instance is filled - #4975

Merged
shepilov merged 6 commits into
masterfrom
feat/common-contacts-remove-member-copies
Oct 27, 2026
Merged

shepilov merged 6 commits into
masterfrom
feat/common-contacts-remove-member-copies

Conversation

@MontaGhanmy

@MontaGhanmy MontaGhanmy commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add cozy-stack fix member-copies <org-id> and POST /instances/fixers/member-copies/:org_id to remove replicated directory contacts after centralization.
  • Require centralized_org_directory, independently of common_contacts. B2B events or the contacts service can populate the organization instance.
  • Before deleting any copies, verify every candidate has an active organization contact, all of its group memberships are present centrally, and the corresponding managed groups exist. Missing or trashed destinations refuse cleanup.
  • Return 404 for a missing organization instance and 412 when centralization is disabled or a candidate is missing from the organization directory.
  • Keep personal contacts, contacts written for sharing, feed-owned contacts and instances still retaining replication. Group copies remain in place.
  • Bulk deletion publishes no old document, preserving existing sharing memberships. Dry runs report candidates; repeated cleanup removes nothing more.
  • Depends on feat(common-contacts): stop copying members into every instance #4970.

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

Comment on lines +36 to +41
if orgInst == nil {
return nil, fmt.Errorf("organization %s has no organization instance: %w", organizationID, instance.ErrNotFound)
}
if !orgInst.HasCommonContacts() {
return nil, errors.New("the organization instance does not read the 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.

Only the context flag is checked, not that the org instance is filled.

Run before the republication is consumed, it wipes the org members with nothing to replace them (live: 0 contacts with carddavPath on the org instance, 5 copies deleted).

Refuse when the org instance has no contact with a carddavPath (FindDocs, Limit 1, by-carddav-path).

Comment thread web/instances/fixers.go
Comment on lines +369 to +376
func memberCopiesFixer(c echo.Context) error {
dryRun := c.QueryParam("dry_run") == "true"
report, err := orgdirectory.RemoveMemberCopies(c.Request().Context(), c.Param("org_id"), dryRun)
if err != nil {
return err
}
return c.JSON(http.StatusOK, report)
}

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.

Unknown org, missing org instance and "does not read the common contacts" all come back as 500 Unqualified error.

Map instance.ErrNotFound to 404 and the precondition to 400 or 412.

Comment thread cmd/fix.go Outdated
Comment on lines +531 to +533
for domain, n := range report.Removed {
fmt.Fprintf(os.Stdout, "%s %d from %s\n", removed, n, domain)
}

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.

report.Removed prints in map order. Sort the domains so outputs diff.

@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.

The problem is the copies come back.

addMembersToGroup (model/orgdirectory/groups.go L346-L376) still upserts into every org instance, common_contacts ones included. Same for syncCreatedOrgContact (pkg/rabbitmq/org_contacts.go L46-L70) when the new member is in a context without common_contacts.

I ran the fixer locally, sent one b2b.group.member.added, and the dry run listed a copy again. Then a user.created from a plain context member brought back two more.

Either skip inst.HasCommonContacts() in both, or say in the help text that the fixer has to be rerun until #1780.

Also, after the fixer, AddGroup on a member instance (model/sharing/group.go L37-L63) only finds the contacts the feed took over. Locally a new group sharing invited alice and zed, and lost bob and carol.

@MontaGhanmy

Copy link
Copy Markdown
Contributor Author

addMembersToGroup (model/orgdirectory/groups.go L346-L376) still upserts into every org instance, common_contacts ones included.

Right, on its own this PR leaves b2b.group.* writing on every instance, so the next group event brings the copies back. #4976, stacked on this one, skips the member instances under common_contacts in forEachOrgInstance: group events only write on the org instance, and TestSyncGroupWithCommonContacts checks the member instances get no copy. The three PRs (#4970 → #4975 → #4976) go together.

Same for syncCreatedOrgContact (pkg/rabbitmq/org_contacts.go L46-L70) when the new member is in a context without common_contacts.

That one is expected: contexts without common_contacts work as before (#4970), so a member created there is still copied into every instance, the common_contacts ones included. Since 04331d7 on #4970, user.deleted removes those copies on every instance, so they don't outlive the member. Nothing changes in this PR.

Also, after the fixer, AddGroup on a member instance only finds the contacts the feed took over.

Right, the membership lives on the contacts, so removing the copies empties the groups on the member instances: your bob and carol case. #4976 looks the group up on the org instance first and reads its members there (TestAddGroupFromOrgInstance). Same reason as on #4970: the groups have to move with the contacts, so this fixer only makes sense with #4976.

@MontaGhanmy
MontaGhanmy force-pushed the feat/common-contacts-remove-member-copies branch from de6d822 to ae3b520 Compare October 20, 2026 09:10
@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-remove-member-copies branch 2 times, most recently from 5ee5d41 to 1a75e14 Compare October 27, 2026 16:56
Base automatically changed from feat/common-contacts-stop-copying-members to master October 27, 2026 18:34
MontaGhanmy and others added 6 commits October 27, 2026 19:34
…eed fills the org instance

Run before the republication was consumed, the fixer deleted the copies
with nothing to replace them. It now waits for at least one contact with
a carddavPath on the organization instance, which only the feed writes.
An unknown organization, a missing organization instance and an
organization instance that is not ready all came back as a 500.
@shepilov
shepilov force-pushed the feat/common-contacts-remove-member-copies branch from 1a75e14 to 09c37d3 Compare October 27, 2026 18:35
@shepilov
shepilov merged commit 58064cd into master Oct 27, 2026
2 checks passed
@shepilov
shepilov deleted the feat/common-contacts-remove-member-copies branch October 27, 2026 18:35
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