fix(hooks): 流程按钮按发起人校验岗位,系统免检收窄到无发起人的纯系统写入 - #33
Conversation
代码评审报告(os-project-dev-review)档位:全量档(依据:改审核状态机唯一真值 结论:不可合并 通用质量(/code-review 高档)发现 1 条阻塞级正确性缺陷(F1)、1 条应修(F2)、4 条记录。除 F1 外,改动本身口径统一、注释到位: 专属核查(逐条)1 改动面越界:✅ 无越界。 2 降级对账:
3 三禁痕迹:✅ 无。 4 硬拍板落地:✅。 调用面抽查(逐调用点)
补记(不阻塞): 运行期复核(独立实例,端口 3120,独立库)
D/E/F 合起来把 F1 钉死:基线不发生,本分支约 30% 概率发生。 发现清单
阻塞与应修各 1 条,未清零。结论:不可合并。 |
评审退回修复(F1 阻塞 + F2 应修)已在原分支 分支已并到最新 main: 修复点 ↔ 提交
成因确认:评审的判断与实测一致。 F2 的替身改到什么程度原替身
新增 6 条用例(本文件 23 → 29 条),其中两条直接盯 F1:
两半修复各被一条用例单独钉住(逐一实测过):
三轮(实际四轮)结果
连续归档明细: 实例:端口 3113(起之前 未扩围:记录级 4 条( |
代码评审报告 —— 复查轮(os-project-dev-review)按轮次纪律只复查两个修复点 + 是否引入新问题,不重开全面评审,记录级 4 条不动。 结论:可合并 ① 🔴 F1 —— 已修复,复现不出来了代码核对(两条都在,缺一不可):
审计戳白名单没有顺带放开业务字段:集合恰为
运行期复核(空库软件档案 + 默认档案):
上一轮同样的做法失败率约 30%(连续归档 7 张挂 3 张、e2e 两轮挂 1 轮 T28);本轮 14 次归档动作(11 张 + 3 轮 e2e)全部干净,无一例「已归档无快照」残留。F1 判定已消除。 ② 🟡 F2 —— 已修复,替身够真,新增用例确实会红时序复刻核对(逐条比对平台源码):
两处小差异,均不影响结论:替身的 回退抽验(逐条实跑,跑完已还原,
与开发方自述完全一致。新增 6 条用例(23 → 29)是真断言,不是占位:归档用例断言 ③ 合入 main 后的交互 —— 未见问题
本轮新发现
无新增 🔴 / 🟡。 阻塞与应修均已清零。结论:可合并。 |
平台 17.2.0 执行动作体(按钮)用的上下文是「{ ...调用者上下文, isSystem: true }」,
发起人的 userId 原样保留(见 objectstack-ai/objectstack#2849)。于是「用户点了按钮」
和「系统自己写」在 isSystem 这一位上完全一样,以它为闸的岗位校验对按钮路径整体失效:
人力审核能越过领导审批节点通过,部门填报人员能审批和归档,加减分的岗位分离也一并落空。
经 REST 直接写入不受影响 —— 同一条规则在两条路径上是两个口径。
判据换成「有没有发起人」:isSystemWrite = 有系统标记且 actorId 为空。真正的系统写入
(种子、脚本、hook 内部自动推进)没有发起人,这一位把两者分得开。hasPosition 一处收口,
sheet / check-task / bonus / adjustment 四个 hook 的岗位闸同时恢复。
hook 内部自发的两处写入(全部分公司确认后自动推进填报单、驳回时重置核对任务)改用
sysNoActor —— 在 sudo 基础上摘掉发起人、保留租户与事务信息。它们不是任何人点出来的,
按发起人校验既没有对象也没有意义;而且节点审核岗位按方案可改(《设计方案》10.1 第 9 条),
不能指望「最后一个确认的人恰好持有该节点岗位」。
非岗位类的 isSystem 闸中,按钮动作体够得到的(加减分与数据调整的归档锁、已批准锁、
填报单的直改 status 与归档锁)同口径收紧;够不到的(填报单新建/删除保护、明细锁定、
快照与结果不可变)按原样保留并在代码里逐处写明理由 —— 收紧会打断发布、调整落地与
归档快照这些带发起人的系统写入。
按钮可见性未按岗位收敛:动作 visible 的 current_user.positions 装的是 auth 层角色,
拿不到业务岗位,已上报 objectstack-ai/objectstack#15136,hook 拦截为唯一防线。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
F1 —— 归档偶发半途失败,填报单永久卡在「已归档但无快照」:
after 阶段第一步 `updateById(id, {pending_action:null, action_reason:null})` 原本走
带发起人的 sys(ctx),isSystemWrite 为假,于是落进归档锁分支;而平台内建的
sys_stamp_audit_update(object '*',priority 10)在本 hook(100)之前把 updated_at /
updated_by 写进同一个 ctx.input,这两个字段不在 SCRATCH_FIELDS 白名单里,touched 非空
→ 抛 KPI_SHEET_ARCHIVED,其后的 writeReview / createSnapshot / regenerateResults 全不
执行,422 返回操作人;归档只能从「已通过」发起,重试被状态机拒绝,该单无法自愈。
两处一起改,缺一条都还会漏:
1. 清场写入改用 sysNoActor —— 它是系统动作,不是任何人点出来的,应当落在免检那一侧;
2. 归档锁的 touched 排除平台审计戳(created_at / created_by / updated_at / updated_by)
—— 平台盖的戳不是用户改数据,不该被算成「改动」。
F2 —— 单测替身没有 sudo,sys() / sysNoActor() 双双退化成它自己,after 阶段与 sysNoActor
的真实行为零覆盖,F1 才得以在 23 条全绿的情况下逃逸。替身重写:
- FakeScopedContext 实现 sudo() 语义,executionContext 是可写实例属性(与平台一致),
仓库在 object() 调用的一刻捕获上下文快照;
- FakeEngine 复刻平台写入时序:审计戳先于业务 hook 写同一个 input,且 hook 内部经
ctx.api 发起的写入会再穿一遍 hook 链;
- 新增「归档 after 阶段清场 + 快照 + 审核记录」与「清场写入确实无发起人」两组用例,
外加归档锁只锁业务字段的三条。两半修复各被一条用例单独钉住:只回退 sysNoActor 挂
1 条,只回退审计戳白名单挂 1 条,两条都回退挂 3 条(已逐一实测)。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
关联工作项 #28。
问题
四个流程按钮(提交填报 / 审核通过 / 驳回 / 归档)是带
api.write的 JS 动作体。平台 17.2.0 以「受信任」方式执行动作体,上下文是{ ...调用者上下文, isSystem: true }—— 发起人的userId原样保留(见 objectstack-ai/objectstack#2849)。于是「用户点了按钮」和「系统自己写」在isSystem这一位上完全一样,hasPosition()第一行if (isSystem(ctx) || !position) return true把按钮路径整体放行:经 REST 直接 PATCH 不受影响(非系统上下文,岗位闸正常)—— 同一条规则在两条路径上是两个口径。
改法
判据从「有没有系统标记」换成「有没有发起人」:
真正的系统写入 —— 种子、脚本、hook 内部自动推进 —— 是没有发起人的;按钮路径永远带发起人。
hasPosition的免检条件一处收窄,sheet / check-task / bonus / adjustment 四个 hook 的岗位闸同时恢复,按钮路径与 REST 路径回到同一口径。hook 内部自发的两处写入改用新增的
sysNoActor(ctx)(在sudo()基础上摘掉发起人,租户与事务信息原样保留):这两处不是任何人点出来的,按发起人校验既没有对象也没有意义;而且节点审核岗位是按方案配置的(《设计方案》10.1 第 9 条「可改审核岗位」),不能指望「最后一个确认的人恰好持有该节点岗位」。
非岗位类的
isSystem闸按「按钮动作体够不够得到」分两类处理,逐处在代码里写明理由:plan.hook.ts,本单禁触碰)归档锁额外把
pending_action/action_reason两个流程暂存字段排除在「改动」之外 —— 那是 after 阶段的清场写入,不是改数据。未做与出口
按钮可见性未按岗位收敛(工作项范围 2):实测发现动作
visible的 CEL 里current_user.positions装的是 auth 层角色(["user","org_member"]),不含业务岗位 —— 按岗位写的谓词对所有人静默判假,持有该岗位的人也看不到按钮。已按支线 B 上报平台 objectstack-ai/objectstack#15136(现象、最小复现、期望能力、平台版本),不在平台仓提交修复。按工作项正文的明文授权,保留 hook 拦截为唯一防线。验证
pnpm verify绿:validate ✓ / typecheck ✓ / test ✓(7 文件 113 条,原 90 + 新 23)/ i18n 529 key in sync(零元数据改动,无词条变化)。scripts/software-flow.mjs54/54 PASS、scripts/e2e-flow.mjs73/73 PASS,覆盖发布生成填报单、核对自动推进、驳回重置、加减分与调整落地重算、归档快照。测试报告与需求符合度清单挂在 #28 评论;截图走
acceptance-evidence分支 commit70ae4a0f9fd7e8ed3a4cba153ac54f819005ad32。🤖 Generated with Claude Code