- On the plugin:
visibilityModeland an optionaldefaultAudience. - On each Tap that carries access facts:
access.roles, and an optionalaccess.defaultAudience. - On the fields of those Taps: the
x-access*annotations.
connector.visibilityModel, connector.defaultAudience, and, for
each Tap in connector.taps, access and the x-access* keys in schema.
Tap schemas lists every other key on a Tap schema.
Tokens and anchors
Parable turns every access fact into tokens. A reader holds tokens. Each row is reached through an anchor that holds tokens. A reader can read a row when the reader and the row’s anchor share at least one token.
Parable matches a reader to their accounts in a Provider by the email that
the reader signs in with. The match goes through the
identity tables, so a user
principal gives tokens only when its Tap is an identity directory. Only an
exact email or account ID match gives tokens. A link through
x-transformExternalUserId gives none. An administrator can also link a
member to an account that has another email.
An anchor is the row whose tokens decide a table row:
Table classes
Parable gives each Provider table one of three classes when it plans the table:- Anchored. The Tap’s root has an
x-accessParentfield, the Tap has theresourcesrole, or the table is a child table of an anchored table. Each row reads by the tokens of its anchor. - Audience. The table is not anchored, and its Tap or its plugin declares
defaultAudience. Every row is open to that audience. - Not modeled. Every other table. Source permissions do not narrow it. Only Provider Policies decide who reads it, and a Provider Policy cannot choose source permissions for it.
The visibility model
visibilityModel says how the Provider decides who sees a record:
visibilityModel describes the Provider. It does not decide a table’s class.
The Tap and field declarations do.
visibilityModel is a different field from permissionModel.
permissionModel says what Parable’s own credential needs in order to read a
Tap. See Required access.
defaultAudience is optional. It is the audience of every Tap that has no
anchor and no audience of its own:
Coupa and Linear both declare
defaultAudience: source_users. A Tap’s own
access.defaultAudience replaces the plugin’s value for that Tap.
Tap roles
A Tap that carries access facts declaresaccess.roles. A Tap can have more
than one role:
issues, needs no access object. Its
x-accessParent field anchors it.
A Tap whose root principal is a user is also an identity directory
(identityDirectory: true), and its x-accessPrincipal field is its
x-transformAccountId field. A reader reaches a Provider user only through
identity matching.
Field annotations
Each annotation goes on the field that carries the fact. Access annotations and identity annotations are separate families, and one field can carry both. A directory user’sprimaryEmail carries both x-transformPersonEmail and
x-accessPrincipalRef.
x-accessParent, x-accessResource, and x-accessMemberOf name a Tap of
the same plugin, or the Tap itself. The value that they read must equal the
merge key of the named Tap. idField says which property of a referenced
object holds that value.
Examples
A user principal. Linearusers:
false is a value. On active, it marks the row inactive when active is
false. An inactive user holds no tokens.
A memberships Tap names the container and the member. Google group_members
keys each row on the group and the member together:
Patterns
Each pattern below is in a Provider Plugin that Parable ships.Each record carries its grants
A Google Drive file holds an array of permissions.x-accessGrants on the
array makes each element a grant on the file. The fields of one element
describe the grant:
grants Tap. Google shared_drive_file_permissions copies the file’s ID
from the parent file and puts x-accessResource on it. Its synthetic key
covers the file, the permission, and the subject, so each grant keeps its own
row (other fields are not shown):
Records inherit from a container
A Linear team is a principal that admits its own members. It is also a resource whose visibility Linear sets:Inheritance that a record turns off
A Google shared drive file inherits from its parent folder, unless the file turns inheritance off:breakWhen holds, the row keeps only its own grants. Rows below it
inherit only those grants. breakWhen.field is a top-level scalar field of
the same row. When Parable cannot read the value, because it is null or the
table does not have the column yet, the row inherits nothing and keeps only
its own grants.
A null reference with a meaning
A row whosex-accessParent reference is null inherits nothing. A Linear
workspace label names no team, and every Linear member who is not a guest
sees it. whenAbsent gives such a row its audience:
whenAbsent is source_users or anyone. A plugin uses it only where the
Provider documents what a null reference means. It is not allowed on an
array.
Only the owner reads the row
A mailbox is open to its owner. Google Gmailmessages_list, threads, and
thread_messages copy the mailbox’s primaryEmail from the parent user and
anchor each row on it:
anchor, every row of one owner shares one anchor. An owner anchor has
these conditions:
- The Tap has the
resourcesrole. - The owner field is a plain column of the root table.
- The owner is the only grant source of the row. The row has no other
x-accessOwner,x-accessGrants,x-accessVisibility,grantsSelfprincipal, orx-accessParent. - Each child table carries the owner field through
parentContextFields. - No other Tap names this Tap’s rows in
x-accessResourceorx-accessParent.
The actor of an audit event
Googlechrome_activity and gemini_activity put x-accessOwner on the
nested actor.email, without anchor. Each event is open to its actor:
Managers read the rows of their reports
A person’s Parable Pulse activity is open to that person. Manager reads let the person’s direct manager read it too. Two declarations make this work, and the Workspace turns it on. The reporting line. A Provider that holds reporting lines declares each user’s direct manager withx-accessManager. It goes on one field of the
root of a principals Tap whose principal is a user. The row’s own user is
the report. The value names the manager by principal ID (by: id) or by an
x-accessPrincipalRef email of the same Provider (by: email). Workday
workers, Justworks members, and Lattice users name the manager by ID.
Google users keeps managers in its relations array. On an array of
objects, idField names the element’s manager field, and when keeps only
the elements whose field equals the value:
managers
on its x-accessOwner. managers needs anchor and an owner of kind
user. Pulse activity opts in:
managers:<owner token>.
After each sync, Parable writes the managers: token of each direct report
for each manager’s email to providers.access.manager_tokens.
The Workspace setting. Manager reads are off by default. An
administrator turns them on in Protection → Provider Policies, in the
Source permissions card, with Managers read their direct reports’ Pulse
activity. The administrator chooses the Provider whose reporting lines
count, and confirms that the Workspace’s people were told that their manager
can read their activity. From code, read the setting with
GET /api/provider-policies/source-permissions (managerReads and
managerReadSources), and change it with
PUT /api/provider-policies/source-permissions/manager-reads:
connectorId of null turns manager reads off. Parable refuses the change
with WA-PP-016 when peopleTold is not true, with WA-PP-017 when the
Provider is not one of managerReadSources, and with WA-PP-009 when the
settings changed after expectedRevision. Agents cannot read or change the
setting.
What a manager reads. While manager reads are on, a manager holds the
managers: tokens of their direct reports, so they read those people’s rows
on each table that opts in. Only direct reports count: a manager’s manager
reads nothing more. A row with no owner, such as a row from a Mac with no
bound person, stays closed to every manager. Manager reads change rows only
where source permissions decide them: while Enforce source permissions is
on, or where a Provider Policy rule reads Source permissions on the
table. Each read through a reporting line is logged with the manager, the
table, and the number of direct reports.
Manager reads
explains the setting and the notice that each person sees.
Every user sees everything
Coupa has no rules for each record. Its plugin declaresvisibilityModel: source_users and defaultAudience: source_users. Its
users Tap is the only Tap with access (roles: ["principals"]). Every
active Coupa user who is not an API user holds source_users and reads every
Coupa row.
A table that only All data access reads
Some tables can hold sensitive data, but the Provider gives Parable no fact that names a reader. A Workday HRIS RaaS custom report has the columns that each Workspace chooses, and it can hold pay. The plugin has no user directory, and no column names an owner. As a not-modeled table, it would read every row with enforcement on. To close it instead, give its Tap theresources role and no grant source:
visibilityModel: not_modeled and declares no
defaultAudience. The Tap needs only a merge key, and its fields carry no
x-access* annotation. The table is anchored on its merge key, and its rows
hold no token:
An administrator opens the table to a role with a Provider Policy rule. A
rule that reads Source permissions on the table gives no row, because no
row holds a token. A Policy change applies at the next read, with no sync.
Parable’s readiness counts report every key of the table as a key with no
token. For this pattern, that count is by design and does not show a broken
mapping.
Use this pattern only when the Provider gives no fact that can name a
reader. When a later change adds a user directory or an owner column, model
the real rule instead. Plugins that use this pattern:
- Oracle Fusion Cloud: the five
audit_history_*Taps. - Slack Events:
events. - Workday HRIS RaaS:
custom_report.
What turns into a token
- Levels.
owner,write,comment, andreadadmit a reader in the same way.none, and a value that thex-accessLevelmap does not name, give nothing. Without anx-accessLevelfield, a grant isread. - Kinds. A subject’s kind comes from the
x-accessKindmap, or else from thekindof itsx-accessSubject.ignore, a value that the map does not name, or no kind gives no grant. - Subjects that match no principal. A grant to an ID or email that no principal Tap holds gives a token that no reader holds.
- Inactive rows. An inactive principal gives no tokens, and its memberships count for nothing. An inactive grant or membership gives nothing.
- Guests. A guest keeps their own
user:token and their memberships. A guest gets nosource_usersand nodomain:token. - Expiry. A grant stops admitting a reader when its
x-accessExpiresAttime passes. A value that is not RFC 3339 never expires. - Link-only.
x-accessDiscoverableaffects onlyanyoneanddomaingrants. A link-only grant admits its audience only when the Workspace turns on Link-only sharing admits its audience. - A resources Tap with no grant source is anchored, but its rows hold no
token. Under enforcement only people with All data access, and the roles
that a Provider Policy rule gives All rows or Rows where, read
them. Slack Events
events, Oracle Fusion Cloud’s fiveaudit_history_*Taps and Workday HRIS RaaScustom_reportare such Taps (A table that only All data access reads).
deletionSemantics: by_absence. Parable reads no
deleted row.
The access tables
After each sync, Parable rebuilds the Workspace’s access tables inproviders.access. Only people with All data access can query them, in SQL
that names no Parable. For every other reader, a query that names them fails
as an unknown table. The
Provider Pool reference
says more about who can read them.
Turn on enforcement
Source permissions are a Workspace choice. A Workspace turns them on in Protection → Provider Policies, in the Source permissions card:- Enforce source permissions narrows each anchored or audience table that no Provider Policy names.
- A Provider Policy rule can read Source permissions on such a table for the roles that it names, whether or not the switch is on.
- Link-only sharing admits its audience lets link-only grants admit their audience.
- Managers read their direct reports’ Pulse activity turns on manager reads. See Managers read the rows of their reports.
What a reader sees
When source permissions apply to a table, every read follows them: SQL, Plots, Pages, and agents. A reader under source permissions gets fewer rows. The query does not fail and does not say which rows it left out. A person with no account in the Provider reads only the rows that are open to everyone. While manager reads are on, a manager also reads the rows of their direct reports on the tables that opt in. People with All data access read every row when they read a table themselves. Each such read is recorded in the Workspace’s audit events. When Parable has not built the access of a Provider yet, for example before its first sync, a read of a table under source permissions fails withQL-TN-009:
Workspace overrides
Your Workspace keeps its own copy of each Tap schema, itsoutputSchema
(Taps). The access
annotations on that copy stay the plugin’s:
- Send them unchanged. The copy holds the plugin’s
x-access*values. A write that changes, adds, or removes anx-access*value fails withWA-TCT-035. The error details list the fields. - Do not exclude an annotated field. A write that sets
x-excludeon a field that the plugin annotates fails withWA-TCT-021. - A later plugin change can annotate a field that you exclude. The plugin
change succeeds, and your copy keeps both the exclude and the annotation.
Parable cannot read the field, so it withholds the annotation. The table
keeps its class but gives no tokens, so only people with All data access
read its rows. The table never falls back to all rows. Remove the
x-excludeto restore the plugin’s access. Until you do, Parable refuses otheroutputSchemachanges on that copy withWA-TCT-021.