ObjectStackObjectStack

Permission

Permission protocol schemas

Entity (Object) Level Permissions Defines CRUD + VAMA (View All / Modify All) + Lifecycle access.

Refined with enterprise data lifecycle controls:

  • Transfer (Ownership change)
  • Restore (Soft delete recovery)
  • Purge (Hard delete / Compliance)

Source: packages/spec/src/security/permission.zod.ts

TypeScript Usage

import { AdminScopeSchema, EffectiveObjectPermissionSchema, FieldPermissionSchema, ObjectAccessScopeSchema, ObjectPermissionSchema, PermissionSetSchema } from '@objectstack/spec/security';
import type { AdminScope, EffectiveObjectPermission, FieldPermission, ObjectAccessScope, ObjectPermission, PermissionSet } from '@objectstack/spec/security';

// Validate data
const result = AdminScopeSchema.parse(data);

AdminScope

Properties

PropertyTypeRequiredDescription
businessUnitstring[ADR-0090 D12] Delegation boundary: sys_business_unit.name of the subtree root
includeSubtreebooleanoptional (default: true)Cover descendant business units too (default true)
manageAssignmentsbooleanoptional (default: false)Manage user↔position assignments within the subtree
manageBindingsbooleanoptional (default: false)Manage position↔permission-set bindings within the subtree
authorEnvironmentSetsbooleanoptional (default: false)Author environment-owned permission sets
assignablePermissionSetsstring[]optional (default: [])Allowlist of permission-set names the delegate may hand out

EffectiveObjectPermission

Properties

