Position
Position protocol schemas
Position Schema — the flat capability-distribution group (ADR-0090 D3).
A position (岗位, "job role" in NetSuite/Workday terms) is a **named,
assignable bundle of permission sets**: users hold positions
(sys_user_position), positions bind permission sets
(sys_position_permission_set), and a user's capability is the union of
every set reached that way plus direct grants.
Positions are deliberately flat — no parent, no hierarchy. The
visibility hierarchy lives on the business-unit tree (sys_business_unit,
ADR-0057 D2) and the manager chain (sys_user.manager_id); re-adding a
second tree here is the mistake ADR-0057 D5 retired and ADR-0090 D3
finalizes.
VOCABULARY (ADR-0090 D3): the word "role" is reserved-forbidden across the
platform — capability = permission_set, distribution = position,
hierarchy = business_unit. The sole exception is better-auth's internal
sys_member.role (org-membership tier), projected as
org_membership_level.
NAMING CONVENTION:
Position names MUST be lowercase snake_case to prevent security issues.
@example Good position names
-
'sales_manager'
-
'ceo'
-
'region_east_vp'
-
'engineering_lead'
@example Bad position names (will be rejected)
-
'SalesManager' (camelCase)
-
'CEO' (uppercase)
-
'Region East VP' (spaces and uppercase)
Source: packages/spec/src/identity/position.zod.ts
TypeScript Usage
import { Position } from '@objectstack/spec/identity';
import type { Position } from '@objectstack/spec/identity';
// Validate data
const result = Position.parse(data);Position
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique position name (lowercase snake_case) |
| label | string | ✅ | Display label (e.g. VP of Sales) |
| description | string | optional | |
| delegatable | boolean | ✅ | ADR-0091 D3: holders may self-service delegate this position, time-boxed (default false). |