Skip to content

Commit da28be0

Browse files
xuyushun441-sysos-zhuangclaude
authored
docs(security): document scope-depth (ADR-0057 D1) + open/paid boundary (ADR-0016) (#2118)
scope-depth (readScope/writeScope = own/own_and_reports/unit/unit_and_below/org) is the core declarative ERP authz axis, but it was undocumented in both the objectstack-data skill and the security guide — so an AI author wouldn't know it exists (and would fall back to hand-written RLS, exactly what ADR-0057 removes), and wouldn't know the open/paid boundary (using `unit` in open-source silently fails closed to `own`). - skills/objectstack-data: new "Access depth (scope-depth)" section before RLS — the 5-value table + a usage example + the ADR-0016 open-core boundary (hierarchy-relative scopes need @objectstack/security-enterprise; fail closed to `own` without it; defineStack requires ['hierarchy-security']). - guides/security.mdx: matching scope-depth section under Object Permission Levels. Doc/skill-only. check:skill-docs in sync; docs build green (1113 pages). showcase deliberately NOT touched — it's open-source, so a `unit` scope there would fail-closed to `own` and mislead; the showcase-scope-depth dogfood already proves the capability with a reference resolver. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 52853f3 commit da28be0

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

content/docs/guides/security.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,36 @@ export const SalesRepProfile: PermissionSet = {
118118
| `viewAllRecords` | View ALL records regardless of ownership |
119119
| `modifyAllRecords` | Edit ALL records regardless of ownership |
120120

121+
### Access depth — `readScope` / `writeScope` (ADR-0057 D1)
122+
123+
Beyond the boolean grants above, an owner-scoped object grant can carry a
124+
**`readScope` / `writeScope`** that widens the owner-match *declaratively* — the
125+
ERP "see my own / my reports / my unit / my unit and below / the whole org" axis,
126+
without hand-writing one RLS policy per object:
127+
128+
| Scope | Owner-match widens to |
129+
|------------|-------------|
130+
| `own` | the caller (baseline; unset = this) |
131+
| `own_and_reports` | the caller + their `sys_user.manager_id` report chain |
132+
| `unit` | owners in the caller's business unit (`sys_business_unit`) |
133+
| `unit_and_below` | the caller's BU + all descendant BUs (BFS) |
134+
| `org` | the whole tenant (≈ `viewAllRecords` / `modifyAllRecords`) |
135+
136+
```typescript
137+
objects: {
138+
account: { allowRead: true, allowEdit: true, readScope: 'unit_and_below', writeScope: 'own' },
139+
}
140+
```
141+
142+
It resolves to an `owner_id IN (…)` set at request time and AND-injects alongside
143+
RLS (no compiler change; ADR-0055). Sharing rules still widen on top.
144+
145+
> **Open-core boundary (ADR-0016).** `own` and `org` are open-source. The
146+
> hierarchy-relative scopes (`own_and_reports` / `unit` / `unit_and_below`) require
147+
> the paid `@objectstack/security-enterprise` plugin (BU-subtree + manager-chain
148+
> resolver); without it they **fail closed to `own`** (never fail-open), and
149+
> `defineStack` errors if a grant uses one without `requires: ['hierarchy-security']`.
150+
121151
### Standard Profiles
122152

123153
#### Sales Representative

skills/objectstack-data/SKILL.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,44 @@ permissions: {
424424
- Source: `node_modules/@objectstack/spec/src/security/permission.zod.ts`
425425
- Combine with `enable.apiMethods` to also restrict the HTTP surface.
426426

427+
### Access depth (scope-depth) — the ERP "see my unit / my unit and below" axis
428+
429+
For owner-scoped (`private`) objects, a per-object grant on a permission set can
430+
carry **`readScope` / `writeScope`** that *widens the owner-match declaratively*
431+
the ERP "my own / my reports / my unit / my unit and below / whole org" axis
432+
(ADR-0057 D1). It saves hand-writing one RLS policy per object.
433+
434+
```typescript
435+
// in a permission set's `objects` map
436+
objects: {
437+
account: {
438+
allowRead: true, allowEdit: true,
439+
readScope: 'unit_and_below', // see accounts owned by my BU + descendant BUs
440+
writeScope: 'own', // but only edit my own
441+
},
442+
}
443+
```
444+
445+
| Scope | Who you can see / write |
446+
|:--|:--|
447+
| `own` | `owner == me` (baseline; unset = this) |
448+
| `own_and_reports` | me + everyone below me on the `sys_user.manager_id` chain |
449+
| `unit` | owners in my business unit (`sys_business_unit`) |
450+
| `unit_and_below` | my BU + all descendant BUs (BFS) |
451+
| `org` | the whole tenant (≈ `viewAllRecords` / `modifyAllRecords`) |
452+
453+
Resolves at request time into an `owner_id IN (…)` set and AND-injects like RLS
454+
(no compiler change; ADR-0055). Sharing rules still widen on top.
455+
456+
> ⚠️ **Open-core boundary (ADR-0016).** `own` and `org` work in open-source. The
457+
> **hierarchy-relative** scopes — `own_and_reports` / `unit` / `unit_and_below`
458+
> need the **paid** `@objectstack/security-enterprise` plugin (BU-subtree +
459+
> manager-chain resolver). Without it they **fail closed to `own`** (never
460+
> fail-open), and `defineStack` errors if a grant uses one without
461+
> `requires: ['hierarchy-security']`. In an open-source app, author `own` / `org`
462+
> + explicit sharing rules; reach for `unit*` only when the enterprise plugin is
463+
> present.
464+
427465
### Row-Level Security (RLS)
428466

429467
The **enforced** RLS surface is a list of `rowLevelSecurity` policies on a

0 commit comments

Comments
 (0)