Repository navigation
「这个 membership 是不是管理员」有两种拼写,大小写敏感性不同:isOrgOrPlatformAdmin 对 role='Owner' 答否,等级尺答是 #5942
Description
Activity
发现分诊轮:晋级
finding→pm:queue,域domain:identity。- 过时前提检查:
git log --oneline origin/main -- packages/plugins/plugin-auth/src/auth-manager.ts无 feat(plugin-auth): break-glass 守卫 —— ban 不得停用最后一个管理员(ADR-0024 D5.2) #5939 之后的改动,isOrgOrPlatformAdmin(:3625-3634)的手抄判据仍不转小写,而 PR feat(plugin-auth): break-glass 守卫 —— ban 不得停用最后一个管理员(ADR-0024 D5.2) #5939 新导出的isOrgAdminGrade()(invitation-role-cap.ts,.trim().toLowerCase())已是等级尺的唯一实现。两种拼写并存,前提成立。 - 为什么晋级(尽管正文自陈「今天撞不到」):①修法是收敛到已存在的唯一实现,不是新增语义 —— XS,且方向无分歧;②两个错的方向都不响(break-glass 守卫多算一个管理员 = fail-open 侧,
/sso/register门少算 = 误拒),同一条安全路径上的静默分歧正是 restore-invariant 的形状;③sys_member.role今日全小写靠的是 ADR-0108 封闭词表 + UI/better-auth 写入,不是结构性保证(导入 / 外部写入 / 手工 SQL 都在词表之外),不可达性没有闸门兜着。 - 同座位文件面提示(非阻塞):本单与本轮入队的 最后一个管理员的「删除」路径无守卫:目标不持本地密码时,SCIM/admin remove 可删掉环境最后一个管理员 #5941(最后一个管理员的删除守卫)都触
auth-manager.ts,车道排批次时按同座位串行处理即可 —— 分诊不代排。 - 查重:三仓搜
isOrgOrPlatformAdmin/orgRoleGrade/isOrgAdminGrade仅命中本单与 plugin-auth: break-glass 守卫 —— SCIM/ban 不得停用最后一个管理员(ADR-0024 D5.2,cloud#621 转入) #5892 / 最后一个管理员的「删除」路径无守卫:目标不持本地密码时,SCIM/admin remove 可删掉环境最后一个管理员 #5941(互补,不重复)。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 过时前提检查:
认领:PM 循环第 7 轮(
domain:identity车道)
会话:session_01JwwiU9bjhwy2SWj13ho8uv
分支:claude/issue-5942-admin-grade-single-ruler
Worktree:objectstack-issue-5942
域:domain:identity
文件面:packages/plugins/plugin-auth/src/auth-manager.ts(isOrgOrPlatformAdmin收敛)+ 测试 +.changeset/*串行核对:#5941 的 PR #5993(在队)最终文件面不含
auth-manager.ts/invitation-role-cap.ts,与本单零相交(#5941 dev 必答项遗漏,PM 已从 diff 判定并注记于 #5941 验收评论);#5993 落地前后对本单无成本影响。分诊晋级理由(不可达性无闸门兜着 + 收敛到唯一实现 XS 修)采纳。
Generated by Claude Code
复核 ACCEPT(PM,
session_01JwwiU9bjhwy2SWj13ho8uv,第 7 轮):交付 PR #6010。- 收敛兑现:
isOrgOrPlatformAdmin的手抄 6 行换成isOrgAdminGrade(m?.role)——「哪种 membership 算管理员」在 plugin-auth 内只剩一个答案,与 break-glass 守卫同尺;platform_admin 半边未动(scope 守恒),invitation-role-cap.ts只读。 - 等价性是测出来的,不是断言的:测试先行 —— 15 条回归 pin 在改动前后都绿(零收窄的实测证据),9 红恰为放宽本体(大小写非常规值 + 数组拼写从误拒变正确放行);fail-closed 地板(空值/非字符串/near-miss/engine 抛错)逐值钉住。
⚠️ 派发词纠偏,公开确认(dev 证伪 PM = 好运行):我把' admin '列为红例是错的 —— 旧手抄本就.trim(),该值前后都判真。dev 未照单硬凑,而是将其如实归为回归 pin,并补上真正红的' Admin '(大小写+空白)变体覆盖派发意图。测试注释里写明了这一区分。- 中断恢复留档:dev 在收尾阶段被会话限额中断,经 SendMessage 从 transcript 复活后完成收尾(worktree 已清理、标签已读回)—— 探活协议的标准路径,交付质量无损。
- CI 23 检查全绿,changeset(patch)方向声明准确。转 ready 入合并队列。
Generated by Claude Code
- 收敛兑现:
队列管家:本单的 PR #6010 被假红踢出(新签名,⛔ 未重投)
PR #6010 于 17:32:44Z 第二次被踢出合并队列。红因既不是本单的 diff,也不是测试失败 —— 是一次门禁假红;因签名不在 #5810 台账,按四分支纪律拦截不重投,通知本车道。
完整签名(完整 job 归档,非 tail):run 31120902911 的两条聚合门禁 ——
Test Core17:32:13Ztest matrix aggregate result: **abandoned**→::error::Test Core shards did not pass;Dogfood Regression Gate17:33:02Z 两条 leg 同为abandoned。判读:队列重建把分片整片丢弃(
Test Core (1/3)/(3/3)于 17:16:26Z / 17:24:48Z 被 cancelled,(2/3)是 success,全 run 零测试失败),聚合读数落成abandoned;而.github/workflows/ci.yml:347的白名单只有success|skipped|cancelled,abandoned落进*)兜底判红。该case上方引 #3668 的注释论证的正是「生命周期状态不得判红」——abandoned只是漏在白名单外。本单需要做的事:无。 #6010
mergeable_state: clean,diff 与红因无关,不需要任何代码改动。是否重投由本车道决定;本座位建议等门禁补abandoned或平台面恢复后再投 ——origin/main已 3h+ 零落地、队列反复重建,现在投进去大概率复现同一次假红。完整分析与建议动作见 PR #6010 上的拦截评论;门禁缺口已在 #5810 提请人工升级台账(⛔ 本座位不自行加表、不改代码)。
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026
观察类发现(
finding),来自 #5892 / PR #5939 的实现过程。今天没有用户会撞上:两种拼写在所有小写取值上答案一致,而 UI 与 better-auth 写入的都是小写。记录下来是因为它是一条安全路径上的静默分歧。两种拼写
等级尺(唯一那把,
packages/plugins/plugin-auth/src/invitation-role-cap.ts):parseOrgRoles()会.trim().toLowerCase(),orgRoleGrade()据此评级。PR feat(plugin-auth): break-glass 守卫 —— ban 不得停用最后一个管理员(ADR-0024 D5.2) #5939 新导出的isOrgAdminGrade()就是它,break-glass ban 守卫用它数管理员。手抄版(
packages/plugins/plugin-auth/src/auth-manager.ts:3625-3634,isOrgOrPlatformAdmin):不转小写。这段是
/sso/register管理员门禁的判据(ADR-0024,fail-closed)。分歧
sys_member.role若存进Owner/ADMIN(导入、外部写入、手工 SQL 都可能),同一行会被:/sso/register门禁算作非管理员(于是拒绝一个本该允许的注册)。两个方向的错都不响 —— 没有任何一处会报「这两处不一致」。
为什么现在只是观察
sys_member.role的可选值来自BUILTIN_MEMBERSHIP_ROLE_OPTIONS(ADR-0108 的封闭词表),值全为小写;better-auth organization 插件写入的也是小写。所以要造出分歧,得有一条绕过表单的写入。没有查到这样的生产路径。可能的收口
把
isOrgOrPlatformAdmin里那 6 行换成isOrgAdminGrade(m.role)(语义相同,额外获得大小写与数组拼写的处理),这样「哪种 membership 算管理员」在 plugin-auth 内只剩一个答案。#5939 没有顺手做:那是另一条安全路径上的方法,不属于 #5892 的范围。顺带记录:
platform_admin的推导目前有三处独立实现 ——packages/core/src/security/resolve-authz-context.ts(权威)、auth-manager.ts的customSession与isOrgOrPlatformAdmin、以及 #5939 的守卫(反方向枚举,resolveAuthzContext按 user 查,回答不了「谁是管理员」这个集合问题)。四处今天一致,但和上面同属一类风险。参考
isOrgAdminGrade的导出与第二个消费者)Generated by Claude Code