feat(approvals): expose the pending node's lockRecord policy on the request row (#3814, objectui#2902) - #3815
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
… request row (#3814, objectui#2902) An approval node declares `lockRecord` (default `true`) and the record-lock `beforeUpdate` hook enforces exactly that — `lockRecord: false` and the record stays writable while the node waits. Correct since Phase B, and invisible to every client. `rowFromRequest` parses `node_config_json` but projects a whitelist out of it (`__flowLabel`, `__nodeLabel`, `__round`, `escalation.timeoutHours`, `decisionOutputs`); `lockRecord` was never in that list, and no other field on `ApprovalRequestRow` carried the lock. So the strongest thing a console could learn from `GET /approvals/requests` was "a pending request exists", from which it can only assume the record is locked. That is wrong on every opted-out node, and a flow chaining nodes with different policies makes it visibly wrong: one UI state for "you may edit this" and "your save dies with RECORD_LOCKED". `ApprovalRequestRow` now carries `lock_record: boolean`, read from the same snapshot the hook reads with the same `!== false` default, on every service read. The flag a client renders and the rule the server applies cannot drift. Additive and backward compatible. The showcase's `showcase_budget_approval` declares `lockRecord: false` on its single-approver Manager Review and keeps `true` on the multi-approver Executive Review, so one flow exercises both. Closes #3814 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
Jul 28, 2026
…ock_record` main shipped the same feature first. #3815 (`a6c3f3806`, #3814/objectui#2902) landed `ApprovalRequestRow.lock_record`, computed `cfg?.lockRecord !== false` off the `node_config_json` snapshot — byte-for-byte the rule this branch's `locks_record` computes, from the same snapshot, with the same default. Only the test file collided textually. `approval-service.ts` and the spec contract auto-merged *clean*, which is the dangerous part: the result declared both fields, so `GET /api/v1/approvals/requests` would have shipped one policy under two names and every client would have had to guess which to read. Resolved toward main: `lock_record` is already merged and released in a changeset, so dropping it would be a breaking change layered on a duplicate. Removed from this branch: - `locks_record` on `rowFromRequest` (plugin-approvals) - `locks_record?: boolean` on `ApprovalRequestRow` (spec contract) - its 3 tests — main's 3 `lock_record` tests are a superset, covering `openNodeRequest` / `listRequests` / `getRequest` on all three cases - the `locks_record` half of the changeset, which main's `approvals-expose-lock-record.md` already describes The changeset is rewritten to the half that is still this branch's own (`droppedFields` on `POST /batch`) and renamed to match; `plugin-approvals` comes off its bump list, because after this resolution the branch no longer touches that package. #3835 (batch creates through the create ingress) is untouched and keeps its own changeset. Console follow-up, NOT covered here: objectstack-ai/objectui#2914 is still open and reads `locks_record`. It must be repointed at `lock_record` or the band falls back to "assume locked" — the exact bug #3794 filed. Verified after resolution: build 71/71 · spec 6836 · rest 419 (incl. all 5 new batch tests) · plugin-approvals 318 (incl. main's 3 `lock_record` tests) · metadata-protocol 71 · no generated-artifact drift. Co-Authored-By: Claude <noreply@anthropic.com>
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.
Closes #3814. Unblocks objectui#2902.
问题
审批节点声明
lockRecord(默认true),记录锁钩子严格执行它 ——lockRecord: false的节点上记录在整个等待期间都可写。服务端一直是对的,但这个策略对任何客户端都不可见。rowFromRequest(approval-service.ts:236)确实解析了node_config_json,却只从中挑白名单几项(__flowLabel/__nodeLabel/__round/escalation.timeoutHours/decisionOutputs)——lockRecord从来不在列表里,ApprovalRequestRow上也没有别的字段承载锁信息。于是
GET /approvals/requests的消费方能知道的最强事实只有「存在一条 pending 请求」,从这里只能推出「记录被锁」。这个推断在每个lockRecord: false的节点上都是错的;一条串了不同策略节点的 flow 会让它错得肉眼可见 —— 同一个 UI 状态既表示「随便改」,也表示「保存会被RECORD_LOCKED打回」。客户端没有第三条路:反过来猜就会放出一个必然在保存时死掉的编辑入口。不是回归 —— 自 Phase B autopilot 引入
lockRecord起就是这样。改动
ApprovalRequestRow增加lock_record: boolean:取值来自钩子读的那同一份
node_config_json快照,用同一个!== false默认,所以客户端渲染的标志和服务端执行的规则不可能漂移。openNodeRequest/getRequest/listRequests三条读路径都带上。顺带把 showcase 的
showcase_budget_approval变成这个能力的 dogfood:单人的 Manager Review 配lockRecord: false(本就带 revise 送回循环,语义上假定记录可改),多人会签的 Executive Review 保持true。一条 flow 同时覆盖两种策略。兼容性
纯增量。老客户端忽略新字段即可;新客户端读
request.lock_record,undefined(老后端)按锁死处理 —— 就是现状行为。无迁移。测试
新增 3 条:节点锁 →
true、节点显式关闭 →false、lockRecord未设 →true(与钩子只在显式=== false时放行的默认严格对齐)。三条都同时断言openNodeRequest/listRequests/getRequest三个读路径。真机验证
配合 objectui 的对应改动,在 showcase 上跑通了一条真实审批:预算 600k→620k 触发 → 停在 Manager Review,API 返回
lock_record: false→ 批准 → 进 Executive Review,返回lock_record: true。同一条记录上两个节点分别报出各自的策略。🤖 Generated with Claude Code