Repository navigation
【能力需求】plugin-approvals 审批请求可见性:对业务记录有读权者应可只读查看审批动态(或提供可见性 hook) #8652
Description
Activity
- addedpriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVP
on Aug 14, 2026 Triage: lands in
packages/plugins/plugin-approvals/src/approval-service.ts(visibleRequestIds()) ⇒domain:services. Type Feature. Escalated toneeds-user-decision.为什么进决策箱(而非直接入队):两条人工地板同时命中 —— ①功能新增类(卡面本身就是「能力需求」);②安全/权限边界:两个方案都在放宽「谁能读到审批记录」。放宽访问控制的「修复」是伪装的产品决定,分诊席不得代裁。
⚠️ priority:p0已保留,但请注意它不会让此卡离开决策箱 —— 优先级是排序,不是豁免;裁决之前它不可派发。现状判读与卡面一致(
visibleRequestIds()可见集 = 提交人 ∪ 当前审批人 ∪ 历史动作人,唯一旁路isOverrideActor()只认平台/租户管理员;ApprovalsPluginOptions无开关)。项目侧实测200 + {"data":[]}而非 403,确认是可见性过滤而非对象权限 —— 这个区分是干净的,省掉了一轮误诊。四棱卡面
- ① 平台长远合理性 — 方案 1。可见性是平台级的权限语义,应当被声明一次、平台强制一次。方案 2(hook)把一条权限判定导出给宿主项目,等于每个项目各自重新发明一遍访问控制规则,平台既无法约束也无法审计。
- ② 实测业务拉力 — 有,且已具名:Titanwind EHR 考核单三级审批,台账角色(设备管理员,非审批参与人)按业务说明书需看流转记录(steedos-labs/os-project-titanwind-ehr#1327,附逐账号 API 探针)。卡面另指出同形场景族(质量 QIF/NCR、零星派工审批)。拉力真实,但已确认消费者为 1 个,「场景族」尚属推断 —— 请按 1 个已证实消费者定价。
- ③ 防 AI 写错 — 方案 1,且这是两案差距最大的一棱。方案 2 让(常由 AI 编写的)宿主项目自己写一条安全相关的可见性谓词 —— 写错就是越权读,且平台看不见。方案 1 是闭合的、平台强制的一档。⛔ 方案 2 应视为风险最高的形状,不是「更灵活」的那个。
- ④ 创业期范围纪律 — 两案都是永久义务:方案 1 欠一个声明档位,方案 2 欠一个公开 hook API(更难收回)。若必须做,方案 1 的义务更窄;若拉力不足,
defer优于declare-and-maintain。
四棱指向一致(方案 1 优于方案 2),但「做不做」在人工地板上 ⇒ 交裁。
建议
若确认要做:方案 1,严格只读、按「对该业务记录有读权」派生,以
ApprovalsPluginOptions开关(默认关闭)启用 —— 默认关闭使其对现有安装非破坏性,开关是显式声明。⛔ 明确不做方案 2:导出权限判定给宿主项目,与「声明即强制」相悖。需要你回答的两个问题:
- 只读可见的粒度:审批动态全量(各级审批人 + 决策 + 时间 + 意见正文),还是只到「谁、何时、什么决策」而不含意见正文?意见正文常含敏感评价 —— 这条决定了能力面的大小。
- 开关粒度:插件级一档,还是 per-object 配置?(per-object 更贴合「台账可见、其它不可见」,但那是更大的一块永久义务。)
项目侧的替代方案(自建审批日志对象 + 每级写副本)会与平台能力重复并带存量回填负担 —— 如果裁决是 defer,建议在卡上给出具名重启条件,让项目侧知道等什么。
Generated by Claude Code
Maintainer ruling (delegated adjudication, batch 1/―; delegation 2026-08-15 verbatim 「决策你直接帮我做」, this batch confirmed verbatim 「同意」 in the live PM session): Option ① — built-in read-only visibility tier; Option ② (host hook) rejected.
A user with read access to the target business record may view that record's approval requests and full action history, read-only (no approval actions delivered). Enabled by a per-object or plugin-level switch, default OFF — existing deployments' confidentiality posture is unchanged; the downstream project opts in.
Rationale (four-prism): real p0 pull (Titanwind EHR, measured 200+empty for a reader holding record CRUD; QIF/NCR-class scenarios generalize; the downstream alternative — duplicating approval logs per app — is worse) · anchors on the EXISTING record-read permission rather than inventing a permission concept · a closed switch is auditable where a visibility hook outsources a security decision to host code — the exact surface where generated code silently over-exposes · one bounded tier vs a permanent extension-API obligation.
needs-user-decision→pm:queue;domain:servicesseat dispatches.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 28, 2026
背景(业务场景)
下游项目 Titanwind EHR(steedos-labs/os-project-titanwind-ehr)的「考核单」走三级审批(部门负责人→总经理→财务)。业务说明书要求:考核单台账的使用角色(设备管理员,非审批参与人)在单据详情中能查看审批流转记录(各级审批人+决策+时间+意见)。项目侧缺陷单:steedos-labs/os-project-titanwind-ehr#1327(已按平台能力受限挂起)。
现状(@objectstack/plugin-approvals@17.0.0-rc.6)
GET /api/v1/approvals/requests返回 ≥1 条时才渲染「审批」页签;ApprovalService.listRequests()→visibleRequestIds()过滤,可见集 = 提交人 ∪ 当前审批人 ∪ 历史动作人;唯一旁路isOverrideActor()只认平台/租户管理员;ApprovalsPluginOptions无任何可见性配置开关,项目侧无 hook 点。实测硬证据(项目侧真机):对一张走完三级审批的单据,非参与人账号(对该业务记录本身有 CRUD_ALL 读权)调
GET /api/v1/approvals/requests返回 HTTP 200 +{"data":[]}(不是 403,即非对象权限问题);同一账号读业务记录本体 200、全字段可见。结果:该角色在 UI 上完全看不到审批页签与任何审批动态。诉求(二选一或都做)
ApprovalsPluginOptions开关或 per-object 配置启用;visibleRequestIds()逻辑。影响面
不止单一项目:任何「台账/主管角色需要查看审批进度但不是审批参与人」的场景(质量 QIF/NCR、考核单、零星派工审批等)都会撞到同一堵墙;项目侧唯一替代方案是自建审批日志对象并在每级审批写副本,与平台能力重复且有存量回填负担。
环境
@objectstack/plugin-approvals@17.0.0-rc.6(runtime/objectql/spec 同版本);复现细节与逐账号 API 探针记录见 steedos-labs/os-project-titanwind-ehr#1327 的开发排查报告评论。