> ## Documentation Index
> Fetch the complete documentation index at: https://developers.askparable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Source permissions

> How a Provider Plugin declares who can open each record in the Provider, and how Parable limits each reader to those rows.

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:

```bash theme={null}
curl "$PARABLE_API_URL/api/vendors/tenant-connector-instances/$CONNECTION_ID" \
  -H "Authorization: Bearer $PARABLE_API_TOKEN" \
  -H "X-Tenant: $PARABLE_WORKSPACE"
```

Look at `connector.visibilityModel`, `connector.defaultAudience`, and, for
each Tap in `connector.taps`, `access` and the `x-access*` keys in `schema`.
[Tap schemas](/connectors/tap-schemas.md) 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.

| Token | Who holds it |
| - | - |
| `user:<id>`, `group:<id>`, `team:<id>`, `role:<id>` | The principal with that ID in the Provider. A reader holds the token of each of their own accounts, and of every group, team, or role that the account belongs to, directly or through other groups. |
| `source_users` | Every active user of the Provider who is not a guest. |
| `domain:<domain>` | Every active user who is not a guest and whose primary email is in that domain. |
| `managers:<owner token>` | The direct managers of the row's owner, while the Workspace turns on manager reads. See [Managers read their reports' rows](#managers-read-the-rows-of-their-reports). |
| `anyone` | Every reader. Parable does not store this 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](/connectors/tap-projection.md#identity-keys), 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:

| The table | Anchors on |
| - | - |
| A root table whose only access fact is one `x-accessParent` reference | The parent row. Linear `issues` anchors on its row in `teams`. |
| A root table with an owner anchor | The owner field. Every row of one owner shares one anchor. |
| Any other anchored root table | Its own merge key. Each row has its own tokens. |
| A child table | The anchor of its root 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` | The Provider | Example |
| - | - | - |
| `record_acl` | Shares each record with people, groups, or domains. | Google Drive files, Parable Pulse activity |
| `container_acl` | Shows a record to the members of the container that holds it. | Linear teams |
| `source_users` | Has no rules for each record. Every active user sees everything. | Coupa |
| `unrestricted` | Shows the data to everyone. | A public status page |
| `not_modeled` | Has rules that Parable does not reproduce. | Most plugins |

`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](/connectors/taps.md#required-access).

`defaultAudience` is optional. It is the audience of every Tap that has no
anchor and no audience of its own:

| `defaultAudience` | Every row of such a Tap is open to |
| - | - |
| `source_users` | Every active user of the Provider who is not a guest. |
| `unrestricted` | Every reader. |

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:

| Role | The Tap's rows are | The Tap needs |
| - | - | - |
| `principals` | Users, groups, teams, or roles of the Provider. | An `x-accessPrincipal` field on the root. |
| `memberships` | A principal that belongs to a group, team, or role. | An `x-accessMemberOf` field. Its key covers both the container and the member. |
| `grants` | A principal or audience that can open a resource at a level. | An `x-accessResource` and an `x-accessSubject` field on the root. Its key covers both ends. |
| `resources` | Records whose access the grants decide. | A merge key. |

```json theme={null}
{
  "name": "teams",
  "access": { "roles": ["principals", "resources"] }
}
```

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

| Annotation | Value | Meaning |
| - | - | - |
| `x-accessPrincipal` | `{"kind": "user"\|"group"\|"team"\|"role", "grantsSelf"?: true}` | The row is a principal. With `grantsSelf`, the row is also a resource that admits its own members. |
| `x-accessPrincipalRef` | `{"by": "email", "primary"?: true}` | Another value that other Taps use to name this principal. Domain tokens come from the `primary` reference only. |
| `x-accessInactiveWhen` | A boolean, string, or number | The row gives nothing when the field equals the value. |
| `x-accessGuestWhen` | A boolean, string, or number | The user is a guest when the field equals the value. |
| `x-accessMemberOf` | `{"tap": "<tap>", "by": "id"\|"email"}` | The field names a group, team, or role that the member belongs to. |
| `x-accessManager` | `{"by": "id"\|"email", "idField"?, "when"?}` | The field names the direct manager of the row's user, in the same Provider. |
| `x-accessGrants` | `{}` | Each element of this array is a grant on the row that holds it. |
| `x-accessResource` | `{"tap": "<tap>", "idField"?: "<field>"}` | The record that a grant row is about. |
| `x-accessSubject` | `{"by": "id"\|"email"\|"domain", "kind"?: "<kind>"}` | Who a grant or membership is for. |
| `x-accessKind` | `{"map": {"<value>": "<kind>"}}` | The subject's kind for each value: `user`, `group`, `team`, `role`, `domain`, `source_users`, `anyone`, or `ignore`. |
| `x-accessLevel` | `{"map": {"<value>": "owner"\|"write"\|"comment"\|"read"\|"none"}}` | The Provider's role as a common level. |
| `x-accessDiscoverable` | `true` | When this boolean field is false, a `domain` or `anyone` grant on the row is link-only. |
| `x-accessExpiresAt` | `true` | The grant stops admitting a reader after this time. |
| `x-accessOwner` | `{"by": "id"\|"email", "kind"?: "user"\|"group"\|"any", "anchor"?: true, "managers"?: true}` | The field names the owner of the row. The owner can read the row. With `managers`, the owner's direct managers can read it too. |
| `x-accessParent` | `{"tap": "<tap>", "idField"?, "breakWhen"?, "whenAbsent"?}` | The row inherits the tokens of the row that the field names. |
| `x-accessVisibility` | `{"map": {"<value>": "grants_only"\|"source_users"\|"domain"\|"anyone"}}` | Adds an audience to the row, by the field's value. |

`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`:

```json theme={null}
{
  "id": { "x-accessPrincipal": { "kind": "user" }, "x-transformAccountId": true },
  "email": {
    "x-accessPrincipalRef": { "by": "email", "primary": true },
    "x-transformPersonEmail": true
  },
  "active": { "x-accessInactiveWhen": false },
  "guest": { "x-accessGuestWhen": true }
}
```

`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:

```json theme={null}
{
  "group_id": { "x-accessMemberOf": { "tap": "groups", "by": "id" } },
  "id": { "x-accessSubject": { "by": "id" } },
  "type": {
    "x-accessKind": { "map": { "USER": "user", "GROUP": "group", "CUSTOMER": "source_users" } }
  }
}
```

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:

```json theme={null}
{
  "permissions": { "type": "array", "x-accessGrants": {} }
}
```

```json theme={null}
{
  "type": {
    "x-accessKind": { "map": { "user": "user", "group": "group", "domain": "domain", "anyone": "anyone" } }
  },
  "emailAddress": { "x-accessSubject": { "by": "email" } },
  "domain": { "x-accessSubject": { "by": "domain" } },
  "role": {
    "x-accessLevel": {
      "map": {
        "owner": "owner",
        "organizer": "owner",
        "fileOrganizer": "write",
        "writer": "write",
        "commenter": "comment",
        "reader": "read"
      }
    }
  },
  "allowFileDiscovery": { "x-accessDiscoverable": true },
  "expirationTime": { "x-accessExpiresAt": true },
  "deleted": { "x-accessInactiveWhen": true }
}
```

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):

```json theme={null}
{
  "file_id": {
    "x-injectedByParent": true,
    "x-accessResource": { "tap": "shared_drive_files" }
  },
  "emailAddress": { "x-accessSubject": { "by": "email" } },
  "permission_key": { "x-syntheticField": true, "x-transformDedupKey": true }
}
```

### 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:

```json theme={null}
{
  "id": { "x-accessPrincipal": { "kind": "team", "grantsSelf": true } },
  "visibility": {
    "x-accessVisibility": {
      "map": { "public": "source_users", "private": "grants_only", "restricted": "grants_only" }
    }
  }
}
```

An issue inherits its team's tokens through one annotation:

```json theme={null}
{
  "team": { "x-accessParent": { "tap": "teams" } }
}
```

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:

```json theme={null}
{
  "parents": {
    "x-accessParent": {
      "tap": "shared_drive_files",
      "breakWhen": { "field": "inheritedPermissionsDisabled", "equals": true }
    }
  }
}
```

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:

```json theme={null}
{
  "team": { "x-accessParent": { "tap": "teams", "whenAbsent": "source_users" } }
}
```

`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:

```json theme={null}
{
  "primaryEmail": {
    "x-injectedByParent": true,
    "x-transformForeignKey": { "tap": "users", "idField": "primaryEmail" },
    "x-accessOwner": { "by": "email", "kind": "user", "anchor": true }
  }
}
```

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:

```json theme={null}
{
  "email": { "x-accessOwner": { "by": "email", "kind": "user" } }
}
```

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:

```json theme={null}
{
  "relations": {
    "x-accessManager": {
      "by": "email",
      "idField": "value",
      "when": { "field": "type", "equals": "manager" }
    }
  }
}
```

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:

```json theme={null}
{
  "userEmail": {
    "x-accessOwner": { "by": "email", "kind": "user", "anchor": true, "managers": true }
  }
}
```

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`:

```json theme={null}
{
  "expectedRevision": 4,
  "connectorId": "<connectorId of the Provider with reporting lines>",
  "peopleTold": true
}
```

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](https://docs.parable.work/protection/provider-policies#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:

```json theme={null}
{
  "name": "custom_report",
  "access": { "roles": ["resources"] }
}
```

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:

| Workspace state | A reader without All data access | A holder of All data access |
| - | - | - |
| Enforcement off, no Provider Policy | Every row | Every row |
| Enforcement on, no Provider Policy | No row | Every row, audited |
| A Provider Policy rule gives the reader's role **All rows** or **Rows where** | The rows that the rule keeps | Every row, audited |

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-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](https://docs.parable.work/protocols/sql/data-catalog/provider-pool#see-who-can-open-what)
says more about who can read them.

| Table | One row for each |
| - | - |
| `providers.access.principals` | User, group, team, or role of a Provider, with `inactive`, `guest`, and the identity of a user. |
| `providers.access.memberships` | Member and container, direct or through nested containers, with the `depth`. |
| `providers.access.grants` | Grant after inheritance: the resource, the `token`, the `level`, and the `cause` (`direct`, `owner`, `inherited`, or `audience`). |
| `providers.access.anchor_tokens` | Token that admits a reader to one anchor row, with `link_only` and `expires_at`. |
| `providers.access.reader_tokens` | Token that a sign-in email holds in a Provider. |
| `providers.access.manager_tokens` | Manager's email and the `managers:` token of a direct report, from a reporting line that `x-accessManager` declares. A manager holds these tokens only while the Workspace turns on manager reads. |
| `providers.access.freshness` | Provider: when Parable built its access, and the oldest sync that the build used. |

```sql theme={null}
-- Why can jordan.rivera@example.com read rows of this Provider?
SELECT connector_id, token
FROM providers.access.reader_tokens
WHERE email_norm = 'jordan.rivera@example.com'
ORDER BY connector_id, token
```

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](#managers-read-the-rows-of-their-reports).

[Provider Policies](https://docs.parable.work/protection/provider-policies#source-permissions)
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`:

```text theme={null}
Source permissions decide who reads this Provider table, and Parable can't
tell who may open its rows: <reason>. Nothing was read.
```

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](/connectors/taps.md#change-a-tap-schema-in-your-workspace)). 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.