Skip to content

流程按钮绕过岗位校验:动作体以受信任身份写入被 hook 当系统写入免检,任何岗位可越节点审批/归档 #28

Open

Description

@baozhoutao

背景(来源:#23 UI 实测 K-3,调度员 2026-09-03 已核实成因,维护者拍板立单)

人力审核(仅持 kpi_hr_reviewer 岗位)对「领导审批中」的填报单点「审核通过」真的生效;部门填报人员在自己单上能看到「归档」「审核通过」「驳回」按钮。成因:四个流程按钮(提交填报 / 审核通过 / 驳回 / 归档)是带 api.write 能力的 JS 动作体,平台 17.2.0 以「受信任」方式执行动作体(不携带调用者 ExecutionContext,见运行时 buildActionEngineFacade 注释与 objectstack#2849),写入到达 src/hooks/sheet.hook.ts 时 ctx.session.isSystem === true,hasPosition 第一行 if (isSystem(ctx) || !position) return true 直接放行,「非系统写入不得直改 status」等其它以 isSystem 为闸的规则同样失效。经 REST 直接 PATCH pending_action 不受影响(hook 正常拦截)。

目标

按钮发起的每一步流程动作都按发起人的岗位校验,与 REST 路径同一口径;按钮只对当前节点的岗位显示。

范围

  1. src/hooks/sheet.hook.ts(状态机唯一真值,本单允许改):岗位校验不再以 isSystem(ctx) 免检。规则:只要本次写入带有发起用户(ctx.user / actorId(ctx) 可解析)且写入的是 pending_action,就按发起人校验 requiredPositionFor 的岗位;isSystem 免检仅保留给没有发起用户的纯系统写入(发布生成填报单、重算、归档快照回写)。同一口径检查 src/hooks/util.ts 的 hasPosition 与其它 hook(bonus / adjustment / check-task / dispute)里以 isSystem 为闸的岗位校验,凡是「用户点按钮 → 动作体写入」能到达的分支一并修正;纯系统写入路径不能被误伤(发布、重算、种子、脚本仍要能跑)。
  2. 按钮可见性:src/actions/index.ts 四个填报单动作的 visibleWhen(或平台等价机制)按「当前状态对应节点的审核岗位 = 当前用户岗位」收敛;归档只对人力审核显示。若平台的 visibleWhen 表达式拿不到当前用户岗位,如实记录并保留 hook 拦截为唯一防线,把平台能力缺口写进「需拍板事项」(只上报不修复)。
  3. 单测:test/ 中为 sheet.hook 补「非本节点岗位经动作体路径发起 approve/reject/archive 被拒」「本节点岗位放行」「系统写入(无发起用户)放行」三类用例;现有 90 个用例不得回退。
  4. 文案:拒绝提示沿用现有三段式 KPI_SHEET_POSITION 文案。

不做

不改流程节点顺序与驳回口径(第 10 章第 8 项);不改 src/lib/scoring.ts;不改平台包;不做 K-1(分公司填报人)、K-5(批量确认)。

方案分级与放行(调度员)

改已有状态机 = 高风险。放行方向 = 上述范围 1 的 A 方案(按发起人校验、系统免检收窄),维护者 2026-09-03 已知悉并同意立单派发。开发子 agent 开工前仍须在本单评论输出「需求理解 + 实现方式 + 受影响写入路径清单(逐条标注 系统/用户)」,与放行方向一致即可直接开工;若发现必须改动 pending_action 机制本身或平台行为,停手升级。

验收标准

  1. 用岗位账号在界面点按钮:人力审核对「领导审批中」的单点「审核通过」被拒并显示岗位提示;部门填报人员对任何单点「审核通过 / 驳回 / 归档」被拒(若按钮已隐藏则以「不可见」为证据);分管领导对「领导审批中」的单审批通过正常;人力审核归档正常。
  2. 同一组动作经 REST 直接 PATCH pending_action 的结果与界面一致。
  3. 发布方案生成填报单、加减分/数据调整落地后的重算、归档快照回写等系统路径全部正常(scripts/software-flow.mjs 在空库软件档案实例上仍 54/54 PASS;scripts/e2e-flow.mjs 在默认档案上仍全 PASS)。
  4. pnpm verify 绿,新增单测通过。
  5. 测试报告(含岗位账号截图,成功与拒绝分支各有)与需求符合度清单挂本单评论,截图走 acceptance-evidence 40 位 SHA 图链。

测试计划草稿

T1 单测三类用例 | T2 界面:人力审核越节点审批被拒 | T3 界面:部门填报人员看不到/点不动审核与归档 | T4 界面:分管领导正常审批、人力审核正常归档 | T5 REST 同口径 | T6 software-flow 54/54 + e2e-flow 全 PASS | T7 pnpm verify

Activity

  1. self-assigned this
    on Sep 3, 2026
  2. baozhoutao commented on Sep 3, 2026

    @baozhoutao
    ContributorAuthor

    认领(调度员占坑,2026-09-03)

  3. baozhoutao commented on Sep 4, 2026

    @baozhoutao
    ContributorAuthor

    开工前方案说明(开发子 agent,2026-09-03)

    一、需求理解

    问题:四个流程按钮是带 api.write 的 JS 动作体。平台 17.2.0 的动作体以「受信任」方式执行,写入到达 hook 时 ctx.session.isSystem === true,于是 hasPosition() 第一行 if (isSystem(ctx) || !position) return true 无条件放行 —— 岗位闸对按钮路径整体失效;REST 直接 PATCH 走非系统上下文,岗位闸正常。结果是同一条规则在两条路径上两个口径。

    要达到的目标(《设计方案》第 9 章:流程按钮由系统按当前节点的审核岗位把关):按钮发起的每一步流程动作,按发起人的岗位校验,与 REST 路径同一口径;并让按钮只在「当前节点岗位 = 当前用户岗位」时显示。

    关键事实(已核实平台源码,非推测):平台构造动作体上下文用的是 buildActionExecutionContext(ec) = { ...ec, isSystem: true }(@objectstack/runtime@17.2.0),发起人的 userId 被完整保留;ObjectQL.buildSession() 把 userId 与 isSystem 一并透给 hook,buildUser() 在 userId == null 时才返回 undefined。所以「有没有发起人」是可用的判别位,actorId(ctx) 在按钮路径上能拿到真实发起人 —— 放行方向 A 可以落地,不需要改 pending_action 机制、不需要平台改动。

    二、实现方式

    1. src/hooks/util.ts
      • 新增 isSystemWrite(ctx) = isSystem(ctx) && actorId(ctx) === null ——「纯系统写入」的唯一判据:系统标记 且 没有发起用户。
      • hasPosition() 的免检条件从 isSystem(ctx) 收窄为 isSystemWrite(ctx)。一处改动,让 sheet / check-task / bonus / adjustment 四个 hook 的岗位闸在按钮路径上同时恢复。
      • 新增 sysNoActor(ctx):在 sudo() 的基础上摘掉发起人(保留租户与事务信息),供 hook 内部自动推进的写入使用 —— 这类写入不是任何人点出来的,按发起人校验没有意义,必须走系统免检。
    2. src/hooks/sheet.hook.ts:岗位闸(第 73 行)因 hasPosition 修正自动生效;把「驳回时重置核对任务」改用 sysNoActor(否则会被 check-task 收紧后的「已确认不能撤回」误伤);「非系统写入不得直改 status」收紧为按发起人判定;归档锁把 pending_action / action_reason 两个流程暂存字段排除在「改动」之外(after 阶段的清场写入),规则本身同步收紧。
    3. src/hooks/check-task.hook.ts:去掉 isSystem 早退(这正是「确认无误 / 提出争议」两个按钮绕过 kpi_branch_checker 的口子);全部确认后的自动推进改用 sysNoActor 写入。
    4. src/hooks/bonus.hook.ts / src/hooks/adjustment.hook.ts:归档锁、已批准锁两条 isSystem 闸同口径收紧(按钮可达);岗位闸由 hasPosition 修正覆盖。
    5. src/actions/index.ts:四个填报单动作的 visible 按「当前节点岗位 = 当前用户岗位」收敛,归档只对人力审核显示。前置待验:平台 record/list 动作的 visible 表达式作用域里是否绑定 current_user(页面组件谓词绑定了,动作按钮这一条我还没在运行期证实);另外《设计方案》10.1 第 9 条明确「可改审核岗位」,节点岗位是按方案配置的,单靠 record.status 推不出所需岗位。这两点会在 dev 实例上实测后如实记录,拿不到就保留 hook 拦截为唯一防线并把能力缺口写进报告(只上报不修复)。
    6. 单测:test/ 补 sheet.hook 三类用例(越节点岗位经动作体路径被拒 / 本节点岗位放行 / 无发起用户的系统写入放行),现有 90 例不回退。
    7. 文案:拒绝提示沿用现有三段式 KPI_SHEET_POSITION,不新增文案。

    三、受影响写入路径清单(逐条标 系统 / 用户)

    # 写入路径 ctx.session 实况 归类 本单处置
    1 按钮「提交填报 / 审核通过 / 驳回 / 归档」→ 动作体 → 写 pending_action isSystem=true + 发起人 userId 用户 改为按发起人校验岗位(本单核心修正)
    2 REST 直接 PATCH pending_action isSystem=false + userId 用户 口径不变,现状已拦截
    3 核对任务全部确认 → check-task afterUpdate → 写 sheet pending_action='approve' isSystem=true + 最后确认人 userId 系统 改用 sysNoActor,免检(节点岗位可被方案改配,不能指望「最后确认人恰好持有该岗位」)
    4 SheetAfterTransition 清空 pending_action / action_reason isSystem=true + userId 系统 归档锁排除这两个字段;updated_by 归属不变
    5 SheetAfterTransition 驳回时把核对任务重置为 pending isSystem=true + userId 系统 改用 sysNoActor(否则被收紧后的核对 hook 误伤)
    6 发布方案 → plan.hook 生成填报单(insert) isSystem=true + 发布人 userId 系统 不动:plan.hook.ts 本单禁触碰,且无任何按钮能到达填报单 insert;新建填报单保护维持 isSystem 免检
    7 归档快照 createSnapshot → 写 kpi_snapshot isSystem=true + userId 系统 不动(immutable hook 维持 isSystem 免检;无按钮可达)
    8 regenerateResults → 写 kpi_result isSystem=true + userId 系统 同上,不动
    9 writeReview → 写 kpi_review_record isSystem=true + userId 系统 同上,不动
    10 按钮「确认无误 / 提出争议」→ 动作体 → 写 kpi_check_task.status isSystem=true + 发起人 userId 用户 去掉 isSystem 早退,恢复 kpi_branch_checker 岗位闸
    11 按钮「批准 / 否决」加减分 → 写 kpi_bonus.status isSystem=true + 发起人 userId 用户 岗位分离闸恢复;归档锁 / 已批准锁同口径收紧
    12 按钮「提交审批 / 批准并落地 / 否决」调整申请 isSystem=true + 发起人 userId 用户 同上
    13 调整落地 → 写 kpi_entry_line isSystem=true + 审批人 userId 系统 不动:填报明细锁定闸必须对这条路径免检,且无按钮直接写明细
    14 种子 / scripts/*.mjs 建链 / backfill isSystem=true,无 userId 系统 天然属于「纯系统写入」,免检口径不变
    15 按钮「采纳 / 不采纳」指标争议 isSystem=true + 发起人 userId 用户 不做:该 hook 现状就没有岗位闸,本单不新增规则(超出范围)

    四、与放行方向的一致性

    与调度员放行的范围 1 A 方案一致:按发起人校验、系统免检收窄到「没有发起用户」。没有改动 pending_action 机制本身,没有改动平台行为,没有改流程节点顺序与驳回口径。按此开工。

  4. baozhoutao commented on Sep 4, 2026

    @baozhoutao
    ContributorAuthor

    测试报告 —— #28 流程按钮岗位校验(开发自测)

    结论:通过。 7 项测试计划(T1~T7)全部通过;新增 23 条单测,原有 90 条零回退;scripts/software-flow.mjs 54/54 PASS、scripts/e2e-flow.mjs 73/73 PASS;pnpm verify 绿。

    按钮路径与 REST 路径现在是同一口径:同一个账号、同一个动作,两条路径给出同一条三段式岗位提示。

    一处如实记录:范围 2「按钮可见性收敛」未实现,原因是平台能力缺口(动作 visible 表达式拿不到当前用户的业务岗位),已上报 objectstack-ai/objectstack#15136,hook 拦截作为唯一防线。详见下文 T2 与需求符合度清单。


    一、测试环境

    项 值
    分支 / 基线 issue-28-position-guard,基线 main @ a760651
    实例 OS_PORT=3113,OS_DATABASE_URL=file:./.objectstack/issue-28-position-guard.db,OS_SEED_PROFILE=software(空库重建)
    档案 软件公司演示档案(8 部门 + 3 分公司,11 张填报单,四节点流程)
    账号 scripts/software-people.mjs 建的岗位账号,口令统一;界面操作全部用岗位账号,不用管理员
    界面 Playwright chromium,1440×900,zh-CN;截图为真实界面
    降级说明 按 CLAUDE.md H2「暂不具备双实例条件」,本轮为同会话自测,测试计划先落盘再逐条对照执行

    二、逐项结果

    编号 测试项 结果 证据
    T1 单测三类用例(越节点被拒 / 本节点放行 / 无发起人系统写入放行) ✅ test/position-guard.test.ts,23 条;pnpm test 113 passed(原 90 + 新 23)
    T2 界面:人力审核越节点审批被拒 ✅ 见 §3 T2
    T3 界面:部门填报人员点审核通过 / 驳回 / 归档被拒 ✅ 见 §3 T3
    T4 界面:分管领导正常审批、人力审核正常归档 ✅ 见 §3 T4
    T5 REST 同口径 ✅ 见 §3 T5,7/7 PASS
    T6 software-flow 54/54 + e2e-flow 全 PASS ✅ 见 §3 T6
    T7 pnpm verify 绿 ✅ 见 §3 T7
    T2 附 按钮可见性按岗位收敛 ⚠️ 平台受限 见 §4,已上报平台

    三、执行记录与证据

    T2 人力审核(马丽,仅持「人力审核」岗位)对「领导审批中」的填报单点「审核通过」

    操作:以 hr.reviewer@kpi.demo 登录 → 打开「2026 年第 3 季度考核 · 销售部」(状态:领导审批中)→ 点「审核通过」→ 弹窗点「确认」。

    • 点击前(按钮可见,状态条停在「领导审批中」):
    • 点击后被拒,提示三段式完整:「操作失败:当前节点「领导审批」需要由对应岗位处理,你没有该岗位。如需处理,请联系管理员分配岗位。」;状态仍是「领导审批中」:

    修改前的行为:同一操作会成功推进 —— 这正是本单要修的缺陷。

    T3 部门填报人员(赵敏 / 李娜,仅持「部门填报人员」岗位)在自己的单上点审核类按钮

    动作 单据状态 结果
    审核通过 领导审批中(销售部) 被拒,岗位提示
    驳回(原因已填) 领导审批中(测试部) 被拒,岗位提示
    归档 已通过(销售部) 被拒,岗位提示
    • 点「审核通过」被拒:
    • 点「驳回」并填写原因(证明不是被必填校验挡住的):
    • 提交后被拒:
    • 点「归档」并在确认框点「继续」后被拒:

    按钮仍然可见(可见性收敛受平台限制,见 §4),但点了拿不到结果 —— 拦截在 hook,数据零变化。

    T4 正常分支

    • 分管领导(冯薇,市场线,分管销售部)对「领导审批中」的单点「审核通过」→ 成功,提示「已审核通过,流程进入下一节点。」,状态推进到「已通过」,最终审批人盖章为冯薇:
    • 人力审核(马丽)对「已通过」的单点「归档」→ 成功,提示「已归档并生成快照。」,状态「已归档」,快照 1 条:

    扩围验证(同因同域,一并修的分支)——加减分的岗位分离经按钮路径也恢复了:

    • 人力审核(马丽)登记加减分后自己点「批准」→ 被拒:「审批加减分失败:只有人力负责人岗位可以审批加减分。请转交人力负责人处理。」
    • 人力负责人(何平)点「批准」→ 成功,「加减分已批准,计入最终得分。」

    修改前:这两个按钮同样以受信任身份写入,岗位分离在按钮路径上整体失效(REST 路径一直是好的)。

    T5 REST 同口径(7/7 PASS)

    以研发部填报单为对象,岗位账号经 REST 直接 PATCH:

    PASS T5-1 REST:人力审核在本节点通过 — status=200
    PASS T5-2 REST:人力审核越节点审批被拒,提示与界面一致 — {"error":"操作失败:当前节点「领导审批」需要由对应岗位处理,你没有该岗位。如需处理,请联系管理员分配岗位。","declaredCode":"KPI_SHEET_POSITION"}
    PASS T5-3 REST:部门填报人员审批被拒 — 同上提示
    PASS T5-4 REST:分管领导本节点通过 — status=200 sheet=approved
    PASS T5-5 REST:部门填报人员归档被拒 — 同上提示
    PASS T5-6 REST:人力审核归档成功 — status=200
    PASS T5-7 REST:直接改 status 仍被拒 — declaredCode=KPI_SHEET_DIRECT_STATUS
    {"passed":7,"failed":0}
    

    界面与 REST 的提示文案、拒绝码逐字相同,验收标准 2 成立。

    T6 系统路径回归

    • scripts/software-flow.mjs(空库软件档案):54/54 PASS。其中直接压到本次改动的两条系统路径:
      • PASS T8f 3 家分公司全部确认后自动推进到人力审核中 — status=hr_reviewing(hook 内部自动推进走无发起人系统上下文,未被岗位闸误伤)
      • PASS T9b 人力审核驳回 → 退回上一节点(分公司核对),核对任务重置为待核对 — 任务=pending,pending,pending(驳回重置核对任务同样走无发起人系统上下文,未被收紧后的「已确认不能撤回」误伤)
      • 另有 T3 发布成功、T4a 发布生成 11 张填报单、T16a 归档快照生成、T14a/T14b 调整落地后重算 —— 发布、快照、重算三条系统路径全部正常。
    • scripts/e2e-flow.mjs(空库默认档案):73/73 PASS(该脚本当前用例数为 73)。

    T7 门禁

    pnpm verify → validate ✓(17 Objects / 184 Fields / 16 Actions)
                → typecheck ✓
                → test ✓ Test Files 7 passed, Tests 113 passed
                → i18n:extract:check ✓ 1 bundle in sync(529/529 key,未改任何 label,无需重生成)
    

    四、平台能力缺口(如实记录,只上报不修复)

    范围 2「四个填报单动作的 visible 按当前节点岗位收敛」未实现。

    实测经过(在本 worktree 的 dev 实例上做的探针实验,做完已把 src/actions/index.ts 恢复原样,最终提交里不含探针):

    1. 把归档动作的 visible 改成 ... && ('kpi_hr_reviewer' in current_user.positions) → 按钮对所有人消失,包括持有该岗位的马丽;控制台无 CEL 告警。
    2. 换成 has(current_user.id) → 按钮出现;换成 has(current_user.positions) → 按钮也出现。说明根与字段都绑上了,不是表达式写错。
    3. 对照接口:
      • GET /api/v1/auth/me/permissions → positions: ["org_member","kpi_hr_reviewer","everyone"](服务端授权用的就是这一组);
      • GET /api/v1/auth/get-session → user.positions: ["user","org_member"] —— Console 的 current_user 取自这里,业务岗位不在其中。

    结论:动作 visible 里的 current_user.positions 装的是 auth 层角色,不是安全层岗位,按岗位收敛的谓词一律静默判假。已按支线 B 上报平台:objectstack-ai/objectstack#15136(含现象、最小复现、期望能力、平台版本)。本单不在平台仓提交修复。

    应用侧现状:四个按钮的可见性仍只按 record.status 判定,拦截由 hook 承担(T2 / T3 的截图就是这条防线在工作)。另需注意:《设计方案》10.1 第 9 条明确「可增删节点、可改审核岗位」,节点审核岗位是按方案配置的,即使拿得到岗位,record.status 也推不出所需岗位 —— 要做到配置忠实的可见性,还需要把「当前节点所需岗位」落到填报单上(见需求符合度清单的出口说明)。

    五、缺陷与遗留

    自测过程中未发现本单引入的缺陷。以下为发现但本单不做的既有问题(不属于本单范围,记为遗留):

    1. 动作体拒绝提示带 KpiError: 前缀。按钮路径的错误经 /api/v1/actions/... 返回时,message 被拼成 KpiError: 操作失败:…,界面 toast 原样显示;REST 路径返回的是干净的 操作失败:…。文案本身是合规的三段式,前缀是异常类名泄漏(std-copy「无异常原文」)。本单只改口径不改文案(范围 4),故未动;修法有两条:应用侧在动作体里 catch 后重抛纯消息,或上报平台统一错误呈现。
    2. 归档被拒时的提示点错了节点名。归档要求的是「人力审核」岗位,但提示句里的节点名取的是流程最后一个节点(「领导审批」),读起来指向错误的岗位。属于既有文案缺陷,本单沿用现有文案未改。
    3. 上述两条都是本次修改让它们从「不可达」变成「可见」,不是新引入的。
  5. baozhoutao commented on Sep 4, 2026

    @baozhoutao
    ContributorAuthor

    需求符合度清单 —— #28

    逐条对照工作项正文的「范围」「不做」「验收标准」。三态:✅ 完整实现 / ⚠️ 有偏差 / ❌ 未实现。非 ✅ 条目均带出口链接。

    范围 1 —— hook 岗位校验按发起人

    # 需求原文要点 态 说明
    1.1 src/hooks/sheet.hook.ts 岗位校验不再以 isSystem(ctx) 免检 ✅ 免检判据换成 isSystemWrite(ctx)(系统标记 且 无发起人),在 hasPosition 一处收口,填报单流程推进的岗位闸随之生效
    1.2 只要写入带发起用户且写的是 pending_action,就按发起人校验 requiredPositionFor 的岗位 ✅ SheetTransitionHook 的 pending_action 分支无条件走 hasPosition(ctx, requiredPositionFor(...));发起人由 actorId(ctx) 解析(ctx.user.id → ctx.session.userId)
    1.3 isSystem 免检仅保留给没有发起用户的纯系统写入(发布生成填报单、重算、归档快照回写) ⚠️ 岗位校验(hasPosition)已完全按此收窄。但另有 4 处非岗位类的 isSystem 闸按原样保留:① 填报单新建保护 ② 填报单删除保护 ③ 填报明细提交后锁定 ④ 快照 / 审核记录 / 结果的不可变闸。理由逐条:发布生成填报单由 plan.hook.ts 以带发起人的系统上下文 insert,而 plan.hook.ts 本单禁触碰,收紧会直接打断发布;调整落地写明细、归档写快照、审批写结果同理,收紧会误伤。这四处没有任何按钮的动作体能到达(填报单无 insert/delete 型动作,明细与快照 / 结果无任何动作),REST 路径仍走非系统上下文照拦。差异已在代码注释逐处写明理由
    1.4 同一口径检查 src/hooks/util.ts 的 hasPosition ✅ hasPosition 首行由 isSystem(ctx) || !position 改为 !position || isSystemWrite(ctx);新增 isSystemWrite 与 sysNoActor 两个具名口径,含成因注释
    1.5 其它 hook(bonus / adjustment / check-task / dispute)里以 isSystem 为闸的岗位校验,凡「按钮 → 动作体写入」能到达的分支一并修正 ✅ check-task:去掉 isSystem 早退,「确认无误 / 提出争议」两个按钮恢复 kpi_branch_checker 岗位闸;bonus:归档锁与已批准锁收紧,岗位分离闸随 hasPosition 恢复(界面已实测,见测试报告 T4 扩围);adjustment:归档锁与已批准锁收紧,审批岗位闸随 hasPosition 恢复。dispute 现状就没有岗位闸,本单只修「失效的闸」,不新增规则(新增属需求变更)
    1.6 纯系统写入路径不能被误伤(发布、重算、种子、脚本仍要能跑) ✅ 两处 hook 内部自发写入改用 sysNoActor(核对全部完成后的自动推进、驳回时重置核对任务),落在免检侧;software-flow 54/54、e2e-flow 73/73 全 PASS,覆盖发布 / 重算 / 快照 / 调整落地 / 种子

    范围 2 —— 按钮可见性

    # 需求原文要点 态 说明与出口
    2.1 四个填报单动作的 visibleWhen 按「当前状态对应节点的审核岗位 = 当前用户岗位」收敛 ❌ 平台能力受限。动作 visible 的 CEL 里 current_user.positions 装的是 auth 层角色(["user","org_member"]),不含业务岗位;按岗位写的谓词对所有人静默判假,持有该岗位的人也看不到按钮。实测三步与两个接口对照见测试报告 §4。出口:已上报平台 → objectstack-ai/objectstack#15136(含现象、最小复现、期望能力、平台版本);按本工作项正文范围 2 的明文授权(「拿不到当前用户岗位…保留 hook 拦截为唯一防线」)保留 hook 拦截
    2.2 归档只对人力审核显示 ❌ 同 2.1,同一出口
    2.3 若拿不到当前用户岗位,如实记录、保留 hook 拦截为唯一防线、把平台能力缺口写进「需拍板事项」(只上报不修复) ✅ 记录在测试报告 §4;hook 拦截为唯一防线且已实测生效(T2/T3 截图);缺口写入返回报告「需拍板事项」并上报平台仓,未在平台仓提交任何修复

    范围 3 —— 单测

    # 需求原文要点 态 说明
    3.1 补「非本节点岗位经动作体路径发起 approve/reject/archive 被拒」 ✅ test/position-guard.test.ts:人力审核越节点 approve、部门填报人员 approve / reject / archive,共 4 条,均断言 KPI_SHEET_POSITION
    3.2 补「本节点岗位放行」 ✅ 分管领导 approve → approved 并盖 approved_by;人力审核 approve → leader_approving;人力审核 archive → archived,共 3 条
    3.3 补「系统写入(无发起用户)放行」 ✅ 无发起人系统写入推进 branch_checking → hr_reviewing;hasPosition 与 isSystemWrite 的直接用例;核对任务重置放行,共 4 条
    3.4 现有 90 个用例不得回退 ✅ pnpm test:7 个文件 113 条通过(原 90 + 新 23),零失败

    范围 4 —— 文案

    # 需求原文要点 态 说明
    4.1 拒绝提示沿用现有三段式 KPI_SHEET_POSITION 文案 ✅ 一字未改;界面与 REST 显示同一句「操作失败:当前节点「X」需要由对应岗位处理,你没有该岗位。如需处理,请联系管理员分配岗位。」。未新增 / 修改任何 label,pnpm i18n:extract:check 报 529/529 in sync

    「不做」项自查

    # 不做要求 态 说明
    5.1 不改流程节点顺序与驳回口径(第 10 章第 8 项) ✅ src/lib/workflow.ts 零改动
    5.2 不改 src/lib/scoring.ts ✅ 零改动
    5.3 不改平台包 ✅ node_modules 零改动;平台问题只上报(objectstack#15136),未在平台仓开 PR
    5.4 不做 K-1(分公司填报人)、K-5(批量确认) ✅ 未触碰
    5.5 禁触碰清单(plan.hook.ts / sharing-service.ts / security/index.ts / apps/index.ts / src/data/ / scripts/ / docs/ / README.md / CLAUDE.md) ✅ 全部零改动,改动面见下

    验收标准逐条

    # 验收标准 态 证据
    A1 界面:人力审核对「领导审批中」点「审核通过」被拒并显示岗位提示 ✅ 报告 T2,截图 01 / 02
    A1 界面:部门填报人员对任何单点「审核通过 / 驳回 / 归档」被拒 ✅ 报告 T3,截图 03 / 04 / 05 / 06(按钮仍可见,以「被拒」为证据)
    A1 界面:分管领导对「领导审批中」审批通过正常 ✅ 报告 T4,截图 07
    A1 界面:人力审核归档正常 ✅ 报告 T4,截图 08
    A2 同一组动作经 REST 直接 PATCH 的结果与界面一致 ✅ 报告 T5,7/7 PASS,提示文案与拒绝码逐字相同
    A3 发布 / 重算 / 归档快照回写等系统路径全部正常;software-flow 54/54;e2e-flow 全 PASS ✅ 报告 T6:software-flow 54/54、e2e-flow 73/73
    A4 pnpm verify 绿,新增单测通过 ✅ 报告 T7
    A5 测试报告(含岗位账号截图,成功与拒绝分支各有)与本清单挂本单评论,截图走 acceptance-evidence 40 位 SHA 图链 ✅ 报告已挂;截图 10 张,commit 70ae4a0f9fd7e8ed3a4cba153ac54f819005ad32,已逐个 gh api contents 核对可达

    改动面

    src/hooks/util.ts             +40 −5   新增 isSystemWrite / sysNoActor;hasPosition 免检收窄
    src/hooks/sheet.hook.ts       +26 −7   岗位闸生效注释;直改 status 与归档锁按发起人判定;驳回重置核对任务改无发起人上下文
    src/hooks/check-task.hook.ts  +7  −3   去掉 isSystem 早退;自动推进改无发起人上下文
    src/hooks/bonus.hook.ts       +4  −2   归档锁 / 已批准锁按发起人判定
    src/hooks/adjustment.hook.ts  +4  −2   归档锁 / 已批准锁按发起人判定
    test/position-guard.test.ts   +300     新增 23 条单测
    

    零元数据改动(对象 / 视图 / 动作 / 权限 / 共享 / 应用导航一律未动),因此无 i18n 词条变化。

  6. baozhoutao commented on Sep 4, 2026

    @baozhoutao
    ContributorAuthor

    交接验收侧

    • PR:https://github.com/objectstack-ai/kpi/pull/33(**开发侧不自行合并**)
    • 分支:issue-28-position-guard,基线 main @ a760651
    • 状态:status:AI开发中 → status:自测报告中 → status:PR审查中
    • 处理人:交接给 @baozhoutao(验收侧)
    • 已挂本单:开工方案说明(需求理解 + 实现方式 + 受影响写入路径清单)、测试报告、需求符合度清单
    • 截图:acceptance-evidence commit 70ae4a0f9fd7e8ed3a4cba153ac54f819005ad32,目录 issue-28/,10 张,已逐个 gh api contents 核对可达

    需要拍板的一件事:范围 2「按钮可见性按岗位收敛」因平台能力受限未实现(动作 visible 的 current_user.positions 只带 auth 层角色,拿不到业务岗位),已上报 objectstack-ai/objectstack#15136;当前由 hook 拦截兜底,点了才报「你没有该岗位」。是否接受这个现状、还是另立单在填报单上落一个「当前节点所需岗位」字段来做可见性,请验收侧定。

  7. added a commit that references this issue on Sep 4, 2026
  8. baozhoutao commented on Sep 4, 2026

    @baozhoutao
    ContributorAuthor

    合并记录(调度员,2026-09-03)

    • 收单核实:PR fix(hooks): 流程按钮按发起人校验岗位,系统免检收窄到无发起人的纯系统写入 #33 关联本单、无自动关闭关键字;6 文件(sheet / bonus / adjustment / check-task 四个 hook + util + 新单测),禁触碰面 plan.hook / 共享服务 / 权限集 / 导航 / 种子 / 脚本零改动;CI 绿;测试报告、需求符合度清单(⚠️ 1.3 四处非岗位类 isSystem 闸保留、❌ 2.1/2.2 按钮可见性收敛 → 平台能力受限 action.visible 的 current_user.positions 装的是 auth 角色而非安全层岗位,按岗位收敛的按钮对所有人静默消失(17.2.0) objectstack#15136)、10 张岗位账号证据图核对存在。全项通过。
    • 评审闸门(全量档):首轮「不可合并」——🔴 F1 归档 after 阶段的清场写入被本单收窄后的归档锁误拦(平台审计戳字段不在白名单),约 30% 概率产生「已归档无快照」且无法重试,评审侧独立实跑 7 张失败 3 张、e2e T28 两轮一红、基线对照 73/73 绿;🟡 F2 单测替身缺 sudo 语义导致 after 阶段零覆盖。原开发子 agent 在原分支追加 cba7628 修复(清场写入改无发起人系统路径 + 归档锁排除四个平台审计戳字段;替身重写并新增 6 条可证伪用例);复查轮「可合并」:连续归档 11/11、e2e 三轮 74/74、白名单混写探针全部被拒、回退抽验三种情形均按预期变红。
    • 拍板落地:按钮可见性按岗位收敛在 17.2.0 做不到(动作可见性表达式拿到的是认证角色而非业务岗位),hook 拦截作为唯一防线,平台侧只上报不修复;两条既有文案缺陷(拒绝提示带 KpiError: 前缀、归档被拒提示节点名指向最后节点)与「已归档单仅含审计戳的 PATCH 变为 200 空操作」按记录级留痕,待应用侧缺陷批次一并排。
    • 已合并 PR fix(hooks): 流程按钮按发起人校验岗位,系统免检收窄到无发起人的纯系统写入 #33 到 main。状态切 status:PM验收中,处理人不变(验收侧)。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions