Skip to content

Commit 85ec90d

Browse files
committed
docs(permissions): document the allowExport axis in permission-sets
The hand-written object-permission-bits table never picked up `allowExport` (#3553 only regenerated the auto-gen reference). Now that the bit is actually enforced server-side, document what it is: the tri-state, the most-permissive merge with an explicit deny in the middle, that it narrows read and never widens it, and that the 403 and the hidden client button are one decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PDbwCy9Jrc1chhR2vnAUos
1 parent 14be972 commit 85ec90d

1 file changed

Lines changed: 31 additions & 0 deletions

File tree

content/docs/permissions/permission-sets.mdx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,41 @@ export const SalesUser = definePermissionSet({
5050
| Permission | Description |
5151
|------------|-------------|
5252
| `allowCreate` / `allowRead` / `allowEdit` / `allowDelete` | CRUD on records the user can see |
53+
| `allowExport` | Bulk data egress — narrows read, see below |
5354
| `allowTransfer` / `allowRestore` / `allowPurge` | Lifecycle class (RBAC-gated ahead of the M2 operations) |
5455
| `viewAllRecords` | Read ALL records regardless of ownership (super-user read) |
5556
| `modifyAllRecords` | Edit ALL records regardless of ownership (super-user write) |
5657

58+
### `allowExport` — the export axis
59+
60+
Read and export are not the same privilege. Reading a record on screen and
61+
pulling the whole table down as a CSV differ in blast radius, which is why
62+
Salesforce ("Export Reports"), Dynamics ("Export to Excel"), NetSuite
63+
("Export Lists") and SAP (`S_GUI` 61) all carry a separate export permission.
64+
`allowExport` is that axis here: `export = list ∧ allowExport`.
65+
66+
It is a **tri-state**, and unset is not the same as `false`:
67+
68+
| Value | Meaning |
69+
|------------|-------------|
70+
| unset | Inherit read — anyone who can list can export. The default, so adding the key changes nothing for existing permission sets. |
71+
| `false` | Deny export while **keeping** read. The reason the axis exists. |
72+
| `true` | Explicitly granted. |
73+
74+
Across several permission sets the merge is most-permissive with an explicit
75+
deny in the middle: any set saying `true` wins, otherwise any set saying `false`
76+
wins, otherwise the bit stays unset and inherits read.
77+
78+
It narrows read — it never widens it. A set granting `allowExport: true` without
79+
a read grant exports nothing, and a `modifyAllRecords` super-user wildcard does
80+
**not** override an explicit per-object `allowExport: false`.
81+
82+
Enforcement is server-side: `GET /api/v1/data/:object/export` answers
83+
`403 EXPORT_NOT_PERMITTED` before it reads the first row. The same decision is
84+
published on `/me/permissions` as the object's effective `apiOperations`, which
85+
is what makes the client hide its Export button — the button and the refusal are
86+
one decision, not two.
87+
5788
## Access depth — `readScope` / `writeScope` (ADR-0057 D1)
5889

5990
An owner-scoped grant can widen the owner-match declaratively — the "see my

0 commit comments

Comments
 (0)