Skip to content

Commit 7c09621

Browse files
os-zhuangclaude
andauthored
feat(security)!: secure-by-default requireAuth flip + RBAC gates for transfer/restore/purge (#2561 P0) (#2562)
* feat(security)!: secure-by-default requireAuth flip + RBAC gates for transfer/restore/purge (#2561 P0) Two production-launch blockers from the authorization gap map (#2561): 1. requireAuth default flip (ADR-0056 D2, enforce stage) — BREAKING - spec: RestApiConfigSchema.requireAuth default(false) → default(true); rest-server normalizeConfig mirrors (?? true). Anonymous /data/* is now denied (401) unless the deployment explicitly opts out. - rest-api-plugin: boot warning now fires only for the EXPLICIT requireAuth:false opt-out, and reads the correct nested config path (the flat read previously warned even when requireAuth was on). - cli serve: keeps the deliberate carve-out — auth-less stacks get an explicit false (nothing could authenticate against them), warned. - verify harness: no override — every dogfood proof now runs on the platform default; anonymous-deny + public-form survival prove the flip (publicFormGrant was the pre-req that unblocked it). - conformance matrix: requireAuth-default-flip experimental → enforced. - REST/client unit tests that dispatch handlers anonymously opt out explicitly (they test routing mechanics, not the auth gate). 2. #1883 — destructive lifecycle ops RBAC gate (pre-mapped) - permission-evaluator: OPERATION_TO_PERMISSION now maps transfer→allowTransfer, restore→allowRestore, purge→allowPurge; modifyAllRecords super-user bypass covers the class. Ops still do not exist in ObjectQL (M2) — but there is no ungated window when they ship; unmapped destructive ops keep failing closed (ADR-0049). - spec: bits re-described [EXPERIMENTAL — not enforced] → [RBAC-gated; operation pending M2]; liveness ledger entries added. - unit tests: deny-without-bit / allow-with-bit / no-cross-leak / modifyAllRecords bypass. Verified: full turbo test suite green (129 tasks); browser-driven showcase run proves anonymous 401, authenticated 200, anonymous public form 200/201 with the data API still closed to the same session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT * docs: honesty caveats for pending transfer/restore/purge ops in permission docs The permissions-matrix cheatsheet and ObjectQL security protocol page listed allowTransfer/allowRestore/allowPurge without noting the operations do not exist yet (roadmap M2) — per the declared≠enforced honesty rule, both now carry the pending-M2 / gate-pre-mapped caveat (#1883). Also documents the modifyAllRecords bypass extension to the lifecycle class. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT * docs(adr): record FLS posture limitation + capability authoring-lint in ADR-0066 future refinements Two findings from the 2026-07 pre-launch authorization assessment that had no ADR home: - ⑧ runtime FLS is block-list + most-permissive union — undeclared fields are visible by default and a field-level deny cannot be expressed until the muting layer (⑦) covers field grants; - ⑨ capability strings need an authoring/publish-gate lint when the D1 registry lands (typos currently fail closed but undiscoverably). Companion engineering issue (not ADR material): #2565 — surface swallowed dbLoader failures in resolvePermissionSets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT * fix(security): address self-review findings on the requireAuth flip + destructive-op gates Correctness (record-level enforcement for the pre-mapped destructive ops): - security-plugin middleware: extend the write-scope stash, the #1994 pre-image RLS check, and the ADR-0055 controlled-by-parent write gate to cover transfer/restore/purge — the object-level RBAC bit was gated but the row-level defenses only listed update/delete/insert, so a granted user could have destroyed out-of-scope rows by id when the M2 ops ship. purge maps onto the delete RLS class, transfer/restore onto update. New unit test proves a not-owned purge is denied by the pre-image check. Callsite coverage for the default flip: - cli serve: carve-out now keys on (tierEnabled('auth') || hasAuthPlugin) — a stack mounting AuthPlugin explicitly under a minimal tier no longer gets an accidental fail-open override. - plugin-dev: mirror the serve carve-out — an auth-less dev stack gets an explicit requireAuth:false so the local data API isn't bricked. - spec: declare ObjectStackDefinitionSchema.api so the documented opt-out (and the enableProjectScoping/projectResolution/enforceProjectMembership knobs serve.ts already reads) survives defineStack strict parsing instead of being silently stripped. Verified: defineStack now returns the api key. - rest-api-plugin: warn only on an explicit opt-out; add a misplaced-key guard for a flat api.requireAuth (silently ignored under the deny default). - fix two in-tree tests that passed requireAuth at the wrong nesting. Cleanup: - derive the modifyAllRecords write-bypass key set from OPERATION_TO_PERMISSION + DESTRUCTIVE_OPERATIONS (module-level Set, no per-call array) so a future destructive op is covered automatically. Docs / provenance: - regenerate the auto-gen references (requireAuth + allow*{Transfer,Restore, Purge} describe text); CHANGELOG entry for the breaking flip; changeset migration note updated to the defineStack-level api opt-out + scope note. Pre-existing anonymous-posture gap on /meta, dispatcher /graphql, and raw hono /data (surfaces that never call enforceAuth) filed as #2567 — out of scope for this flip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f84f8d5 commit 7c09621

34 files changed

Lines changed: 435 additions & 145 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@objectstack/plugin-security": minor
3+
"@objectstack/spec": patch
4+
---
5+
6+
feat(security): pre-map `transfer`/`restore`/`purge` to their RBAC bits (#1883)
7+
8+
The permission evaluator now maps the destructive record-lifecycle operations
9+
to their spec permission bits (`transfer``allowTransfer`, `restore`
10+
`allowRestore`, `purge``allowPurge`) and extends the `modifyAllRecords`
11+
super-user bypass to cover them. The ObjectQL operations themselves are still
12+
roadmap M2 — but the gate now exists ahead of them: the moment such an
13+
operation is dispatched through the security middleware it is denied unless a
14+
resolved permission set grants the matching bit. Unmapped destructive
15+
operations continue to fail closed (ADR-0049). Spec descriptions updated from
16+
`[EXPERIMENTAL — not enforced]` to `[RBAC-gated; operation pending M2]`.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
"@objectstack/spec": major
3+
"@objectstack/rest": major
4+
"@objectstack/verify": patch
5+
"@objectstack/cli": patch
6+
---
7+
8+
feat(security)!: `api.requireAuth` now defaults to `true` — anonymous access to the data API is denied by default (ADR-0056 D2 flip)
9+
10+
**BREAKING.** The global `requireAuth` default flipped FROM `false` TO `true`
11+
(`RestApiConfigSchema.requireAuth` in `@objectstack/spec`, mirrored by
12+
`RestServer.normalizeConfig` in `@objectstack/rest`). Anonymous requests to
13+
the `/data/*` CRUD + batch endpoints are now rejected with HTTP 401 unless the
14+
deployment explicitly opts out. (Scope note: this gate covers the REST
15+
`/data/*` surface — the metadata read/write endpoints and the dispatcher
16+
GraphQL route have their own pre-existing anonymous posture, tracked
17+
separately; this flip does not change them.)
18+
19+
**Migration (one line):** a deployment that intentionally serves data publicly
20+
(demo / playground / kiosk) sets the flag on the stack config — now a declared
21+
`ObjectStackDefinitionSchema.api` field, so it survives `defineStack` strict
22+
parsing (previously an undeclared top-level `api` key was silently stripped):
23+
24+
```ts
25+
export default defineStack({
26+
//
27+
api: { requireAuth: false },
28+
});
29+
```
30+
31+
The REST plugin logs a boot warning for the explicit opt-out so a fail-open
32+
posture is always visible. A misplaced `api.requireAuth` at the plugin level
33+
(one nesting short) is now also called out with a boot warning instead of
34+
being silently ignored.
35+
36+
**What keeps working with no action:**
37+
38+
- **Share links** — validate their token, then read under a system context.
39+
- **Public forms** — self-authorizing via the declaration-derived
40+
`publicFormGrant` (create + read-back on the declared target object only);
41+
no `guest_portal` profile needed.
42+
- **Control plane**`/auth`, `/health`, `/discovery` are exempt.
43+
- **`objectstack serve` with an auth-less stack** — the CLI passes an explicit
44+
`requireAuth: false` for stacks whose tier set has no `auth` (nothing could
45+
authenticate against them), with the boot warning.

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed — Security: anonymous access denied by default (BREAKING) + destructive-op RBAC gates
11+
12+
- **`api.requireAuth` now defaults to `true`** (ADR-0056 D2 flip): anonymous
13+
requests to `/data/*` CRUD + batch endpoints are rejected with HTTP 401
14+
unless the deployment explicitly opts out with `api: { requireAuth: false }`
15+
(the REST plugin logs a boot warning for the explicit opt-out). Share-links,
16+
public forms (via the declaration-derived `publicFormGrant`), and the
17+
control plane (`/auth`, `/health`, `/discovery`) keep working with no
18+
action. `objectstack serve` passes an explicit `false` only for stacks with
19+
no `auth` tier (nothing could authenticate against them). Conformance row
20+
`requireAuth-default-flip``enforced`.
21+
- **`transfer` / `restore` / `purge` are RBAC-gated ahead of the ops** (#1883):
22+
the permission evaluator maps them to `allowTransfer` / `allowRestore` /
23+
`allowPurge` (with the `modifyAllRecords` super-user bypass), so when the
24+
M2 operations ship there is no ungated window. Unmapped destructive
25+
operations keep failing closed (ADR-0049).
26+
1027
### Added — ObjectUI one-month frontend scan + backend alignment summary
1128

1229
Scanning `../objectui` from 2026-05-08 through 2026-06-08 shows a broad Studio

content/docs/concepts/implementation-status.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ The `auth` service in `CoreServiceName` covers both **authentication** (identity
287287
- Client SDK supports bearer token header — but token validation requires the auth plugin
288288
- Auth route (`/auth/*`) only appears in Discovery when the auth plugin is registered
289289
- Fine-grained authorization (RLS, sharing, territory) is internal to the auth plugin
290-
- **Phase-1 RBAC enforcement is live end-to-end**: REST → ObjectQL → SecurityPlugin middleware now receives a populated `ExecutionContext` (userId, tenantId, roles, permissions). Default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the global tables that lack an `organization_id` column. The earlier `tenantField` indirection (RLS expressions written against an abstract `tenant_id` column then rewritten to the configured physical column at compile time) was removed — the placeholder, the column name, and `RLSUserContext.organization_id` are now the same name end-to-end. The legacy `objectql.registerTenantMiddleware` (hardcoded `where.tenant_id` injection that pre-dated SecurityPlugin) has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics now uses the same reusable read scope via `security.getReadFilter`, so dataset-bound dashboards/reports do not bypass RLS. Verified cross-organization isolation on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic still falls open by default** (the default-deny flip is release-gated); since ADR-0056 D2 the server logs a boot-time warning when `requireAuth` is unset, and public forms no longer depend on the fall-open — they carry a declaration-derived `publicFormGrant` (ADR-0056 Option A).
290+
- **Phase-1 RBAC enforcement is live end-to-end**: REST → ObjectQL → SecurityPlugin middleware now receives a populated `ExecutionContext` (userId, tenantId, roles, permissions). Default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the global tables that lack an `organization_id` column. The earlier `tenantField` indirection (RLS expressions written against an abstract `tenant_id` column then rewritten to the configured physical column at compile time) was removed — the placeholder, the column name, and `RLSUserContext.organization_id` are now the same name end-to-end. The legacy `objectql.registerTenantMiddleware` (hardcoded `where.tenant_id` injection that pre-dated SecurityPlugin) has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics now uses the same reusable read scope via `security.getReadFilter`, so dataset-bound dashboards/reports do not bypass RLS. Verified cross-organization isolation on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic is denied by default** (the ADR-0056 D2 default-deny flip landed: `requireAuth` defaults to `true`); an explicit `requireAuth: false` opt-out logs a boot-time warning, and public forms do not depend on any fall-open — they carry a declaration-derived `publicFormGrant` (ADR-0056 Option A).
291291
- **OWD / sharing-model enforcement is live and proven end-to-end (ADR-0056)**: `private`, `public_read`, `public_read_write`, and `controlled_by_parent` are enforced through `plugin-sharing` + `plugin-security` and verified by dogfood proofs over the real HTTP stack. `object.sharingModel` now accepts the canonical OWD vocabulary (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) alongside the legacy `read` / `read_write` / `full` spellings (D1). RLS owner policies resolve `current_user.email` in addition to `id` / `organization_id` / `roles` (#2054). Permission sets may declare `isDefault: true` to act as the app-declared fallback profile (D7).
292292

293293
---
@@ -429,7 +429,7 @@ cloud / EE.
429429
- [x] Organization-Wide Defaults / sharing model — `private`, `public_read`, `public_read_write`, and `controlled_by_parent` enforced via `plugin-sharing` + `plugin-security`, proven by dogfood over the real HTTP stack (ADR-0056). `object.sharingModel` accepts the canonical OWD vocabulary alongside the legacy `read` / `read_write` / `full` spellings (D1)
430430
- [x] Sharing Rule evaluator — owner + criteria rules re-evaluated on `afterInsert` / `afterUpdate` (`plugin-sharing/rule-hooks.ts`); recipients include user / role / group and configurable `role_and_subordinates` role-hierarchy widening (ADR-0056 D6)
431431
- [x] App-declarable default profile — a permission set may set `isDefault: true` to be the fallback profile for unassigned users (ADR-0056 D7)
432-
- [ ] Default-deny for anonymous traffic — boot-time warning lands when `requireAuth` is unset (ADR-0056 D2) and public forms self-authorize via `publicFormGrant` (Option A); the global default-deny **flip** is release-gated
432+
- [x] Default-deny for anonymous traffic — the global default-deny **flip landed** (ADR-0056 D2): `requireAuth` defaults to `true`, explicit `requireAuth: false` opt-outs warn at boot, and public forms self-authorize via `publicFormGrant` (Option A)
433433
- [ ] Studio RLS visual editor
434434
- [ ] Per-user×org permission cache
435435
- [ ] Audit UI / denied-access logging

content/docs/guides/cheatsheets/permissions-matrix.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ ObjectStack's `ObjectPermission` schema defines these boolean flags for object a
3030
| **Modify All** | `modifyAllRecords` | Edit/delete all records regardless of ownership | Full object access (bypass sharing) |
3131

3232
<Callout type="tip">
33-
**Super-user bypass:** When `modifyAllRecords` is set it satisfies write checks (`allowEdit`/`allowDelete`) 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`.
33+
**Super-user bypass:** When `modifyAllRecords` is set it satisfies write checks (`allowEdit`/`allowDelete`, and the lifecycle class `allowTransfer`/`allowRestore`/`allowPurge`) 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`.
34+
</Callout>
35+
36+
<Callout type="warn">
37+
**Lifecycle operations are pending:** the `transfer` / `restore` / `purge` operations do not exist in ObjectQL yet (roadmap M2). Their RBAC gate is already mapped in the permission evaluator — the moment the operations ship they are denied unless the matching flag (or `modifyAllRecords`) is granted — but authoring these flags today grants nothing (#1883).
3438
</Callout>
3539

3640
---

content/docs/guides/security.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description: "Complete guide to implementing enterprise-grade security in Object
77

88
Complete guide to implementing enterprise-grade security in ObjectStack with fine-grained permissions and data access controls.
99

10-
> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus explicit per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the two global tables that lack an `organization_id` column. RLS expressions, the physical column, and `RLSUserContext.organization_id` all use the same canonical name — there is no `tenantField` rewrite indirection (schemas with a different physical tenant column should fork the defaults). The legacy `objectql.registerTenantMiddleware` has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics also reuses the same read scope: `@objectstack/service-analytics` auto-bridges to `security.getReadFilter(object, context)` when the security service is registered, so dataset-bound dashboards/reports do not bypass RLS. End-to-end verified on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic still falls open by default** (the default-deny flip is release-gated); since ADR-0056 D2 the server logs a boot-time warning when `requireAuth` is unset, and public forms self-authorize via a declaration-derived `publicFormGrant` (ADR-0056 Option A — see [Public Forms](./public-forms)). Organization-Wide Defaults (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) and Sharing Rules (owner + criteria, with `role_and_subordinates` hierarchy widening) are live and dogfood-proven (ADR-0056); the Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and `concepts/implementation-status.mdx` for the latest matrix.
10+
> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus explicit per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the two global tables that lack an `organization_id` column. RLS expressions, the physical column, and `RLSUserContext.organization_id` all use the same canonical name — there is no `tenantField` rewrite indirection (schemas with a different physical tenant column should fork the defaults). The legacy `objectql.registerTenantMiddleware` has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics also reuses the same read scope: `@objectstack/service-analytics` auto-bridges to `security.getReadFilter(object, context)` when the security service is registered, so dataset-bound dashboards/reports do not bypass RLS. End-to-end verified on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic is denied by default** (the ADR-0056 D2 default-deny flip landed: `requireAuth` defaults to `true`); an explicit `requireAuth: false` opt-out logs a boot-time warning, and public forms self-authorize via a declaration-derived `publicFormGrant` (ADR-0056 Option A — see [Public Forms](./public-forms)). Organization-Wide Defaults (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) and Sharing Rules (owner + criteria, with `role_and_subordinates` hierarchy widening) are live and dogfood-proven (ADR-0056); the Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and `concepts/implementation-status.mdx` for the latest matrix.
1111

1212
## Table of Contents
1313

content/docs/protocol/objectql/security.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ Beyond the four CRUD flags, the schema also exposes lifecycle and super-user gra
100100
| Flag | Meaning |
101101
| --- | --- |
102102
| `allowCreate` / `allowRead` / `allowEdit` / `allowDelete` | Standard CRUD |
103-
| `allowTransfer` | Change record ownership |
104-
| `allowRestore` | Restore from trash (undelete) |
105-
| `allowPurge` | Permanently delete (hard delete / GDPR) |
103+
| `allowTransfer` | Change record ownership — *operation pending (M2); RBAC gate pre-mapped (#1883)* |
104+
| `allowRestore` | Restore from trash (undelete) — *operation pending (M2); RBAC gate pre-mapped (#1883)* |
105+
| `allowPurge` | Permanently delete (hard delete / GDPR) — *operation pending (M2); RBAC gate pre-mapped (#1883)* |
106106
| `viewAllRecords` | Read every record, bypassing sharing & ownership |
107107
| `modifyAllRecords` | Write every record, bypassing sharing & ownership |
108108

content/docs/references/api/rest-server.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ const result = BatchEndpointsConfig.parse(data);
192192
| **enableOpenApi** | `boolean` || Enable OpenAPI 3.1 spec & docs viewer endpoints |
193193
| **enableProjectScoping** | `boolean` || Enable project-scoped routing for data/meta/AI APIs |
194194
| **projectResolution** | `Enum<'required' \| 'optional' \| 'auto'>` || Project ID resolution strategy |
195-
| **requireAuth** | `boolean` || Reject anonymous requests on /data/* with HTTP 401 |
195+
| **requireAuth** | `boolean` || Reject anonymous requests on /data/* with HTTP 401 (secure-by-default; set false to serve data publicly) |
196196
| **documentation** | `Object` | optional | OpenAPI/Swagger documentation config |
197197
| **responseFormat** | `Object` | optional | Response format options |
198198

content/docs/references/security/permission.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ const result = FieldPermission.parse(data);
6868
| **allowRead** | `boolean` || Read permission |
6969
| **allowEdit** | `boolean` || Edit permission |
7070
| **allowDelete** | `boolean` || Delete permission |
71-
| **allowTransfer** | `boolean` || [EXPERIMENTAL — not enforced] Change record ownership |
72-
| **allowRestore** | `boolean` || [EXPERIMENTAL — not enforced] Restore from trash (Undelete) |
73-
| **allowPurge** | `boolean` || [EXPERIMENTAL — not enforced] Permanently delete (Hard Delete/GDPR) |
71+
| **allowTransfer** | `boolean` || [RBAC-gated; operation pending M2] Change record ownership |
72+
| **allowRestore** | `boolean` || [RBAC-gated; operation pending M2] Restore from trash (Undelete) |
73+
| **allowPurge** | `boolean` || [RBAC-gated; operation pending M2] Permanently delete (Hard Delete/GDPR) |
7474
| **viewAllRecords** | `boolean` || View All Data (Bypass Sharing) |
7575
| **modifyAllRecords** | `boolean` || Modify All Data (Bypass Sharing) |
7676
| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own|unit|unit_and_below|org |

0 commit comments

Comments
 (0)