status column (the “pending” state lives entirely on the invitation). A member row always means “this user actually works here.”
The workspace’s owner never has a member row — owner and member rows are disjoint (see Workspace → Ownership).
Properties
The two arrays: capabilities vs permissions
The single most important sentence for developers:
Sublay understands and enforcesThey never mix.capabilities; Sublay is blind topermissions.
capabilities is a fixed set Sublay uses to gate its own workspace plumbing (who can invite, who can remove members, etc.). permissions is whatever your app means by “can deploy,” “can edit,” etc. — Sublay stores, grants, revokes, and returns them, but never validates or interprets which strings you use.
capabilities (the closed vocabulary)
There are exactly 7 capabilities. Sublay ships no built-in owner/admin/member role presets — you compose your own role concepts from these keys (the “keyring” model).
The four people-operating capabilities (
invite, remove-member, edit-member-access, edit-member-profile) additionally grant visibility of other members’ authority.
Two tiers, not one. Reading the roster at all —
GET /workspaces/:id/members and GET /workspaces/:id/members/:userId — needs only some relation to the workspace (owner, ancestor-owner, member, or reach-holder); that tier shows who is there, plus each member’s title and metadata.The authority-bearing fields — capabilities, permissions, rank — are omitted from other users’ entries unless the caller holds one of the four people-operating capabilities above, or is the owner / an ancestor-owner (service and master keys are never fenced). A caller always sees their own — their own roster row, their own standing, and the authority read — so no one is ever blind to their own access.title.
Capabilities are resolved across the tree — a member’s effective set includes their direct capabilities, plus any reaching in from ancestors via an open inherit-chain, plus the full vocabulary if they are an owner/ancestor-owner. See reach and the authority read.
permissions (opaque)
A free-form array of developer-defined strings, opaque to Sublay. Unlike capabilities, permissions do NOT cascade down the tree — they are strictly per-node. A read of a user’s standing returns the permissions stored on their direct membership on the target workspace only; an ancestor membership’s permissions never appear. If you want a parent permission to imply a child one, that is your app logic to add.
Rank semantics
rank is a per-workspace integer where a smaller number = more senior. The owner is the apex, above rank 0 (not a numbered rank).
- The owner mints rank 0 (only an owner can mint rank 0); each level appointed below gets the next number up (1, 2, 3, …). The top is anchored at 0 and the ladder extends downward without a ceiling.
- Act rule: a member may only strip capabilities from, remove, or re-rank members who are strictly less senior (a strictly larger rank number). Equals cannot act on equals.
- Assign rule: you may only set another member’s rank to a number strictly larger than your own.
- Initial rank is chosen on the invitation.
No privilege escalation
- Granting a capability or permission string requires the actor to already hold it on the target node (their resolved set there, including via reach).
- Stripping/removing a capability or permission is governed by rank alone (strip anything from a lower-ranked member; you need not hold it yourself).

