Uh oh!
There was an error while loading. Please reload this page.
fix(platform-objects): retire the set_user_role action from sys_user - #11530
Conversation
set_user_role's only effect was internalAdapter.updateUser(userId, { role })
- a gated, UI-driven writer for the legacy sys_user.role scalar ADR-0068 D2
stopped synthesizing. Platform-admin membership is granted through
sys_user_permission_set / admin_full_access; a working "Set Platform Role"
button was a supported, one-user-at-a-time channel for resurrecting the dual
identity representation the 2026-08-18 ruling permanently vetoed (Option 3).
Removal, not a narrowed re-implementation (maintainer ruling, 2026-08-20,
reaffirmed 2026-08-22, Option B). The vendor's POST /admin/set-role route
itself stays mounted and vendor-gated, unchanged - only the sys_user console
action pointing at it is gone.
packages/spec/src/kernel/public-auth-features.ts drops the corresponding
'sys_user.actions.set_user_role' entry from PUBLIC_AUTH_FEATURES.admin's
gatedInputs - the bidirectional feature-gate-guard completeness check in
platform-objects reads this registry and forces the edit.
Pinned in one test with its counter-direction so a retirement that removed
the wrong entry (or several) cannot pass: the retired name is asserted gone
BY NAME, and all 15 sibling sys_user actions are asserted present BY NAME.
Translations regenerated via `node scripts/check-i18n-bundles.mjs --write`
(never hand-edited).
#9968 half 1. Half 2 (impersonate_user) is already fixed and merged on main
via PR #10352 - no changes needed here; see the PR body for the measurement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4📓 Docs Drift CheckThis PR changes 2 package(s): 42 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 65edcb806f6d9726e9bcb5f62eacaac785f58c57 && git checkout 65edcb806f6d9726e9bcb5f62eacaac785f58c57
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d10e2146263913d0f81b36607b41d21926249837 4d5129541635718ba3482aa03e944af5f78057ac && git checkout -B drift-repro d10e2146263913d0f81b36607b41d21926249837 && git merge --no-ff 4d5129541635718ba3482aa03e944af5f78057ac
node scripts/docs-audit/affected-docs.mjs --json d10e2146263913d0f81b36607b41d21926249837
|
Uh oh!
There was an error while loading. Please reload this page.
Part of #9968
Summary
This PR ships half 1 only — the
set_user_roleretirement. Half 2 (impersonate_user) is already fixed and merged onmain: PR #10352 (merged 2026-08-21) re-implemented/admin/impersonate-useras a better-auth plugin endpoint and admits ObjectStack platform admins today. That half's premise ("still 403 every platform admin") is false on currentmain— see the Measurements section below. No impersonate-side code changes are in this diff.set_user_role— retired, not re-implementedsys_user'sset_user_roleaction ("Set Platform Role") is removed. Its only effect wasPOST /api/v1/auth/admin/set-role, which better-auth'sadminplugin lowers tointernalAdapter.updateUser(userId, { role })— a gated, UI-driven writer for the legacysys_user.rolescalar ADR-0068 D2 stopped synthesizing. Platform-admin membership is granted throughsys_user_permission_set/admin_full_access; a working button was a supported, one-user-at-a-time channel for resurrecting the dual identity representation the 2026-08-18 ruling permanently vetoed (Option 3). Matches the maintainer's Option B ruling (2026-08-20, reaffirmed 2026-08-22).What stays unchanged. The vendor's
POST /api/v1/auth/admin/set-roleroute itself stays mounted and vendor-gated exactly as before — only thesys_userconsole action pointing at it is gone. Every othersys_useradmin action (ban_user,unban_user,unlock_user,create_user,set_user_password,impersonate_user) is unaffected.Files changed
packages/platform-objects/src/identity/sys-user.object.ts— removes theset_user_roleaction block; leaves a retirement note in its place.packages/spec/src/kernel/public-auth-features.ts— drops the now-nonexistent'sys_user.actions.set_user_role'entry fromPUBLIC_AUTH_FEATURES.admin.gatedInputs(the completeness guard reads this bidirectionally; forcing the edit, not a discretionary spec change).packages/platform-objects/src/platform-objects.test.ts— removes the two hard-coded references to the retired action, and adds a dedicated pin (#9968 — set_user_role is retired from sys_user, every sibling admin action survives) asserting the retired name is gone by name and every one of the 15 sibling actions survives by name, in one test so a retirement that removed the wrong entry (or several) cannot pass.packages/platform-objects/src/feature-gate-guard.test.ts— the hand-maintained walker floor drops from 38 to 37 booked gated inputs (comment updated to say why).packages/platform-objects/src/pages/sys-user.page.ts— two doc-comment mentions of the retired action removed for accuracy.packages/platform-objects/src/apps/translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts— regenerated vianode scripts/check-i18n-bundles.mjs --write(never hand-edited); drops theset_user_rolelabel/successMessage/param strings..changeset/retire-set-user-role-action.md— patch changeset on@objectstack/platform-objects+@objectstack/spec.Out of scope, deliberately untouched
POST /api/v1/auth/admin/set-roleroute stays mounted (unaffected) —auth-route-ledger.ts'sBETTER_AUTH_MOUNTED_SURFACE,vendor-admin-refusal-envelope.ts/test, and the dogfood/checklist coverage that exercises that route directly (not via thesys_useraction) are all still accurate and untouched.docs/adr/0092-sys-user-profile-field-delegation.mdmentions the retired action in a historical implementer note;docs/adr/**is a governed surface (Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14) and this PR does not touch it.Measurements
Half 1 premise — CONFIRMED valid before this PR
git grep -n "set_user_role" origin/main -- packages/platform-objects packages/specshowed the action still declared onsys_useratpackages/platform-objects/src/identity/sys-user.object.ts:253before this change.Half 2 premise — FALSE on current
main(measured, not assumed)git merge-base --is-ancestor 5b0af2b5d9 origin/main(PR fix(plugin-auth): admit ObjectStack platform admins on /admin/impersonate-user (better-auth plugin endpoint, not a raw mount) #10352's merge commit) → true: the fix is an ancestor of the branch point this PR was cut from.packages/plugins/plugin-auth/src/admin-impersonate-endpoint.tsandadmin-impersonate-endpoint.test.tsexist onmainand cover exactly the pins the ruling required: platform admin admitted, plain member refused403 YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS, anonymous refused401, org owner/admin (non-platform-admin) refused, admin-grade target refused403 YOU_CANNOT_IMPERSONATE_ADMINS, missing target404, and three dedicated#8243rotation-hook pins (bearer no longer resolves to the caller,set-admin-session-tokenreturned + CORS-exposed, original admin session row replaced).needs:contract-reviewcleared there, not on this card — this card's label covers the retirement half only).Tests
pnpm --filter @objectstack/platform-objects test— 452/452 passed (28 files).pnpm --filter @objectstack/platform-objects typecheck— clean.pnpm --filter @objectstack/spec test— 11204/11204 passed (420 files).pnpm --filter @objectstack/spec typecheck— clean.node scripts/check-i18n-bundles.mjs— in sync after--writeregeneration (8 bundles).node scripts/pm/dispatch-gates.mjs(no paths) — full derived gate list run; all green, including the repo-widecheck:type-check-debtratchet re-measure (32 ledger entries, none above their recorded ceiling).Generated by Claude Code