Skip to content

docs(approvals): drop duplicate role-vs-position callout (unbreak role-word ratchet)#3122

Closed
os-zhuang wants to merge 1 commit into
mainfrom
claude/affectionate-lamarr-0730c1
Closed

docs(approvals): drop duplicate role-vs-position callout (unbreak role-word ratchet)#3122
os-zhuang wants to merge 1 commit into
mainfrom
claude/affectionate-lamarr-0730c1

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Why main is red

The ESLint job of Lint & Type Check fails on main at its Reserved-word ("role") docs ratchet step, so every open PR inherits a red check. pnpm lint itself is clean — this is not an eslint rule failure.

• content/docs/automation/approvals.mdx: role-word count grew 5 → 9.
  New occurrences are banned (ADR-0090 D3).

Introduced by 25a19be (#3113). Red at 021ba4c and every commit after.

The occurrences are not drift — and must not be reworded

The obvious fix (swap role → the D3 vocabulary) is wrong here, so this PR does not do it:

  • role is a real, shipping approver typeapproval.zod.ts:14, sitting in the enum next to position with a comment explaining exactly why it stays.
  • It resolves against better-auth's org-membership tier (sys_member.role: owner/admin/member) — which ADR-0090 D3 names as its single documented exception: "the better-auth boundary — sys_member.role is third-party schema we do not own; it remains."
  • Writing position in these spots would make the docs factually wrong. Distinguishing the two is the entire point of the passage — and of the approval-role-not-membership-tier lint rule, which exists precisely because authors confuse them and their approvals silently route to nobody.

What the ratchet did catch

Real redundancy. The callout added under "The request opens, the run pauses" restates the callout ~75 lines above it — same fact, same page, and the older one says more (it names the lint rule and sys_user_position). Removed the duplicate (−3 occurrences). That's a docs improvement on its own merits.

That left role:<r> in the approverId filter list (−1 short): a genuine new fact at the same sanctioned boundary (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 is…" and "authored as type: 'role'"), now once. That frees the slot and 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. scripts/role-word-baseline.json is untouched — the only changed file is the .mdx.

Reviewer's judgment call

I tightened the surviving callout partly to stay under the cap. If maintainers would rather keep that callout's original wording and ratchet the baseline 5 → 6 for role:<r>, that is a defensible call — the ratchet is a blunt per-file counter that can't tell a sanctioned better-auth mention from real drift. I didn't make that call unilaterally, because it's a human decision, not a reflex.

Verification

Gate Result
node scripts/check-role-word.mjs OK (49 baselined file(s), no new occurrences) — exit 0
pnpm lint exit 0

Docs-only change → skip-changeset.

🤖 Generated with Claude Code

…e-word ratchet)

The ESLint job's "Reserved-word (role) docs ratchet" step has been red on main
since 25a19be, 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:<r>` 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 <noreply@anthropic.com>
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Jul 17, 2026
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jul 17, 2026 12:03pm

Request Review

@os-zhuang

Copy link
Copy Markdown
Contributor Author

关闭(仓库 owner 指示):与 #3120 重复,两个 PR 改同一个文件、同一个修复。结论本身是对的(删同页重复 callout、基线别抬),但都停在创可贴层面——没碰造成这个坑的根因:ApproverType.role 这个名字。

合并处理到 #3133role 改名为 ADR-0057 D7 / ADR-0090:203 早已规定的投影名 org_membership_level。改完文档不再需要写这个保留词,棘轮的红是副产品。#3120 也一并关闭。

@os-zhuang os-zhuang closed this Jul 17, 2026
xuyushun441-sys pushed a commit that referenced this pull request Jul 17, 2026
…l` (#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
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>
xuyushun441-sys pushed a commit that referenced this pull request Jul 17, 2026
…l` (#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
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>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
…l` (#3137)

Closes #3133. Renames the last platform surface projecting the ADR-0090 D3 reserved word: `ApproverType.role` → `org_membership_level`, with `role` kept as a deprecated alias for one window (resolves identically, warns). Publishes `xEnumDeprecated` on the node configSchema so Studio drops the deprecated spelling from the approver-type picker. Supersedes #3120/#3122. objectui half: objectstack-ai/objectui#2643 (merged).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant