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:

ClaimReality
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

GateResult
check:role-wordOK (ratchets down 5→1 and 3→1; baseline diff is exactly those 2 lines)
check:doc-authoring / check:release-notesOK
check:api-surface0 breaking, 2 added → snapshot regenerated
spec exec tsc --noEmit / pnpm lintclean
testsspec 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

vercelBot commented Jul 17, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 17, 2026 3:31pm

Request Review

@xuyushun441-sys
xuyushun441-sysforce-pushed the feat/approver-type-org-membership-level branch from 0cb5a54 to 0abc1b3CompareJuly 17, 2026 13:01
@github-actionsgithub-actionsBot 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-zhuangand 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-sysforce-pushed the feat/approver-type-org-membership-level branch from 8636492 to 7d4fb1eCompareJuly 17, 2026 15:27
@os-zhuang
os-zhuang merged commit 3a18b60 into mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

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

@os-zhuang