ObjectStackObjectStack

Permission — Security Protocol reference

Permission schemas of the ObjectStack Security Protocol: AdminScope and 5 more — each property with its type, default and a TypeScript example.

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. Required and non-blank: an empty or whitespace-only value names no business unit and is refused.
includeSubtreebooleanoptional (default: true)Cover descendant business units too (default true)
manageAssignmentsbooleanoptional (default: false)Manage user↔position assignments within the subtree
manageBindingsneveroptional[REMOVED] permission.adminScope.manageBindings was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0090 D12) — nothing honoured it once the position-to-permission-set binding rows it gated were retired: a position's sets are part of its definition (ADR-0131 D3), and that definition is written through the metadata door, which admits a manage_metadata holder and reads no adminScope. Delete the key; a delegate's scope is placement, assignment and the allowlist — businessUnit with includeSubtree, manageAssignments, and assignablePermissionSets. 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.
authorEnvironmentSetsneveroptional[REMOVED] permission.adminScope.authorEnvironmentSets was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0090 D12) — nothing honoured it once permission sets stopped being rows: a permission set is an environment metadata definition (ADR-0131 D3), written through the metadata door, which admits a manage_metadata holder and reads no adminScope. Delete the key; a delegate's scope is placement, assignment and the allowlist — businessUnit with includeSubtree, manageAssignments, and assignablePermissionSets. 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.
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; --write applies the ones it can prove, and you apply the rest 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; --write applies the ones it can prove, and you apply the rest 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; --write applies the ones it can prove, and you apply the rest 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; --write applies the ones it can prove, and you apply the rest 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
namestring✅Permission set name, unique within the security catalog the environment serves (lowercase snake_case)
labelstringoptionalDisplay label
descriptionstringoptionalHuman-readable description shown in Setup
packageIdstringoptional[ADR-0086 D3] Owning package id for a package-shipped set (absent = env-authored); a value that disagrees with the package that ships the set, or that it is saved into, is refused by the stack schema (defineStack, os validate, os build), the package registration, and the metadata save, restore and publish doors
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; assignablePermissionSets?: string[] }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; --write applies the ones it can prove, and you apply the rest 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; --write applies the ones it can prove, and you apply the rest 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
namestring✅Policy unique identifier (snake_case)
labelstringoptionalHuman-readable policy label
descriptionstringoptionalPolicy description and business justification
objectstring✅Target object name
operationEnum<'select' | 'insert' | 'update' | 'delete' | 'all'>✅Database operation this policy applies to
usingstringoptionalRow predicate, authored in canonical CEL (ADR-0058 D1): the rows a select policy lets a caller read, and the existing rows an update or delete policy lets a caller change or remove. On an insert or an update, when no applicable policy for that operation declares check, each applicable policy's using also stands in as its check on every row written (see check); on an insert policy that is its only effect. 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. Needed on a select or delete policy (a check there is refused); optional on an insert, update or all policy that declares check.
checkstringoptionalValidation condition judged on every row an insert or an update writes (enforced at application level): each row of an insert, an array insert included, as its beforeInsert hooks leave it, and each row an update changes, by id or multi: true, as the prior row merged with the final payload after its beforeUpdate hooks. One failing row refuses the whole write. A by-id update is also judged, before its hooks, on the prior row merged with the change set as sent. The default to using is decided per operation across the applicable policies, not per policy: when any applicable policy for that operation declares check, only the declared checks decide (OR-combined) and a USING-only sibling adds nothing; only when none declares check does each applicable policy's using stand in as its check (OR-combined). Applicable = not enabled: false, object matches or is '*', operation matches or is 'all', and the caller holds one of its positions when it lists any. Refused on a policy whose operation is select or delete, which write no new row to check: limit the rows such a policy admits with using, and declare the check on an insert, update or all policy.
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. Delete the key — policy outcomes are unchanged. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
tagsneveroptional[REMOVED] rowLevelSecurity[].tags was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — nothing ever read it: the RLS compiler never consulted a policy's tags and nothing else acted on them, so a tag scoped, restricted and reported nothing. Delete the key. A tag never limited whom a policy applies to; to do that, list the positions in positions. A policy is identified by its name and its object; say why it exists in description. 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.

Nested Shape: PermissionSet.adminScope

PropertyTypeRequiredDescription
businessUnitstring✅[ADR-0090 D12] Delegation boundary: sys_business_unit.name of the subtree root. Required and non-blank: an empty or whitespace-only value names no business unit and is refused.
includeSubtreebooleanoptional (default: true)Cover descendant business units too (default true)
manageAssignmentsbooleanoptional (default: false)Manage user↔position assignments within the subtree
manageBindingsneveroptional[REMOVED] permission.adminScope.manageBindings was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0090 D12) — nothing honoured it once the position-to-permission-set binding rows it gated were retired: a position's sets are part of its definition (ADR-0131 D3), and that definition is written through the metadata door, which admits a manage_metadata holder and reads no adminScope. Delete the key; a delegate's scope is placement, assignment and the allowlist — businessUnit with includeSubtree, manageAssignments, and assignablePermissionSets. 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.
authorEnvironmentSetsneveroptional[REMOVED] permission.adminScope.authorEnvironmentSets was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0090 D12) — nothing honoured it once permission sets stopped being rows: a permission set is an environment metadata definition (ADR-0131 D3), written through the metadata door, which admits a manage_metadata holder and reads no adminScope. Delete the key; a delegate's scope is placement, assignment and the allowlist — businessUnit with includeSubtree, manageAssignments, and assignablePermissionSets. 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.
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.
reasonstring✅User-visible reason shown when the lock blocks an action.
docsUrlstringoptionalOptional URL the Studio banner links to for more context.

On this page