Repository navigation
spec: BulkActionDefSchema 缺 requiredPermissions —— 渲染器已按它过滤,作者却写不出来(内联 update/delete 批量定义没有任何合法写法加能力门) #6257
Description
Activity
Triage:
pm:queue+domain:spec.Landing site.
packages/spec/src/ui/bulk-action.zod.ts⇒domain:spec, and under "shared contract surfaces have one owner" anything touchingpackages/specbelongs to that seat regardless of who needs it.Premise verified on
origin/main@2598216.BulkActionDefSchemaends in}, { error: bulkActionDefUnknownKeyError }).strict()and its key set isname / label / icon / variant / operation / execution / patch / params / confirmText / confirmLabel / visible / maxRecords / batchSize / condition / style— norequiredPermissions. The consumer half is already shipped: objectui#3492 landed via objectui#3548 (d915c47, "批量按钮补上 requiredPermissions 与布尔 visible"). So this really isenforced ≠ declarable, and the fix is additive with no consumer change.Cross-repo dedup — a field-report shadow existed and has been converged. objectui#3564 reported the same gap from a running
17.0.0-rc.5deployment. Per the convergence rule (one thing, exactly one dispatch entry) it has been closed as a duplicate of this issue, and its independent evidence is carried over here because it strengthens the case:- real-machine probe on a device list, admin account: a bulk button with
requiredPermissions: ['<nonexistent capability>']is filtered out, and admin is not exempt — same semantics as navigation-itemrequiredPermissions; - the documented workaround (declare an object action, reference it by name from
bulkActionDefs) requiresoperation === 'custom' && execution === 'aggregate', after whichoperationis pinned tocustom. So a declarativeoperation: 'update' + patchbulk button and a capability gate are mutually exclusive today — matching this issue's fourth table row; - reported user-visible impact: four declarative bulk buttons (reassign / push-down / transfer / bulk delete) visible to every user who can open the list, rejected per-record by a server hook only after the click.
⚠️ One process note, no action implied. This issue carried an assignee before any triage label existed, and has no claim comment. Flagging because "an unlabelled issue must not be claimed" is the lane protocol's only mechanical guard, and because a claim comment is what tells a later reader whose claim it is. This seat never assigns and has not touched the assignee.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- real-machine probe on a device list, admin account: a bulk button with
认领。维护者(assignee)已直接委派本任务;此前实现分支已删除,从零重做。
- 分支:
claude/issue-6257-bulk-action-required-permissions - 会话:
session_01PD7tZG1vENc5peMaLQC1uD
范围按本单方案执行:
BulkActionDefSchema增加可选requiredPermissions(含 did-you-mean 键池与生成 artifacts 同步),schema 测试,examples/app-showcase补内联 update/delete +requiredPermissions标本(与 #6157 矩阵的showcase.restricted_ops/showcase.export_data对齐),并在 showcase UI 真机 A/B 实测后附证据。
Generated by Claude Code
- 分支:
已完成并开 PR:#6332(分支
claude/issue-6257-bulk-action-required-permissions,会话session_01PD7tZG1vENc5peMaLQC1uD)。真机 UI 实测矩阵(console dev :5190 → showcase
--fresh后端,admin 勾选全部 5 行,同一用户同一批记录):步骤 授予 purge_restricted声明批量条 baseline 仅平台能力 ['showcase.restricted_ops']4 个未加门按钮,两个带门按钮均不在 授予 showcase_ops+ showcase.export_data同上 Relabel (Ops) 出现,Purge 仍不在 声明翻转 同上 []Purge (Restricted) 出现 声明还原 同上 ['showcase.restricted_ops']Purge 再次消失 夹具(
relabel_ops/purge_restricted内联 def +e2e/bulk-capability-gate.spec.ts)永久保留在 showcase。spec 全量 8500 passed;showcase validate/typecheck/146 tests 全绿;check:generated10/10。过程中记录到一个独立缺口(另行立案,不在本单修):hono
current-user-endpoints的独立解析器不读sys_user_position,岗位绑定的能力到不了/me/permissions,因此正向对照改用sys_user_permission_set用户直绑验证。
Generated by Claude Code
BulkActionDefSchema是.strict()且没有requiredPermissions,所以内联批量定义声明不了能力门:而渲染侧早已在读这个键 —— objectui#3492 落地后
BulkActionBar就是按它过滤的:也就是说这是「declared ≠ enforced」的镜像面:enforced ≠ declarable。运行时认这个键、按它隐藏按钮,spec 却不让作者写出来。
现状盘点(对着合并后的 main 实测,三条断言全过)
requiredPermissionsbulkActions: ['my_action'](命名式)resolveBulkActions提升时转发动作上的声明(objectui#3492)bulkActionDefs: [{ name, operation:'custom', execution:'aggregate' }],name命中已声明动作objectDef.actions并把命中动作合并进 defbulkActionDefs: [{ …, requiredPermissions: [...] }]直写bulkActionDefs: [{ operation:'update' | 'delete', patch }]前两行意味着大多数场景今天就有出路(也是 schema 自己在
label描述里推荐的那条:「declare a real action and name it inbulkActionsto get localization」)。真正表达不出来的是最后一行:内联的
update/delete数据面批量定义。它按设计就不该去引用一个对象动作(它不派发动作,它是数据面 mass mutation),于是没有任何合法写法能给「批量删除」加上能力门 —— 而这恰恰是最需要门的一类按钮。方案
给
BulkActionDefSchema(packages/spec/src/ui/bulk-action.zod.ts)加:消费侧无需改动 —— objectui 的
BulkActionDef类型与BulkActionBar的过滤都已就位,加上 spec 声明即打通。配套:
examples/app-showcase里补一条内联delete+requiredPermissions的标本(#6157 的动作显隐矩阵已经把命名式的四种规格钉住了,缺的正是这一格)。验收
bulkActionDefs: [{ operation: 'delete', requiredPermissions: ['x'] }]能通过objectstack validate,且无权用户在批量条上看不到该按钮 —— 与列表工具栏 / 行内 kebab / 记录页头三面结论一致。