Skip to content

feat(approvals)!: rename role approver type to org_membership_level — and unbreak main#3137

Merged
os-zhuang merged 3 commits into
mainfrom
feat/approver-type-org-membership-level
Jul 17, 2026
Merged

feat(approvals)!: rename role approver type to org_membership_level — and unbreak main#3137
os-zhuang merged 3 commits into
mainfrom
feat/approver-type-org-membership-level

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3133. Supersedes #3120 and #3122 (both closed) — they only deleted the duplicated docs callout without touching the name that causes the confusion.

This also unbreaks main

lint.yml has been red since #3113 (approvals.mdx role-word count 5 → 9), so every open PR inherits it. Fixing the name makes the docs stop needing the word at all — the ratchet goes green as a side effect, and ratchets down (5 → 1) rather than freezing 4 more occurrences in the baseline.

Why the D3 exception does not cover this enum

ADR-0090 D3 reserves "role", with one exception:

the better-auth boundary — sys_member.role is third-party schema we do not own; it remains, already relabelled org_membership_level in the platform projection (ADR-0057 D7), and its UI label is "organization membership", never "role".

The exception protects better-auth's column, which we cannot rename. ApproverType is ours — an authoring surface an author types into a flow node, i.e. the projection. D3 says the projection is spelled org_membership_level.

And the sentence licensing the leak is false:

Claim Reality
ADR-0090:203 — "already relabelled" org_membership_level appeared nowhere in the codebase — one comment in position.zod.ts:25
ADR-0057 D7 (0057-…md:335) Lists that relabel under "Deferred (evidence-gated, P4)"

The projection never landed, so the word leaked from the third-party column onto our authoring surface. Every sibling already converged — sys_rolesys_position, ShareRecipientType.roleposition, ctx.roles[]positions — leaving ApproverType.role the sole hold-out.

The name manufactures a silent failure

Because everything else became position, { 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 on an approver that cannot exist. That is the "hotcrm class" incident approval-role-not-membership-tier was written to catch — a lint rule existing to disambiguate a name is a smell pointing at the name (ADR-0077: make the mistake unwritable, don't lint it after the fact).

Repo-wide, type: 'role' had zero real callers — only lint tests and the docs warning that undoes the confusion the name creates.

What changed

  • specApproverType 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 read it), so removal next major is a one-line edit.
  • plugin-approvals — resolves on the canonical type, warns on the deprecated spelling; expandRoleUsersexpandMembershipTierUsers.
  • lintapproval-role-not-membership-tierapproval-approver-not-membership-tier (the rule id carried the word too) + new approval-approver-type-deprecated.
  • docs / skill / reference + baseline ratcheted down; api-surface regenerated.

Two decisions worth reviewing

  1. The fallback literal keeps the AUTHORED spelling. 15.x wrote role:<v> into sys_approval_approver and pending_approvers; canonicalising it here would orphan every stored slot. Locked by a test.
  2. The two lint rules are mutually exclusive, and a bad value wins. Telling someone to rewrite { type: 'role', value: 'sales_manager' } as org_membership_level would be wrong advice — the fix is position. Locked by a test.

Verification

Gate Result
check:role-word OK (ratchets down 5→1 and 3→1; baseline diff is exactly those 2 lines)
check:doc-authoring / check:release-notes OK
check:api-surface 0 breaking, 2 added → snapshot regenerated
spec exec tsc --noEmit / pnpm lint clean
tests spec 6892 · lint 227 · plugin-approvals 100 (+4 new)

minor, not major: the enum only gains a value, role still parses and resolves, and the one removal (APPROVAL_ROLE_NOT_MEMBERSHIP_TIER) is a lint const with zero consumers in cloud/objectui. All three packages are in the same 69-package fixed group. The actual removal of role is next major.

Follow-ups (not in this PR)

  • ⚠️ objectui: Studio still offers "Role" in the approver dropdown and its picker calls client.list('role') — a metadata type D3 deleted, so that picker is already dead and actively walks authors into the trap. objectui hardcodes its own copy of this enum (flow-node-config.ts), so it needs its own PR. 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.
  • content/docs/references/ has pre-existing drift from spec (7 unrelated files regenerate dirty, e.g. allowTransfer still says "pending M2" though 安全:owner_id(属主锚点)客户端可写、服务端无守卫 → 非属主可伪造/转移记录属主 #3004 enforced it). Deliberately reverted here to stay focused; nothing in CI catches it.
  • v14.mdx still names the old rule id — correct as history, left alone.

🤖 Generated with Claude Code

@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 Ready Ready Preview, Comment Jul 17, 2026 3:31pm

Request Review

@xuyushun441-sys
xuyushun441-sys force-pushed the feat/approver-type-org-membership-level branch from 0cb5a54 to 0abc1b3 Compare July 17, 2026 13:01
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/plugin-approvals, @objectstack/spec.

102 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/plugin-approvals, packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

os-zhuang and others added 3 commits July 17, 2026 23:25
…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>
…ated (#3133)

Dogfooding the Studio flow designer surfaced the gap the rename alone left:
the approver-type dropdown derives straight from this enum, so adding
`org_membership_level` beside `role` put the NEW name in the list without
removing the trap — `role` was still one click away from `position`.

`.meta({ xEnumDeprecated })` on the approver type (sourced from
DEPRECATED_APPROVER_TYPES, the single map) rides into the published node
configSchema. Renderers omit listed members from pickers while still rendering
a stored value, so a 15.x flow keeps loading. Locked by a test asserting the
published schema carries it (the objectui side reads exactly this key).

No new export (api-surface unchanged); generated references stay in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s org_membership_level

The generated reference's nested `approvers[].type` inline shape lagged the
top-level enum. Regenerated (pnpm gen:schema && gen:docs) on latest main so the
`check:docs` drift gate passes; xEnumDeprecated is a machine-readable hint and
does not surface in the human reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xuyushun441-sys
xuyushun441-sys force-pushed the feat/approver-type-org-membership-level branch from 8636492 to 7d4fb1e Compare July 17, 2026 15:27
@os-zhuang
os-zhuang merged commit 3a18b60 into main Jul 17, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the feat/approver-type-org-membership-level branch July 17, 2026 15:41
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ApproverType.role is the last surface projecting the reserved word — the ADR-0090 D3 exception it relies on has a false premise

1 participant