Skip to main content
A Provider decides which of its users can open each record. A Google Drive file is shared with people, groups, and domains. A Linear issue belongs to a team, and the members of that team see it. A Coupa record is open to every active Coupa user. Source permissions keep those rules in your Workspace: when your Workspace turns them on, each person reads only the Provider rows that they could open in the Provider. The Provider Plugin declares the rules in three places:
  1. On the plugin: visibilityModel and an optional defaultAudience.
  2. On each Tap that carries access facts: access.roles, and an optional access.defaultAudience.
  3. On the fields of those Taps: the x-access* annotations.
After each sync, Parable reads these facts from the Provider’s rows and builds the Workspace’s access tables. A query then keeps only the rows that a reader can open. The declarations never change how a table is stored. They change which rows a reader gets, and only when the Workspace enforces source permissions. Parable maintains every Provider Plugin. Read the declarations on the plugin of a connection:
Look at 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:
  1. Anchored. The Tap’s root has an x-accessParent field, the Tap has the resources role, or the table is a child table of an anchored table. Each row reads by the tokens of its anchor.
  2. Audience. The table is not anchored, and its Tap or its plugin declares defaultAudience. Every row is open to that audience.
  3. 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 declares access.roles. A Tap can have more than one role:
A Linear team is both a principal and a resource. A Tap that only inherits access, such as Linear 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’s primaryEmail 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. Linear users:
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:
Membership is transitive. A member of a group that belongs to another group holds both group tokens, to a depth of 20.

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:
When a Provider returns grants from a separate endpoint, they get their own 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:
An issue inherits its team’s tokens through one annotation:
Inheritance is transitive. A row inherits what its parent row holds, inherited tokens included. When references form a cycle, the reference that closes the cycle gives nothing.

Inheritance that a record turns off

A Google shared drive file inherits from its parent folder, unless the file turns inheritance off:
When 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 whose x-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 Gmail messages_list, threads, and thread_messages copy the mailbox’s primaryEmail from the parent user and anchor each row on it:
With anchor, every row of one owner shares one anchor. An owner anchor has these conditions:
  • The Tap has the resources role.
  • 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, grantsSelf principal, or x-accessParent.
  • Each child table carries the owner field through parentContextFields.
  • No other Tap names this Tap’s rows in x-accessResource or x-accessParent.
A row whose owner field is null has no anchor. Only people with All data access read it.

The actor of an audit event

Google chrome_activity and gemini_activity put x-accessOwner on the nested actor.email, without anchor. Each event is open to its actor:
The table anchors on its merge key, and each row has the actor’s grant.

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 with x-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:
A value gives a reporting line only when the manager is a user of the same Provider, both people are active, and the manager is not the report. The table that opts in. An owner-anchored table opts in with managers on its x-accessOwner. managers needs anchor and an owner of kind user. Pulse activity opts in:
Each row of such a table also carries the token 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:
A 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 declares visibilityModel: 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 the resources role and no grant source:
The plugin keeps 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, and read admit a reader in the same way. none, and a value that the x-accessLevel map does not name, give nothing. Without an x-accessLevel field, a grant is read.
  • Kinds. A subject’s kind comes from the x-accessKind map, or else from the kind of its x-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 no source_users and no domain: token.
  • Expiry. A grant stops admitting a reader when its x-accessExpiresAt time passes. A value that is not RFC 3339 never expires.
  • Link-only. x-accessDiscoverable affects only anyone and domain grants. 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 five audit_history_* Taps and Workday HRIS RaaS custom_report are such Taps (A table that only All data access reads).
A principals Tap reads every account, inactive accounts included, so that an account that becomes inactive shows the new value at the next sync. A complete read also declares deletionSemantics: by_absence. Parable reads no deleted row.

The access tables

After each sync, Parable rebuilds the Workspace’s access tables in providers.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.
A change of sharing in the Provider reaches reads in Parable after the Provider’s next sync.

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.
Provider Policies explains the switches, how rules combine, and the card that shows what source permissions do for each member.

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 with QL-TN-009:
Parable never reads all rows in that case. Wait for the Provider’s next sync, or check the Freshness tab of the What source permissions do card.

Workspace overrides

Your Workspace keeps its own copy of each Tap schema, its outputSchema (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 an x-access* value fails with WA-TCT-035. The error details list the fields.
  • Do not exclude an annotated field. A write that sets x-exclude on a field that the plugin annotates fails with WA-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-exclude to restore the plugin’s access. Until you do, Parable refuses other outputSchema changes on that copy with WA-TCT-021.
A change to an access declaration rewrites no stored data. Readers take access from the declarations and the access tables, not from the stored rows.