fix(plugin-grid): ObjectGrid 的 editable 键补 can(object,'update') 门 (#5143) - #5147
Merged
Merged
Conversation
) #4647 在 **ListView** 层关上了行内编辑的权限缺口(PR #5145):开关、紧凑 工具栏入口、以及 ListView 下发的 `editable` 值,三者都 AND 了对象的已解析 可编辑性与 `can(obj, 'update')`。 `ObjectGrid` 另有一扇**不经过 ListView** 的门:声明式编写的 `object-grid` schema 直接带 `editable: true`。组件其实早已算出该 principal 的判定 —— `permissionUpdate = can(objectName, 'update')`,就在上方几行 —— 却只 喂给行 kebab;三处行内编辑 props 读的是 schema 原值。于是同一个组件对 「该用户可否写这些记录」给出两个相反答案:kebab 正确隐藏 Edit,而点一下 单元格却把人放进编辑器,只能靠服务端 403 拦住。没有数据落库(服务端门是 牢的),代价是 UI 明知会失败还把用户走完一遍往返。 现在 `editable` / `renderCellEditor` / 承载保存取消列的 `rowActions` 读同 一个判定:作者声明的键 ∧ 对象已解析的可编辑性(ADR-0103 桶、 `userActions.edit`、服务端 effective API operations)∧ principal 自己的 授权。与 #4647 用的合取式逐字同构,两扇门因此无从漂移。 `rowActions` 随同一判定,是本 PR 单独核实后的定夺,不是顺手扩面:该列 **唯一**会被填充的状态就是行有 pending change 时的保存/取消对,而 ObjectGrid 从不把 `onRowEdit`/`onRowDelete`/`rowActionDefs` 传给 DataTable 的内建菜单(它们走 `columnsWithActions`),所以 `DataTableRowActionsMenu` 在这里每行都渲染 `null`。只关编辑而留下该列,无授权用户会拿到一条永远空 的尾列加表头 —— 一种今天并不存在的 grid 形态(不带 `editable` 的 schema 今天就不生成该列)。跟随同一判定,才使被门收紧后的 grid 与「本就不可编辑 的 grid」逐列一致。 fail-open 与本文件每个兄弟门一致:无 `PermissionProvider` 时 `can()` 答 `true`,无 objectName 的纯内联 data grid 解析到默认可写桶 —— standalone embed、Studio 设计器画布、无对象语义的内联表格行为不变。收紧只在「确实 存在一个对象可供判定」时才生效。 行为变化(如实记录):无 `update` 授权的 principal 在此类 grid 上不再进入 行内编辑,也不再看到为其服务的保存/取消列。有授权者不受影响。 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
反向验证 (c) 查出一条非显然事实,值得留成钉子而不是只写进 PR 正文: 本门与 #4647(PR #5145)ListView 那道**并非字面同一谓词**。ObjectGrid 解析 `objectName = dataConfig.object ?? schema.objectName`,ListView 的 `inlineEditOffered` 只读 `schema.objectName`。因此当对象身份**仅**经 `data: { provider: 'object', object: … }` 传入、无顶层 `objectName` 时, ListView 的 `schema.objectName ? … : true` 分支会落开,该形态**只由本门** 判定 —— 本门不是 ListView 那道的冗余副本。 新增钉子覆盖该形态(无授权 ⇒ 不进编辑态;有授权 ⇒ 照常)。变异验证:把 `objectName` 收窄回 `schema.objectName`,预判「仅此一例红」,实测恰好 1 红 11 绿 —— 钉子既咬得住又不误伤。 同时订正上一次提交里一句过强的注释:原文写「两门是同一谓词」,与刚验证出 的差异相抵触。改为如实说明幂等只成立于 ListView 路径,以及差异朝安全方向 跑。「上游已经设过门了」正是当初留下这扇门的推理,不该由注释再复述一遍。 纯注释 + 测试,产物行为不变。 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收:ACCEPT(session_01GTRjn8xBqp75dk7kFupVRt,objectui 分片,批次 23) 实物核验:merge-base 验收要点:
三件套照常:本评论 → undraft → auto-merge(SQUASH)。#5142(importPredicates)因 PR #5145 落 main 已解锁,PM 即将派发。 Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5143
问题
ObjectGrid早已算出当前 principal 的写判定 ——permissionUpdate = perms.can(objectName, 'update')—— 却只把它喂给行 kebab(resolveRowCrudAffordances)。三处行内编辑 props 读的是 schema 原值:editable: schema.editable ?? falserenderCellEditor: schema.editable ? … : undefinedrowActions: !!(schema.editable && hasActions)于是同一个组件对「该用户可否写这些记录」给出两个相反答案:无
update授权的 principal 打开一张 grid 块声明editable: true的 SDUI 页面,点单元格即进入编辑器,只能靠服务端 403 拦住;而同一批行的 kebab 正确隐藏了 Edit。没有数据落库(服务端门是牢的),代价是 UI 明知会失败还把用户走完一遍往返。卡面前提三条已逐条实测复核,全部成立(复核记录见 #5143 评论)。
与 #4647 / PR #5145 的关系
#4647 在 ListView 层关上了同一个洞(PR #5145 已落 main,本 PR 基线
dfc697554):开关、紧凑工具栏入口、以及 ListView 写进 grid schema 的editable值,都 AND 了isObjectInlineEditable(objectDef, effectiveApiOps) && can(objectName, 'update')。声明式编写的
object-gridschema 是不经过 ListView 的第二扇门。本 PR 用同构的合取式把它关上,两扇门因此无从漂移。改动
新增一个已解析的判定,三处 props 共读:
作者声明的键留在合取式左半,所以本门只收紧、不放宽:任何判定都无法为「没要求可编辑」的 grid 打开编辑。
rowActions随同一判定(卡面待核点的定夺)单独核实后确认应当随同,理由不是顺手扩面:该列唯一会被填充的状态,就是行有 pending change 时的保存/取消按钮对。ObjectGrid 从不把
onRowEdit/onRowDelete/rowActionDefs传给 DataTable 的内建菜单(它们走columnsWithActions另一列),所以DataTableRowActionsMenu在这里每行都渲染 null。只关编辑而留下该列,无授权用户会拿到一条永远空的尾列加表头 —— 一种今天并不存在的 grid 形态(不带editable的 schema 今天就不生成该列)。跟随同一判定,才使被收紧后的 grid 与「本就不可编辑的 grid」逐列一致。测试 (e) 正是钉这一条:editableNoGrant === neverEditable,并以editableWithGrant === neverEditable + 1作对照证明探针确实能分辨两种形态。fail-open 语义(与同文件兄弟门一致)
PermissionProvider⇒can()答true—— standalone embed、Studio 设计器画布行为不变;isObjectInlineEditable(null)解析到默认可写桶,且can()根本不会被调用。收紧只在「确实存在一个对象可供判定」时才生效。
反向验证(预判先写,commit 后变异还原)
(a) 摘门 —— 三处 props 还原为读 schema 原值。预判 7 红 / 4 绿(逐条点名),实测完全一致:红 = a / d / e / engine-owned /
userActions.edit: false/ effectiveOps 缺 update / opt-in 敌不过缺授权;绿 = b(有授权)/ c(无 Provider)/ narrows-only / 纯内联 grid。(b) 塞假 verdict ——
objectInlineEditable强制为false。预判 6 红 / 5 绿,实测完全一致:红 = b / c / d / e(对照半)/ effectiveOps 含 update 的后半 / 纯内联 grid;绿 = a / engine-owned /userActions.edit: false/ opt-in / narrows-only。两个方向都可观测,证明钉子不是「因为什么都没产出」而绿。(c) 自选方向:两条通道在门后的合成语义。 ListView 把
editable: inlineEdit && inlineEditOffered写进 grid schema,所以 ObjectGrid 的schema.editable已被同一合取式预先 AND 过一次,本门再 AND 一次。预判 c1「幂等」:A ∧ B 再 ∧ B = A ∧ B,ListView 路径行为不变 —— 实测符合(ListView 说 false 仍 false,说 true 且有授权仍 true;plugin-list 全量 +
ListView.permissions.test.tsx全绿)。预判 c2(非显然的一半)两门并非字面同一谓词:ObjectGrid 解析
objectName = dataConfig.object ?? schema.objectName,ListView 只读schema.objectName。故当对象身份仅经data: { provider: 'object', object: X }传入、无顶层objectName时,ListView 的schema.objectName ? … : true分支会落开,而 ObjectGrid 这门有对象可判 —— 预判严格更强。实测证实:该形态下无授权 ⇒ 不进编辑态(can(X,'update')确被调用),有授权 ⇒ 照常。也就是说本门在 dataConfig 寻址的 grid 上是唯一的门,不是 ListView 那道的冗余副本。这条非显然事实已留成第二个 commit 的钉子(而非只写进正文):变异验证把
objectName收窄回schema.objectName,预判「仅此一例红」,实测恰好 1 红 11 绿 —— 钉子既咬得住又不误伤。同一 commit 另订正了首个 commit 里一句过强的注释(原写「两门是同一谓词」,与此处实测相抵触)。测试
新增
packages/plugin-grid/src/__tests__/inlineEditPermissionGate.test.tsx(12 例),断言用户可见结果(点单元格是否出现编辑器 / 表头列数 / kebab 条目),而非产生它的 prop。kebab 两例与编辑两例刻意同文件:两个 affordance 的分歧本身就是本卡的缺陷,任何一半被重新打开都必须在这里红。pnpm exec vitest run packages/plugin-grid⇒ 76 files / 680 tests 全绿pnpm type-check⇒ 81/81pnpm check:control-bytes⇒ OK;另按[\x00-\x08\x0b\x0c\x0e-\x1f]自扫变更文件 ⇒ 无命中半径外发现
#5148 ——
showAddRow骑在作者声明的operations.create上、无can(object,'create')门(同族的 create 面)。已按 observation-class 挂finding、不挂pm:queue、不指派,可达性在卡面写明,留给 triage 定级。本 PR 不动它。同族
can(object, 'update');userActions.editInlineis declared in spec but has no consumer #4647 / PR fix(app-shell,plugin-list): 关联列表「+ New」消费 create 谓词,行内编辑开关补 update 权限门 (#4646, #4647) #5145 —— ListView 工具栏那一半(本 PR 的姊妹门)apiOperations而非调用者权限设门requiredPermissions