Skip to content

Record sharing 9/14: sync writes mailbox and calendar share rows - #25441

Draft
martmull wants to merge 1 commit into
claude/adversarial-agents-impl-vq7601-pr8from
claude/adversarial-agents-impl-vq7601-pr9
Draft

Record sharing 9/14: sync writes mailbox and calendar share rows#25441
martmull wants to merge 1 commit into
claude/adversarial-agents-impl-vq7601-pr8from
claude/adversarial-agents-impl-vq7601-pr9

Conversation

@martmull

@martmull martmull commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Eighth PR of the record-sharing stack in merge order (plan number 9), stacked on #25439 (inherited objects). Inert: message, messageThread and calendarEvent stay OPEN until the next PR flips them, so the rows written here are not consulted yet.

What changes

  • Next to every channel association the sync writes share rows for the message, its thread and the calendar event: one FULL row for the channel's connected account owner (the workspace member behind connectedAccount.userWorkspaceId, skipped when that member left) and one EVERYONE READ row, both rowCause APPLICATION with sourceId = the channel id. A thread or message imported by two mailboxes carries both channels' rows (the unique index keys on sourceId).
  • Rows are written in the same transaction as the association insert, in saveMessagesWithinTransaction, in calendar-save-events.service.ts, and in campaign materialization, which inserts messages and associations directly.
  • A channel visibility change enqueues RefreshMessageChannelRecordSharesJob / RefreshCalendarChannelRecordSharesJob: one transaction that deletes the channel's rows and rebuilds them from the association table, keyset-paged by 500. An association removal (mailbox cleanup, folder exclusion, blocklist, cancelled or deleted events) enqueues the same rebuild; a channel deletion removes its rows.
  • upgrade:2-39:backfill-channel-record-shares runs the rebuild for every message and calendar channel of every workspace, skipping workspaces without the recordShare, message or calendarEvent objects, with --dry-run.
  • ConnectedAccountOwnerService resolves a connected account's owner to a workspace member (nothing did that in one place before).

Ambiguity to decide before PR 10

The plan says "the channel owner for a personal channel, EVERYONE for a share-everything one". Today no channel visibility hides messages from other members at the row level: METADATA and SUBJECT only blank fields. To keep today's behaviour, this PR writes the EVERYONE READ row for every visibility and the owner row on top; the field-level tiers stay in the read hooks. If the intent is that METADATA and SUBJECT mailboxes become owner-only once email is PRIVATE, only the visibility rule in the two row builders changes.

Things to know

  • A rebuild holds the channel's recordShare row locks for its whole transaction, so a sync of that channel that inserts rows at the same time waits for it.
  • The dev seeder writes associations without rows; seeded workspaces need the backfill before the flag is turned on (the next PR handles it).
  • Rows of hard-deleted messages, threads and events are left in place (uuids are never reused); a channel deletion removes its rows in bulk.

Tests

  • Unit: the two row builders (owner and everyone per record, one thread row per batch, owner missing, every visibility value).
  • Integration test/integration/google/messaging/channel-record-shares.integration-spec.ts (6 cases, Google mocks): imported thread and messages carry the owner FULL and EVERYONE READ rows sourced from the channel; a second mailbox importing the same thread adds its own rows; a visibility change rebuilds the rows and removes a stray row; the calendar twin; the backfill dry run writes nothing and two real runs reproduce the live writer's rows exactly. The existing messaging and calendar channel-visibility and channel-deletion-cleanup suites still pass.

Stack

  1. Record sharing 1/14: add the readability level on object metadata #25421 readability level
  2. Record sharing 2/14: add the recordShare hidden standard object and its service #25425 recordShare object and service
  3. Record sharing 3/14: gate reads and writes on private objects with record share rows #25426 read and write gate
  4. Record sharing 4/14: let a create state who may read the record with shareWith #25428 shareWith on create
  5. Record sharing 5/14: evaluate record shares in memory for events #25429 in-memory twin for events
  6. Record sharing 6/14: call recordings go private #25438 call recording goes PRIVATE
  7. Record sharing 8/14: inherited objects follow their parent #25439 INHERITED objects follow their parent
  8. This PR: sync writes mailbox and calendar share rows
  9. email and calendar go PRIVATE (next)

Generated by Claude Code

Review in cubic

Next to every message, thread and calendar event association, the sync
writes one FULL row for the channel owner and one EVERYONE READ row,
sourced from the channel, so the objects can become PRIVATE later
without changing who sees what. A visibility change or an association
removal enqueues a rebuild of the channel's rows, a channel deletion
removes them, campaign materialization writes them too, and a 2.39
workspace command backfills existing channels.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant