You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(approvals)!: rename role approver type to org_membership_level (#3133)
`ApproverType.role` was the last platform surface projecting the word
ADR-0090 D3 reserved. Renaming it also unbreaks `lint.yml` on main: the
role-word ratchet has been red since #3113 (approvals.mdx 5 → 9), and the
docs stop needing the word once the type is spelled correctly.
D3's exception does not cover this enum. It protects better-auth's own
`sys_member.role` COLUMN — third-party schema we cannot rename. `ApproverType`
is ours: an authoring surface, i.e. the *projection*, which D3 says is spelled
`org_membership_level` and labelled "organization membership", never "role".
The sentence licensing the leak is itself false. ADR-0090 D3:203 claims
`sys_member.role` is "already relabelled `org_membership_level` in the platform
projection (ADR-0057 D7)" — but `org_membership_level` appeared nowhere in the
codebase (one comment in position.zod.ts), and ADR-0057 D7:335 lists that
relabel under "Deferred (evidence-gated, P4)". The projection never landed, so
the word reached authors.
The name manufactured a silent failure ("hotcrm class"): every sibling surface
renamed to `position` (`sys_role`, `ShareRecipientType.role`, `ctx.roles[]`),
so `{ type: 'role', value: 'sales_manager' }` reads as a position's legacy
spelling. It resolves against the membership tier, finds no member row, falls
back to an inert `role:sales_manager` literal, and the request waits forever.
Repo-wide, `type: 'role'` had ZERO real callers — only lint tests and the docs
warning that exists to undo the confusion the name creates.
- spec: `ApproverType` gains `org_membership_level`; `role` kept as a
deprecated alias for one window so a published 15.x flow keeps loading.
`DEPRECATED_APPROVER_TYPES` + `canonicalApproverType()` are the single
source for the mapping (runtime and lint both read it).
- plugin-approvals: resolves on the canonical type, warns on the deprecated
spelling, `expandRoleUsers` → `expandMembershipTierUsers`. The `type:value`
fallback literal deliberately keeps the AUTHORED spelling — 15.x wrote
`role:<v>` into `sys_approval_approver` / `pending_approvers`, and
canonicalising it here would orphan every stored slot.
- lint: `approval-role-not-membership-tier` →
`approval-approver-not-membership-tier` (the rule id carried the word too),
plus `approval-approver-type-deprecated`. Mutually exclusive: a bad VALUE
wins, because prescribing `org_membership_level` for a position name is
wrong advice — the fix there is `position`.
- docs/skill/reference + role-word baseline ratcheted DOWN (approvals.mdx
5 → 1, automation SKILL.md 3 → 1); api-surface snapshot regenerated
(0 breaking, 2 added).
Studio still offers "Role" and its picker calls `client.list('role')` on a
metadata type D3 deleted — that picker is already dead, and the dropdown is
objectui's own hardcoded copy of this enum. Tracked as objectui follow-up in
#3133; the xRef map here points both spellings at the new picker kind, which
degrades to free text (strictly better than a picker that lists nothing).
Closes#3133. Supersedes #3120 and #3122, which only deleted the duplicated
docs callout without touching the name that causes the confusion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/automation/approvals.mdx
+17-15Lines changed: 17 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -53,11 +53,14 @@ defineFlow({
53
53
```
54
54
55
55
<Callouttype="warn">
56
-
**`position` vs `role`.**`{ type: 'position', value: 'finance_manager' }` routes to the
57
-
holders of a position (`sys_user_position`, ADR-0090 D3). The `role` approver type is the
58
-
better-auth **org-membership tier** (`sys_member.role`: `owner`/`admin`/`member`) — a position
59
-
name authored as `type: 'role'` matches nobody and the request stalls; `os lint` flags this
60
-
(`approval-role-not-membership-tier`).
56
+
**`position` vs `org_membership_level`.**`{ type: 'position', value: 'finance_manager' }` routes
57
+
to the holders of a position (`sys_user_position`, ADR-0090 D3). `org_membership_level` is a
58
+
different thing — the better-auth **org-membership tier**, whose only values are
59
+
`owner`/`admin`/`member`. A position name authored there matches nobody and the request stalls;
60
+
`os lint` flags it (`approval-approver-not-membership-tier`).
61
+
62
+
Authored `type: 'role'` on 15.x? That is the deprecated spelling of `org_membership_level`
63
+
(ADR-0090 D3): it still resolves, warns at runtime, and is removed in the next major.
61
64
</Callout>
62
65
63
66
Approving is itself a gated action — model "may approve" as a capability (`approve_invoice`) the approver's permission set grants, and gate the approve action's `requiredPermissions` on it so the gate is enforced on **both** the UI and the server (ADR-0066 D4).
@@ -130,14 +133,11 @@ record is **locked** against edits while pending (`lockRecord`, default `true`),
130
133
and the flow run parks until a decision arrives.
131
134
132
135
Only `approvers` is required on the node; everything else has a default
0 commit comments