You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(permissions): rewrite content/docs/permissions/* to the Permission Model v2 vocabulary (ADR-0090) (#2717)
- roles.mdx → positions.mdx: flat positions (岗位), assignment BU anchor,
built-in identity positions, everyone/guest audience anchors; the
hierarchy narrative moves to business units.
- profiles.mdx → migration tombstone: the Profile concept was removed
(D2); maps each former use to everyone-anchor bindings, isDefault
suggestions, and position-bound sets.
- permission-sets.mdx: rewritten as the single capability container —
union semantics, access depth (moved here from profiles), capabilities,
built-in sets (additive member_default baseline), governed assignment
tables, isDefault suggestion, adminScope delegated administration,
package provenance.
- sharing-rules.mdx: OWD default corrected to fail-closed private (D1 —
the page previously documented the pre-v2 fail-open default), canonical
four values only, externalSharingModel dial (D11), recipient types
position / unit_and_subordinates / team.
- authorization.mdx: position vocabulary, D1/D11 in the enforcement
chain, delegated-admin gate in anti-escalation, new explain-engine
section (D6), D7 linter + access-matrix snapshot added to governance,
ADR-0090 in the index.
- permissions-matrix.mdx: role-hierarchy section replaced with
business-unit hierarchy & positions; isProfile removed from samples;
position recipients.
- permission-metadata.mdx: isProfile → isDefault/adminScope in the field
table; union-semantics section replaces Profile-vs-Set;
current_user.positions.
- index.mdx: five-concepts overview, v2 implementation-status callout,
best practices and example updated.
- access-recipes.mdx / field-level-security.mdx: link + heading fixes.
Authoritative reference: docs/design/permission-model.md; decision
record: ADR-0090 (P1 #2697, P2 #2708, P3 #2711, P4 #2716).
Claude-Session: https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/permissions/access-recipes.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -37,7 +37,7 @@ definePermissionSet({
37
37
});
38
38
```
39
39
40
-
-**"My own"** vs **"my team's"** vs **"the org's"** is the RLS *depth* axis (`readScope` / `writeScope`: `own | own_and_reports | unit | unit_and_below | org`, ADR-0057). A manager set uses `unit`; a rep uses `own`. The hierarchy-relative scopes (`own_and_reports` / `unit` / `unit_and_below`) need the enterprise hierarchy resolver and **fail closed to `own`** without it — see [Access depth](/docs/permissions/profiles#access-depth--readscope--writescope-adr-0057-d1).
40
+
-**"My own"** vs **"my team's"** vs **"the org's"** is the RLS *depth* axis (`readScope` / `writeScope`: `own | own_and_reports | unit | unit_and_below | org`, ADR-0057). A manager set uses `unit`; a rep uses `own`. The hierarchy-relative scopes (`own_and_reports` / `unit` / `unit_and_below`) need the enterprise hierarchy resolver and **fail closed to `own`** without it — see [Access depth](/docs/permissions/permission-sets#access-depth--readscope--writescope-adr-0057-d1).
41
41
- Grants combine **most-permissively** across a user's sets; the tenant-isolation policy `AND`s on top; the superuser bypass (`viewAllRecords` / `modifyAllRecords`) short-circuits RLS where the object's posture allows it (ADR-0066).
42
42
- For genuinely sensitive objects, set `access: { default: 'private' }` so they are **not** covered by blanket wildcard grants — access needs an explicit grant.
43
43
@@ -62,7 +62,7 @@ Keeping capability, assignment and requirement decoupled means resources stay st
maintained by admins in Setup — and, since ADR-0090 D12, a **governed**
35
+
surface: writing them requires tenant-level administration or a covering
36
+
delegated `adminScope`, never just CRUD on the tables.
34
37
3.**Requirement** — *what a resource needs* — an object / field / action
35
38
references a capability as a contract. A resource never bakes in "who",
36
39
only "what is required".
@@ -43,17 +46,22 @@ site — the file you read when behavior surprises you.
43
46
| # | Gate | What it decides | Enforcement site | Failure direction |
44
47
|---|---|---|---|---|
45
48
| 1 |**Anonymous deny**| No identity → HTTP 401 on `/data/*`. **Default-on** (ADR-0056 D2): public data serving requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Control plane (`/auth`, `/health`, `/discovery`) is exempt; share-links validate their token then read as SYSTEM. |`packages/rest/src/rest-server.ts``enforceAuth` (default in `packages/spec/src/api/rest-server.zod.ts`) | fail-closed |
46
-
| 2 |**Public-form grant**| An anonymous form submission carries a declaration-derived `publicFormGrant` authorizing ONLY create + read-back on the form's declared target object — never anything else (ADR-0056 Option A). No `guest_portal` profile needed. |`packages/plugins/plugin-security/src/security-plugin.ts` (ObjectQL middleware) | scope-limited allow |
49
+
| 2 |**Public-form grant**| An anonymous form submission carries a declaration-derived `publicFormGrant` authorizing ONLY create + read-back on the form's declared target object — never anything else (ADR-0056 Option A). No guest-portal configuration needed (anonymous principals hold the `guest` position). |`packages/plugins/plugin-security/src/security-plugin.ts` (ObjectQL middleware) | scope-limited allow |
47
50
| 3 |**Object CRUD**|`allowRead/Create/Edit/Delete` (+ the destructive lifecycle class `allowTransfer/Restore/Purge`, gated ahead of the M2 operations — #1883) resolved across the caller's permission sets. |`packages/plugins/plugin-security/src/permission-evaluator.ts``checkObjectPermission`| fail-closed 403 |
48
-
| 4 |**OWD / sharing**| Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`), manual record shares, criteria sharing rules (owner-type rules are declared but seed-skipped — [not enforced](/docs/permissions/sharing-rules#owner-based-sharing-rules)), business-unit hierarchy widening (ADR-0057 D5: scope-depth hierarchy lives on `sys_business_unit`, not roles). |`packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts`| owner-only baseline|
51
+
| 4 |**OWD / sharing**| Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`; **unset or unknown ⇒ `private`, fail-closed** — ADR-0090 D1) plus the external dial (`externalSharingModel`, ADR-0090 D11), manual record shares, criteria sharing rules (owner-type rules are declared but seed-skipped — [not enforced](/docs/permissions/sharing-rules#owner-based-sharing-rules)), business-unit hierarchy widening (ADR-0057 D5: scope-depth hierarchy lives on `sys_business_unit`, not positions). |`packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts`|fail-closed to owner-only |
49
52
| 5 |**Row-level security**| CEL predicates (`using` read filter, `check` write post-image) compiled into the query. If **no** applicable policy compiles, the result is a deny-all sentinel (fail-closed); an uncompilable policy alongside compilable ones is excluded from the OR-union **with a logged warning** — exclusion can only narrow access, never widen it. Tenant isolation is a wildcard RLS rule AND-ed on top. |`packages/plugins/plugin-security/src/rls-compiler.ts` + `security-plugin.ts`| fail-closed |
50
53
| 6 |**Field-level security**| Read mask (strip non-readable fields) + write deny per `fields` rules. Caller queries that **filter, sort, group, or aggregate by** a non-readable field are rejected outright (HTTP 403, `field_predicate_denied`) — masking only the output would leave row presence as a value oracle. RLS-injected predicates are exempt (they run after the guard and may reference hidden fields like `owner_id`). |`packages/plugins/plugin-security/src/field-masker.ts` + `predicate-guard.ts`| fail-closed on predicates; see posture note below |
51
54
52
55
Two orthogonal identity-layer gates run before all of this: the ADR-0069
53
56
**authentication-policy gate** (password expiry / enforced MFA blocks a gated
54
57
session from protected resources while keeping remediation reachable), and
55
-
**anti-escalation** (RBAC tables are read-only for `organization_admin`, so an
56
-
org admin cannot self-grant platform admin).
58
+
**anti-escalation** — RBAC tables are read-only for `organization_admin`, and
59
+
since ADR-0090 D12 every RBAC-table write is additionally checked by the
60
+
**delegated-admin gate**: tenant admins pass, delegates are confined to their
61
+
`adminScope` (BU subtree + assignable-set allowlist, no self-escalation,
62
+
strict containment for scope grants), and everyone else is denied. The
63
+
`everyone`/`guest` audience-anchor bindings reject high-privilege sets at the
64
+
data layer for every caller.
57
65
58
66
## Combination semantics (the fixed order)
59
67
@@ -123,7 +131,7 @@ one of two doors, each writing only what it owns:
123
131
resolve).
124
132
-**Environment-admin door** (`metadata-admin`) — the cross-package all-objects
125
133
matrix plus subject **assignment** (`sys_user_permission_set`,
126
-
`sys_role_permission_set`), edited **live** (config). It owns env-authored
134
+
`sys_position_permission_set`), edited **live** (config). It owns env-authored
127
135
sets (`managedBy``platform`/`user`) and assignments — not package sets.
128
136
-**Data-layer write gate** — the security middleware **refuses** any admin-door
129
137
write to a `managedBy:'package'``sys_permission_set` row, and refuses a
@@ -141,14 +149,46 @@ one of two doors, each writing only what it owns:
141
149
|---|---|---|
142
150
| 1 · Package development | Zod-validated authoring; roles / sharingRules / permissions seeded at boot with provenance | ADR-0057 D6, ADR-0086 D5, ADR-0049/0078 gates |
0 commit comments