Skip to content

Record sharing 8/14: inherited objects follow their parent - #25439

Draft
martmull wants to merge 3 commits into
claude/adversarial-agents-impl-vq7601-pr6from
claude/adversarial-agents-impl-vq7601-pr8
Draft

Record sharing 8/14: inherited objects follow their parent#25439
martmull wants to merge 3 commits into
claude/adversarial-agents-impl-vq7601-pr6from
claude/adversarial-agents-impl-vq7601-pr8

Conversation

@martmull

@martmull martmull commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Seventh PR of the record-sharing stack in merge order (plan number 8), stacked on #25438 (call recording goes PRIVATE). Inert until a parent object is PRIVATE and IS_RECORD_SHARING_ENABLED is on.

What changes

  • objectMetadata.readabilityParentFieldUniversalIdentifiers (uuid array, nullable) names the MANY_TO_ONE relation fields an INHERITED object follows. Declared in app manifests as readabilityParentFieldUniversalIdentifiers, synced by the standard application (compared and stringified), exposed read-only on the metadata API. Fast instance command 2-39-instance-command-fast-1788560886501-add-readability-parent-fields-to-object-metadata.
  • The gate: for an INHERITED alias the repository builds one condition per parent column: (every parent column IS NULL) OR (col IS NOT NULL AND <parent readable>), where an OPEN parent needs nothing, a PRIVATE parent needs a share row on the parent record at the operation's access level (so updating an attachment needs READ_WRITE on its note), an INHERITED parent recurses (depth capped at 3), and a SYSTEM or APPLICATION parent contributes nothing. Declaring one morph relation field expands to every sibling of the same morph (custom objects added as targets included). When every parent is OPEN the condition is omitted entirely.
  • A joined alias whose join comes from one of its parent fields is not re-gated: the parent alias was gated already and the join condition is the foreign key equality.
  • Fail closed: an INHERITED object that resolves no parent field (unknown identifier, wrong object, not MANY_TO_ONE) is refused like SYSTEM, instead of silently behaving as OPEN.
  • Six standard objects become INHERITED from their target morph: attachment (targetNote), timelineActivity (targetPerson), noteTarget (note), taskTarget (task), messageThreadTarget (messageThread), calendarEventTarget (calendarEvent).

Things to know

  • New workspaces get the six INHERITED objects from the standard-application sync. Existing workspaces keep them OPEN (today's behaviour) until the settings PR ships the make-standard-child-objects-inherited workspace command; the flip is harmless to defer since no standard parent is user-private before then.
  • Rows whose every parent column is null stay visible.
  • A row pointing at two parents is readable when either is.
  • The in-memory twin (Record sharing 5/14: evaluate record shares in memory for events #25429) still treats INHERITED as open: subscriptions, webhooks and workflow triggers on an attachment of a private note still fire. It will be addressed with the timeline guard (PR 7 of the plan), which is the point where a user can make notes private.
  • timelineActivity inherits from its target (the person or company), not from its linked record, so a private note's cached title on an open person's timeline is still visible; that is the leak PR 7 closes.
  • The unscoped root read on an inherited object pays one indexed lookup per candidate row; the plan's "checked once" optimisation for a root filter on a parent column is not implemented, the per-row condition serves both shapes.

Tests

  • Unit: parent resolution (declared plain relation, morph sibling expansion, non-to-one ignored), the condition builder (open, private, inherited, all-open collapses to no condition), the standard-object readability spec now asserting the six inherited objects and their resolved parents.
  • Integration inherited-readability-object-records-permissions.integration-spec.ts (8 cases): attachments of a private note hidden from the unshared member and visible to the shared one, morph sibling on an open person still visible, update and delete refused with READ and allowed with READ_WRITE on the note, ordering by the parent relation keeps the gate, all-null parents visible, zero resolved parents refused.
  • Sync-application snapshot updated with the new property.

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. This PR: INHERITED objects follow their parent
  8. Record sharing 9/14: sync writes mailbox and calendar share rows #25441 sync writes mailbox and calendar share rows
  9. Record sharing 10/14: email and calendar go private #25445 email and calendar go PRIVATE
  10. Record sharing 11/14 (server): manual sharing and ownership #25443 manual sharing and ownership (server)
  11. the Share action and dialog (next)
  12. Record sharing 12/14 (storage): the sharingRule entity #25444 the sharingRule entity

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

App docs drift check

Change Docs page Status Suggested fix
readabilityParentFieldUniversalIdentifiers?: string[] added to ObjectManifest (twenty-shared/src/application/objectManifestType.ts) packages/twenty-docs/developers/extend/apps/data/objects.mdx ✅ already updated in this PR n/a

Add readabilityParentFieldUniversalIdentifiers on objectMetadata (uuid
array, set by the standard application and by app manifests) and the
INHERITED readability level: a row is readable when one of the parent
records it points to is readable, with morph relation siblings expanded
automatically. Six standard objects follow their target: attachment,
timelineActivity, noteTarget, taskTarget, messageThreadTarget and
calendarEventTarget. An INHERITED object that resolves no parent field
denies reads instead of falling open.
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