PropertyTypeRequiredDescription
allowCreatebooleanoptional (default: false)Create permission
allowReadbooleanoptional (default: false)Read permission
allowEditbooleanoptional (default: false)Edit permission
allowDeletebooleanoptional (default: false)Delete permission
allowExportbooleanoptionalUser-level export axis over read (opt-in grant). true = export granted (still bounded by read); unset/false = no export. Merged most-permissively like the CRUD bits; NOT implied by viewAllRecords/modifyAllRecords.
allowTransferbooleanoptional (default: false)[RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id)
allowRestoreneveroptional[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; apply them by hand.
allowPurgeneveroptional[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; apply them by hand.
viewAllRecordsbooleanoptional (default: false)View All Data (Bypass Sharing)
modifyAllRecordsbooleanoptional (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.
readScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Read depth: own|unit|unit_and_below|org
writeScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Write depth: own|unit|unit_and_below|org
apiOperationsEnum<'get' | 'list' | 'create' | 'update' | 'delete' | 'upsert' | 'bulk' | 'aggregate' | 'history' | 'search' | 'restore' | 'purge' | 'import' | 'export'>[]optionalServer-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.

FieldPermission

Properties

PropertyTypeRequiredDescription
readablebooleanoptional (default: true)Field read access
editablebooleanoptional (default: false)Field edit access

ObjectAccessScope

Allowed Values

  • own
  • own_and_reports
  • unit
  • unit_and_below
  • org

ObjectPermission

Properties

PropertyTypeRequiredDescription
allowCreatebooleanoptional (default: false)Create permission
allowReadbooleanoptional (default: false)Read permission
allowEditbooleanoptional (default: false)Edit permission
allowDeletebooleanoptional (default: false)Delete permission
allowExportbooleanoptionalUser-level export axis over read (opt-in grant). true = export granted (still bounded by read); unset/false = no export. Merged most-permissively like the CRUD bits; NOT implied by viewAllRecords/modifyAllRecords.
allowTransferbooleanoptional (default: false)[RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id)
allowRestoreneveroptional[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; apply them by hand.
allowPurgeneveroptional[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; apply them by hand.
viewAllRecordsbooleanoptional (default: false)View All Data (Bypass Sharing)
modifyAllRecordsbooleanoptional (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.
readScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Read depth: own|unit|unit_and_below|org
writeScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Write depth: own|unit|unit_and_below|org

PermissionSet

Properties

PropertyTypeRequiredDescription
namestringPermission set name, unique per organization (lowercase snake_case)
labelstringoptionalDisplay label
descriptionstringoptionalHuman-readable description shown in Setup (persisted as sys_permission_set.description)
packageIdstringoptional[ADR-0086 D3] Owning package id for a package-shipped set (absent = env-authored)
managedByEnum<'package' | 'platform' | 'user'>optional[ADR-0086 D3] Record provenance: package (upgrade-owned metadata) vs platform/user (env config)
isDefaultbooleanoptional (default: false)[ADR-0090 D5] App baseline for the everyone position: app-level sets are auto-bound at boot (guarded, idempotent); package-level sets become install-time suggestions an admin confirms
objectsRecord<string, { allowCreate: boolean; allowRead: boolean; allowEdit: boolean; allowDelete: boolean; … }>Entity permissions
fieldsRecord<string, { readable: boolean; editable: boolean }>optionalField level security
systemPermissionsstring[]optionalSystem level capabilities
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optionalApp/tab visibility: visible, hidden, default_on (shown by default), default_off (available but hidden initially)
rowLevelSecurity{ name: string; label?: string; description?: string; object: string; … }[]optionalRow-level security policies (see rls.zod.ts for full spec)
adminScope{ businessUnit: string; includeSubtree: boolean; manageAssignments: boolean; manageBindings: boolean; … }optional[ADR-0090 D12] Scoped delegated-administration grant (BU subtree + assignable-set allowlist)
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this permission set.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

Nested Shape: PermissionSet.objects[string]

PropertyTypeRequiredDescription
allowCreatebooleanoptional (default: false)Create permission
allowReadbooleanoptional (default: false)Read permission
allowEditbooleanoptional (default: false)Edit permission
allowDeletebooleanoptional (default: false)Delete permission
allowExportbooleanoptionalUser-level export axis over read (opt-in grant). true = export granted (still bounded by read); unset/false = no export. Merged most-permissively like the CRUD bits; NOT implied by viewAllRecords/modifyAllRecords.
allowTransferbooleanoptional (default: false)[RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id)
allowRestoreneveroptional[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; apply them by hand.
allowPurgeneveroptional[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; apply them by hand.
viewAllRecordsbooleanoptional (default: false)View All Data (Bypass Sharing)
modifyAllRecordsbooleanoptional (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.
readScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Read depth: own|unit|unit_and_below|org
writeScopeEnum<'own' | 'own_and_reports' | 'unit' | 'unit_and_below' | 'org'>optional[ADR-0057 D1] Write depth: own|unit|unit_and_below|org

Nested Shape: PermissionSet.fields[string]

PropertyTypeRequiredDescription
readablebooleanoptional (default: true)Field read access
editablebooleanoptional (default: false)Field edit access

Nested Shape: PermissionSet.rowLevelSecurity[number]

PropertyTypeRequiredDescription
namestringPolicy unique identifier (snake_case)
labelstringoptionalHuman-readable policy label
descriptionstringoptionalPolicy description and business justification
objectstringTarget object name
operationEnum<'select' | 'insert' | 'update' | 'delete' | 'all'>Database operation this policy applies to
usingstringoptionalFilter condition for SELECT/UPDATE/DELETE, authored in canonical CEL (ADR-0058 D1). It enforces when the predicate lowers to an ObjectQL filter: a field compared against a literal or a current_user.* context value using ==, !=, <, <=, > or >=; in against a current_user.* array or an inline literal list (e.g. status in ['draft', 'pending']); these combined with && / ||; or the bare allow-all true. Anything that does not lower fails closed — the policy matches zero rows. The legacy SQL-ish spellings are still accepted through a transitional bridge that rewrites = to == and IN to in (deprecated under ADR-0058 D1); SQL AND / OR / NOT IN / IS NULL / LIKE are NOT bridged and fail closed. Optional for INSERT-only policies.
checkstringoptionalValidation condition for INSERT/UPDATE (defaults to USING clause if not specified - enforced at application level)
positionsstring[]optionalPositions this policy applies to (omit for all)
enabledbooleanoptional (default: true)Whether this policy is active
priorityneveroptional[REMOVED] rowLevelSecurity[].priority was removed in @objectstack/spec 17.0.0. It never had an effect and could not: applicable policies OR-combine (most permissive wins), so there is no conflict to order. Delete the key — policy outcomes are unchanged. Run os migrate meta --from 16 to list the mechanical edits for existing sources; apply them by hand.
tagsstring[]optionalPolicy categorization tags

Nested Shape: PermissionSet.adminScope

PropertyTypeRequiredDescription
businessUnitstring[ADR-0090 D12] Delegation boundary: sys_business_unit.name of the subtree root
includeSubtreebooleanoptional (default: true)Cover descendant business units too (default true)
manageAssignmentsbooleanoptional (default: false)Manage user↔position assignments within the subtree
manageBindingsbooleanoptional (default: false)Manage position↔permission-set bindings within the subtree
authorEnvironmentSetsbooleanoptional (default: false)Author environment-owned permission sets
assignablePermissionSetsstring[]optional (default: [])Allowlist of permission-set names the delegate may hand out

Nested Shape: PermissionSet.protection

PropertyTypeRequiredDescription
lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>Lock policy — none | no-overlay | no-delete | full.
reasonstringUser-visible reason shown when the lock blocks an action.
docsUrlstringoptionalOptional URL the Studio banner links to for more context.

On this page