Skip to content

Commit f6ba134

Browse files
feat(spec,lint)!: refuse allowDelete: false beside modifyAllRecords: true (#25707)
Fixes #25575 Clause-②: no (narrowing) Metadata-protocol: narrowed Ruling-ref: 6377890121 (letter A, under 6377842908); retriage 6382861731 ## What changes `modifyAllRecords` stays the write bypass for edit, delete and transfer (the runtime and `MODIFY_ALL_WRITE_KEYS` are untouched). What an author can write narrows: - **Schema refusal.** `ObjectPermissionSchema` refuses an explicit `allowDelete: false` in an entry that sets `modifyAllRecords: true`: one `custom` issue at `objects.OBJECT.allowDelete` (wildcard `*` included). The message names the route for "edit every record, delete none". It rides the existing `checkScopeAgainstSuperUserBits` refinement (the #16870 precedent), so the response-side `EffectiveObjectPermissionSchema` stays tolerant. - **Omitted key materialises as `true`.** A raw-input stage in front of the schema writes `allowDelete: true` into an entry that sets `modifyAllRecords: true` and omits `allowDelete`. See "Mechanism assumption" below for why the refinement alone could not work. - **Lint arm.** `security-modify-all-delete-withheld` (error) in the security-posture family (`packages/lint/src/validate-security-posture.ts`), with an `os explain` entry. On parsed doors the schema refuses first, so the arm reaches the unparsed doors only (`os lint` on a raw object-literal config, a direct call), the same posture `security-owd-alias` documents. - **Data at rest.** ADR-0087 D2 conversion `permission-modify-all-allow-delete-false-to-true` (retired from the load path from day one, `retiredAfter: '18.1.0'`) plus the D3 entry `permission-modify-all-allow-delete-false-refused`. Stored rows and built artifacts inside their declared-floor window get the inert `false` rewritten to the `true` the evaluator always acted on. A source authored at the current surface is refused, not rewritten. - **Docs.** A measured recipe in `content/docs/permissions/sharing-rules.mdx` ("Recipe — edit every record, delete none"); one paragraph in `permissions-matrix.mdx`; two table rows in `permission-sets.mdx` / `permission-metadata.mdx` that said Modify All is "edit" now say "edit and delete". Generated references regenerated. ## Mechanism assumption (re-measured, and why the shape differs from "a parse-time refinement") `allowDelete` carries `.default(false)` and a refinement sees the parsed value, so `{ modifyAllRecords: true }` parsed to `{ allowDelete: false, modifyAllRecords: true }` on `origin/main` 6021b5e (measured on the built dist). A refinement alone would refuse that output the next time it is parsed: `definePermissionSet` output handed to `defineStack`, a build re-read at boot, a stored row saved again. So the explicitness is read off the raw input. The stage writes `true` into an omitted key (what `objectPermissionGrants` already answers), and the refinement then only ever sees an authored `false`. `parse(parse(x))` equals `parse(x)` is pinned. Data at rest carries the pair two ways. Authored: HotCRM's `src/sales/profiles/sales-manager.profile.ts:57` declares `crm_forecast` with `allowDelete: false` beside `modifyAllRecords: true`, and its built 17.1 artifact (the fixture `packages/metadata/src/__fixtures__/hotcrm-17.1-built-permissions.artifact.json`) carries that entry. Filled in: every earlier parse wrote `false` into an omitted `allowDelete`, so an artifact or stored row built from a source that omits it (the showcase's `showcase_ops`) carries the pair too. Hence the D2 conversion. The artifact door converts the 17.1 artifact (floor below `retiredAfter`) and refuses the same body when it claims a surface past `retiredAfter`; both pinned in `plugin-artifact-forward-conversion.test.ts`. ## Reading: can a criteria rule select every row, and does it grant edit without delete? Read: `checkDelete` in `plugin-sharing/src/sharing-service.ts` has no share branch (ADR-0111 D3), so an `edit` share opens update and never delete. `rule-criteria.ts` `isMatchAllCriteria` refuses or skips a criteria that lowers to an empty filter, and accepts any concrete field predicate. Measured on a booted in-process stack (`@objectstack/verify` `bootStack`, at 9f2f3fa, whose runtime code the later commits do not touch; a temporary test, not committed), the card's own `srm_material` shape: `public_read`, admin-owned rows, permission set `{ allowRead, allowCreate: false, allowEdit, allowDelete: false }` distributed by position `srm_buyer`, rule `{ type: 'criteria', condition: "record.id != ''", accessLevel: 'edit', sharedWith: { type: 'position', value: 'srm_buyer' } }`: | step | result | |---|---| | buyer updates an admin-owned row, before holding the position | 403 PERMISSION_DENIED | | `sys_user_position` row inserted | one `sys_record_share` row, `edit`, `source: rule`, to the buyer | | buyer updates the admin-owned row | 200, `category` written, owner unchanged | | buyer deletes it | 403 PERMISSION_DENIED; row still present | | admin inserts a new row; buyer updates / deletes it | 200 / 403 | | buyer creates a row | 403 | Control, same stack with `condition: 'true'`: boot logs `declared rule not served … reason: match-all-criteria`, no share row, and the buyer holding the position is refused the same update at the record ("You do not have access to this record"). So the update above came from the share. A literal `true` is not refused at parse; it is never served. The docs and every prescription say so. ## Producers Measured on 6021b5e before the change: no permission set in this repository (`examples/**`, `packages/platform-objects`, plugins, services) authors the pair. Two set `modifyAllRecords: true` and omit `allowDelete`, and now parse to `allowDelete: true` with no edit: `examples/app-showcase/src/security/permission-sets.ts` `showcase_ops.showcase_announcement` and `packages/verify/src/parent-write-reach.ts` `modifyAll`. The platform sets (`admin_full_access`, `organization_admin`) write `allowDelete: true` explicitly. Raw evaluator fixtures that declare the pair (`plugin-security` `security-plugin.test.ts`, "should allow edit/delete via modifyAllRecords") are never parsed and keep pinning the runtime semantics the ruling keeps. **Downstream producer that authors the pair: HotCRM.** `src/sales/profiles/sales-manager.profile.ts:57` (`sales_manager.crm_forecast`). Its deployed 17.1 artifact is converted at load (the evaluator's behaviour unchanged); its source build is refused at `objects.crm_forecast.allowDelete` once it upgrades, and the fix is the ruling's choice (drop `allowDelete: false` to keep Modify All, or replace `modifyAllRecords` with the edit-share route). The seat files the HotCRM card at merge. ## Tests and gates **Patch round 1, at head `0a8846cd6`** (strings and comments only: the D3 `reason` without a tracker id, the D2 docblock and fixture comment, a test comment). The CI red was reproduced first on `c5b702ced` (cli integration `migrate-meta-engine-guidance.test.ts`, 1 failed). At `0a8846cd6`, each under the lock with VERDICT exit 0: cli `test` (unit + integration) 499 files / 6286 tests; spec 719 / 21788 and `test:repo` 79 / 1269; lint 161 / 7274; metadata 64 / 906. `dispatch-gates --commands`: 116 run, 0 NOT-MEASURED. Ablations, each restored blob-identical: (a) disabling the refinement arm turns 2 of 101 `permission.test.ts` pins red, as predicted; (b) making the materialise stage return its input unchanged turns 2 of 101 red, as predicted. The dogfood battery was not re-run: the push changes strings and comments only. `retiredAfter: '18.1.0'` still names the latest published `@objectstack/spec`. Round 1, at head `c5b702ced`: - **Derived gates** (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`): 116 derived, 116 run, every exit captured before any pipe; `--ran` with exit codes answers "116 run, 0 NOT-MEASURED (a DERIVED zero)". One gate (`check-comment-mask-corpus`) first went red on ENOENT for a temporary bundle the concurrent dogfood run wrote into `examples/app-showcase`; re-run alone it is green (10121 files, 0 disagree). `check-plugin-teardown-shape --self-test` needed its pinned fixture commit fetched in this shallow clone; green after. - **Package suites** (lock VERDICT exit 0 each): spec `test` 719 files / 21788 tests, spec `test:repo` 79 / 1269, spec `typecheck`; lint `test` 161 / 7274 and `typecheck`; metadata `test` 64 / 906 and `typecheck`. - **Consumers of the contract** (each green): metadata-core 19 / 800; runtime 441 / 7080 and `test:repo` 3 / 854; core 118 / 2757 and `test:repo` 6 / 82; verify 46 / 393; metadata-protocol 263 / 21475; rest 320 / 6406 and `test:repo` 5 / 280; plugin-security 222 / 4628; cli `--project unit` 328 / 4816 (integration layer declared to CI: the diff touches no CLI spawn entry). - **Dogfood battery** (`(narrowing)` arm): 295 passed, 3 skipped files; 2520 passed, 18 skipped tests; VERDICT command-exit 0. - `check:dev-prereqs`: green (local-only). ## Acceptance notes - `GetObjectPermissionsResponseSchema.permissions` uses the authoring `ObjectPermissionSchema` (not the effective one), so it inherits the refusal, as it already inherits the #16870 scope refusals. Noted, not changed. - `os migrate meta --from 17` replays the retired D2 entry, so a source carrying the pair would be rewritten to `allowDelete: true` (the runtime behaviour) with the D3 entry listed beside it as the judgment owed. Recorded in the D3 entry's `conversionIds`. - The no-share control above also shows the platform's write floor on a `public_read` object: without the share, the update is refused at the record, not at the object CRUD check. _Body edited by the `domain:spec` seat 2 after patch round 1, with the dev's proposed text from `6390006109`, plus the round's readings._ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017Eg8KpUtdKh6r8YTPjbhod --- _Generated by [Claude Code](https://claude.ai/code/session_017Eg8KpUtdKh6r8YTPjbhod)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 04273b9 commit f6ba134

18 files changed

Lines changed: 593 additions & 20 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': minor
4+
---
5+
6+
feat(spec,lint)!: a permission set can no longer declare `allowDelete: false` beside `modifyAllRecords: true`
7+
8+
`modifyAllRecords` (Modify All Records) is the write bypass for edit, delete and transfer, as Salesforce's Modify All is. The permission evaluator grants delete on the bypass bit and never reads `allowDelete`, so an object-permission entry declaring `allowDelete: false` beside it parsed, was stored, and let its holder delete every record. The runtime does not change. What changes is what an author can write:
9+
10+
- `ObjectPermissionSchema` refuses an explicit `allowDelete: false` in an entry that sets `modifyAllRecords: true`, with one `custom` issue at `objects.<object>.allowDelete` (the wildcard `*` entry included). The message names the route for "edit every record, delete none".
11+
- An entry that sets `modifyAllRecords: true` and omits `allowDelete` now parses to `allowDelete: true` instead of `false`. That is what the evaluator already granted, and it keeps the parse idempotent: the output of `definePermissionSet` still parses when `defineStack`, a build or a save parses it again.
12+
- `os lint` reports the pair as `security-modify-all-delete-withheld` (error) on a raw config that has not been through the schema. `os explain security-modify-all-delete-withheld` prints the route.
13+
- The response-side `EffectiveObjectPermissionSchema` is unchanged and still accepts the pair.
14+
15+
"Edit every record, delete none" is a `criteria` sharing rule with `accessLevel: 'edit'` shared with the position that holds the set, beside `allowEdit: true` and `allowDelete: false` with no `modifyAllRecords`. An edit share opens update and never delete (ADR-0111 D3). The object needs an owner field and an owner-write default (`private` or `public_read`), and the rule's `condition` must be a field predicate every row satisfies, such as `record.id != ''`: a literal `true` lowers to an empty filter, which the sharing service never serves (it logs the rule as not served at boot).
16+
17+
Stored rows and older built artifacts: every earlier parse filled an omitted `allowDelete` with `false`, so the pair also sits where nobody wrote it. The ADR-0087 D2 conversion `permission-modify-all-allow-delete-false-to-true` rewrites it to `true` when a stored row is rehydrated and when a built artifact inside its declared-floor window is loaded. That changes no access decision. It is retired from the load path, so a source that still declares the pair is refused, not rewritten. The D3 entry `permission-modify-all-allow-delete-false-refused` carries the judgment for `os migrate meta`.
18+
19+
Clause-②: no (narrowing) — the object-permission schema refuses a shape it used to accept (`allowDelete: false` beside `modifyAllRecords: true`). Nothing is widened.
20+
21+
Metadata-protocol: narrowed (an object-permission entry may no longer declare `allowDelete: false` beside `modifyAllRecords: true`)
22+
23+
Ruling-ref: 6377890121 (letter A, under 6377842908); retriage 6382861731
24+
25+
<!-- adr-0087: registered permission-modify-all-allow-delete-false-refused, permission-modify-all-allow-delete-false-to-true -->
26+
27+
**BREAKING** for an author who declared the pair.
28+
29+
- **Before.** `{ allowEdit: true, allowDelete: false, modifyAllRecords: true }` parsed, and the holder could delete every record.
30+
- **Now.** The entry is refused at `objects.<object>.allowDelete`.
31+
- **Fix.** If the set should delete every record, delete `allowDelete` from the entry. If it should edit records it does not own but delete none, remove `modifyAllRecords` and add a sharing rule: `{ type: 'criteria', name, object, condition: "record.id != ''", accessLevel: 'edit', sharedWith: { type: 'position', value: '<position>' } }`.

‎content/docs/permissions/permission-metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ objects: {
9494
| `allowDelete` | Soft-delete records |
9595
| `allowTransfer` | Transfer record ownership |
9696
| `viewAllRecords` | View all records regardless of sharing rules |
97-
| `modifyAllRecords` | Edit all records regardless of sharing rules |
97+
| `modifyAllRecords` | Edit and delete all records regardless of sharing rules; `allowDelete: false` beside it is refused |
9898

9999
> **Retired:** the former `allowRestore` / `allowPurge` keys were removed
100100
> (#12497, ADR-0049) — the `restore` / `purge` operations they claimed to gate

‎content/docs/permissions/permission-sets.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const SalesUser = definePermissionSet({
5353
| `allowExport` | Bulk data egress — an opt-in grant on top of read, see below |
5454
| `allowTransfer` | Lifecycle class: change record ownership — enforced today via the `owner_id` guard (#3004). The former `allowRestore` / `allowPurge` keys were retired (#12497, ADR-0049 — the operations they claimed to gate do not exist yet) and return with the M2 lifecycle batch (#1883) |
5555
| `viewAllRecords` | Read ALL records regardless of ownership (super-user read) |
56-
| `modifyAllRecords` | Edit ALL records regardless of ownership (super-user write) |
56+
| `modifyAllRecords` | Edit and delete ALL records regardless of ownership (super-user write); `allowDelete: false` beside it is refused |
5757

5858
### `allowExport` — the export axis
5959

‎content/docs/permissions/permissions-matrix.mdx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ ObjectStack's `ObjectPermission` schema defines these boolean flags for object a
3030
<Callout type="tip">
3131
**Super-user bypass:** When `modifyAllRecords` is set it satisfies write checks (`allowEdit`/`allowDelete`, and the lifecycle bit `allowTransfer`) on any record; `viewAllRecords` (or `modifyAllRecords`) satisfies `allowRead` on any record — both bypass ownership and sharing. See `packages/plugins/plugin-security/src/permission-evaluator.ts`.
3232

33+
**Modify All includes delete.** An entry that sets `modifyAllRecords: true` and
34+
omits `allowDelete` parses to `allowDelete: true`, and an explicit
35+
`allowDelete: false` beside it is refused at `objects.<object>.allowDelete`,
36+
because the evaluator would never read it. To let a set edit every record but
37+
delete none, use the [edit-share recipe](/docs/permissions/sharing-rules#recipe--edit-every-record-delete-none).
38+
3339
**The one exception is `allowExport`.** Neither super-user bit confers it: a
3440
principal with View/Modify All Data may read every record and still be refused a
3541
bulk copy of them. Export is an opt-in grant that must be stated explicitly —

‎content/docs/permissions/sharing-rules.mdx‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,55 @@ grants it had issued rather than re-granting the whole object. If you really
347347
do want everyone to read every record of an object, that is the object's
348348
organization-wide default (`sharingModel`), not a sharing rule.
349349

350+
The gate judges the **lowered filter**, not the intent. A literal
351+
`condition: 'true'` lowers to an empty filter, so the rule is not served (a
352+
declared rule is logged at boot as `match-all-criteria`). A field predicate
353+
that every row happens to satisfy, such as `record.id != ''`, is an ordinary
354+
rule and is served. Use one only where the OWD would grant too much, as in the
355+
recipe below.
356+
357+
### Recipe — edit every record, delete none
358+
359+
`modifyAllRecords` cannot express this: Modify All Records includes delete, and
360+
the schema refuses `allowDelete: false` beside `modifyAllRecords: true`. A
361+
sharing rule can, because an `edit` share opens update and never delete
362+
(ADR-0111 D3). On a `public_read` object whose rows an administrator owns:
363+
364+
```typescript
365+
// The permission set: edit and read, no Modify All, delete withheld.
366+
objects: {
367+
srm_material: { allowRead: true, allowCreate: false, allowEdit: true, allowDelete: false },
368+
},
369+
370+
// The position distributes the set; the rule shares every row with it at `edit`.
371+
definePosition({ name: 'srm_buyer', label: 'Buyer', permissionSets: ['srm_buyer_set'] });
372+
373+
defineSharingRule({
374+
type: 'criteria',
375+
name: 'srm_material_edit_buyers',
376+
object: 'srm_material',
377+
condition: "record.id != ''",
378+
accessLevel: 'edit',
379+
sharedWith: { type: 'position', value: 'srm_buyer' },
380+
});
381+
```
382+
383+
Measured on a booted in-process stack (`@objectstack/verify`): a member who
384+
holds `srm_buyer` updates a row the administrator owns, and their delete of
385+
that row is refused `403 PERMISSION_DENIED`; a row inserted after the rule is
386+
shared the same way; holding the position without a served rule, the same
387+
update is refused at the record. The rule's grants follow the position's
388+
holders and the object's writes, so nobody re-runs it by hand. Field-level
389+
security narrows which fields the edit may touch.
390+
391+
Three conditions, each one a reason the share would enforce nothing:
392+
393+
- The object needs an **owner field** and an owner-write OWD (`private` or
394+
`public_read`). Under `public_read_write` every member with `allowEdit`
395+
already edits every row, so the share adds nothing.
396+
- The condition must be a **field predicate** (see above).
397+
- The permission set still grants `allowEdit`; a share widens rows, never verbs.
398+
350399
> **Retired shapes.** The pre-ADR-0090 `group` recipient was renamed to
351400
> `team`, and the `guest` recipient was removed — anonymous access is served
352401
> by the [public-form grant](/docs/permissions/authorization) and share

‎content/docs/references/api/protocol.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,7 +1196,7 @@ This schema accepts one of the following structures:
11961196
| **allowRestore** | `never` | optional | [REMOVED] `objects.<object>.allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
11971197
| **allowPurge** | `never` | optional | [REMOVED] `objects.<object>.allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
11981198
| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) |
1199-
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. |
1199+
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. |
12001200
| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org |
12011201
| **writeScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Write depth: own\|unit\|unit_and_below\|org |
12021202
| **apiOperations** | `Enum<'get' \| 'list' \| 'create' \| 'update' \| 'delete' \| 'upsert' \| 'bulk' \| …>[]` | optional | Server-resolved effective API operations for this object. Present only when the object tightens exposure via apiMethods; absent = default-allow. The frontend renders this effective set, never the raw whitelist. Vocabulary is the EFFECTIVE ApiOperation set (six primitives + eight derived verbs), not the authored six-value ApiMethod enum. |
@@ -1648,7 +1648,7 @@ One changed key path of the pending draft, and the last write that changed it.
16481648
| **allowRestore** | `never` | optional | [REMOVED] `objects.<object>.allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
16491649
| **allowPurge** | `never` | optional | [REMOVED] `objects.<object>.allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
16501650
| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) |
1651-
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. |
1651+
| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. |
16521652
| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org |
16531653
| **writeScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Write depth: own\|unit\|unit_and_below\|org |
16541654

0 commit comments

Comments
 (0)