Positions
Model job functions and audience anchors with positions.
Positions describe the job function a user holds — "Sales Manager", "Support Agent", "Auditor". Since ObjectStack 13 (Permission Model v2, ADR-0090), positions are the single people-grouping concept: the legacy Role and Profile frameworks were consolidated into positions and removed from the protocol.
Positions are intentionally flat. There is no parent field and no
position hierarchy — reporting structure and subtree-based visibility
live on the business-unit tree
instead.
Position responsibilities
Use positions to model:
- job functions ("Sales Manager", "Recruiter", "Auditor");
- audience targeting for apps, tabs, and books;
- approval responsibility (approvers of type
positionexpand to the current holders automatically); - distribution of permission sets to everyone holding the same function.
Use business units to model the org chart (departments, regions, manager/subordinate structure) and permission sets to grant concrete object, field, and system capabilities.
System objects
| Object | Purpose |
|---|---|
sys_position | Position definition (flat — no hierarchy) |
sys_user_position | A user's assignment to a position |
sys_position_permission_set | Permission sets distributed through a position |
sys_business_unit | Org-chart tree used for hierarchy and subtree sharing |
sys_business_unit_member | A user's membership in a business unit (function_in_business_unit) |
Writes to the RBAC link tables (sys_user_position,
sys_position_permission_set, sys_user_permission_set,
sys_permission_set) require tenant-level admin rights or a
delegated-admin scope —
plain CRUD grants on those tables are not sufficient.
Audience anchors: everyone and guest
Two system-managed positions are seeded in every deployment:
| Anchor | Held by |
|---|---|
everyone | Every authenticated principal, implicitly |
guest | Sessionless (anonymous) HTTP principals only |
Bind a permission set to everyone to grant a baseline to all signed-in
users. Bindings are additive, and the platform refuses to bind
high-privilege sets (View All / Modify All, delete/purge/transfer,
system permissions, wildcards) to these anchors.
guest grants nothing. Anonymous access to object data is denied
on every HTTP surface (ObjectStack 17.0), and no binding on the guest
anchor gives an anonymous caller a permission set (ObjectStack 17.6).
Open something to anonymous visitors by declaring the
surface instead — a
public form or a share
link — each of which authorizes only what it declares.
The configured baseline permission set (fallbackPermissionSet, default
member_default) applies through the same everyone binding path. Note
that since ObjectStack 14.2 the member baseline no longer includes
delete — record deletion must be granted explicitly per object
through position-distributed permission sets.
A caller with no permission set gets the deny baseline (ObjectStack
17.6). A request that resolves no permission set at all — an anonymous
request, or one naming only sets that do not exist — is refused every
object with 403 PERMISSION_DENIED. It is not served a field gated by
requiredPermissions (or is served it masked, if the field declares a
maskingRule), and cannot filter, sort, group or write on one. Signed-in
users are unaffected as long as a baseline set is configured — the
default, member_default, always resolves for them.
Deactivating a position
A position's Deactivate action takes away everything it confers, immediately (ObjectStack 17.1):
- it distributes none of its permission sets, and its name leaves the
holders'
current_user.positions; - a sharing rule whose recipient is the position shares with nobody, and the record shares it had already granted are revoked on the next reconcile.
Deactivation stops access, not addressing: an approval step with an
approver of type position still routes to the holders of a
deactivated position. A position whose active value is empty is
unaffected — only an explicit deactivation counts.
Before ObjectStack 17.1 the switch was inert: a deactivated position kept granting. Upgrading past it revokes that access on live data, so re-activate any deactivated position that is still meant to grant before you upgrade.
Effective-dated assignments
Position and permission-set assignments carry optional valid_from /
valid_until windows (half-open, UTC; null means unbounded). Expired
grants stop resolving immediately — useful for temporary coverage,
contractors, and certification-bound access. Each grant can record a
reason and, for delegations, delegated_from.
A position marked delegatable: true allows its current holders to
self-delegate it to another user for a bounded window (a future
valid_until capped at 30 days, mandatory reason). Positions that
distribute admin-scoped permission sets can never be delegatable.
Recommended pattern
Keep a small, stable set of function-shaped positions:
Sales Manager
Sales Representative
Support Manager
Support AgentModel the org chart as business units:
Sales
├─ EMEA
└─ AMER
SupportThen attach permission sets to positions for capabilities:
Sales Manager position
-> CRM User
-> Sales Manager Access
-> Report ViewerThis keeps positions stable while permissions evolve independently.
Avoid
- one position per individual user;
- duplicating every permission inside every position;
- using position names as business-logic checks;
- rebuilding hierarchy on positions — subtree visibility belongs to business units and record sharing;
- granting broad access through manager positions when record sharing would be more precise.
Migrating from roles (pre-13)
If you are upgrading metadata authored against ObjectStack 12 or earlier:
| Legacy | Current |
|---|---|
sys_role / sys_user_role / sys_role_permission_set | sys_position / sys_user_position / sys_position_permission_set |
defineRole / RoleSchema (with parent) | definePosition / PositionSchema (flat) |
Metadata kind role or profile | position |
Sharing recipient role / role_and_subordinates | position / unit_and_subordinates |
PermissionSetSchema.isProfile | Removed — isDefault marks install-time suggestions |
Role hierarchies must be re-modeled as business-unit trees. os lint
flags leftover role vocabulary via the security-role-word rule.