From de46ea1a471fcc03eda1da5daca64d5b88a64f50 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Fri, 17 Jul 2026 20:03:10 +0800 Subject: [PATCH] docs(approvals): drop duplicate role-vs-position callout (unbreak role-word ratchet) The ESLint job's "Reserved-word (role) docs ratchet" step has been red on main since 25a19be72, which took approvals.mdx from 5 -> 9 role-word occurrences. Every open PR inherits the red check. The occurrences are NOT ADR-0090 D3 drift and cannot be reworded away: `role` is a real, shipping approver type (spec/src/automation/approval.zod.ts:14) that resolves against better-auth's org-membership tier (`sys_member.role`), which D3 names as its single documented exception ("third-party schema we do not own"). Writing `position` in these spots would make the docs wrong -- telling the two apart is the entire point of the passage, and of the `approval-role-not-membership-tier` lint rule. What the ratchet did catch is real redundancy: the new callout under "The request opens, the run pauses" restates the callout ~75 lines above it, which says the same thing and more (it names the lint rule). Removed the duplicate. That leaves `role:` in the approverId filter list -- a genuine new fact at the same sanctioned boundary (spec/src/contracts/approval-service.ts:231). Rather than delete a true API detail to satisfy a counter, the surviving callout is tightened: it stated role-the-approver-type twice ("The `role` approver type ..." and "authored as `type: 'role'`"), now once. That frees the slot, so the file lands back at exactly 5. Baseline deliberately NOT bumped -- a high-water mark that only ratchets down is the point of the gate. Verified: node scripts/check-role-word.mjs -> OK (exit 0); pnpm lint -> exit 0. Co-Authored-By: Claude Opus 4.8 --- content/docs/automation/approvals.mdx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/content/docs/automation/approvals.mdx b/content/docs/automation/approvals.mdx index 592e26199c..9ca09d86bd 100644 --- a/content/docs/automation/approvals.mdx +++ b/content/docs/automation/approvals.mdx @@ -54,10 +54,10 @@ defineFlow({ **`position` vs `role`.** `{ type: 'position', value: 'finance_manager' }` routes to the -holders of a position (`sys_user_position`, ADR-0090 D3). The `role` approver type is the -better-auth **org-membership tier** (`sys_member.role`: `owner`/`admin`/`member`) — a position -name authored as `type: 'role'` matches nobody and the request stalls; `os lint` flags this -(`approval-role-not-membership-tier`). +holders of a position (`sys_user_position`, ADR-0090 D3). `type: 'role'` is a different thing +entirely — the better-auth **org-membership tier** (`sys_member.role`: `owner`/`admin`/`member`) +— so a business position name authored there matches nobody and the request stalls; `os lint` +flags this (`approval-role-not-membership-tier`). 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). @@ -132,13 +132,6 @@ and the flow run parks until a decision arrives. Only `approvers` is required on the node; everything else has a default (`behavior: 'first_response'`, `lockRecord: true`, `maxRevisions: 3`). - -**`type: 'role'` is not a position.** In an approver entry, `role` means the -better-auth membership tier (`owner` / `admin` / `member`). Naming a business -role like `sales_manager` there matches nobody and the request silently has no -approvers — use `{ type: 'position', value: 'sales_manager' }`. - - ### The approver finds it in their queue Requests land in **Setup → Approvals → Requests**, whose `my_pending` view