You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console: a non-participant reader is offered inline edit on a record whose pending approval locks it (lockRecord: true), and the save answers 409 RECORD_LOCKED #25934
A member can read a record that has a pending lockRecord: true approval, but is not on the request's slate, did not submit it, and holds no override. On that record's page the console offers inline edit. The save is then refused with 409 RECORD_LOCKED. The admin sees the same record correctly locked: no inline edit, Edit greyed, and the "Locked for approval" band. So the lock is drawn only for viewers the approvals door answers with a row.
Measured on main77392e9 with the console at pin 5fef032b1ada (built from f0fdd45b; see #25478). Reproduced on 3 fresh loads. The resolver below is unchanged at objectui origin/main3a73c07.
Reproduction
On a stock showcase boot, as admin, mint a contributor member: POST /api/v1/auth/organization/invite-member {"email":E,"role":"member"}, then POST /api/v1/auth/sign-up/email as E, then POST /api/v1/data/sys_user_position {"user_id":U,"position":"contributor","organization_id":ORG}.
As admin, PATCH /api/v1/data/showcase_project/ID {"budget":600000}. This opens showcase_budget_approval at manager_review.
As admin, POST /api/v1/approvals/requests/MGR/approve. The run moves to exec_review, and the new request row reads lock_record: true.
As the member, sign in through the console form and open /_console/apps/showcase_app/showcase_project/record/ID.
Expected: no inline-edit affordance (the record is locked). Actual: Edit is enabled, 12 fields show "Double-click to edit", and there is no lock band. Saving Health sends PATCH /api/v1/data/showcase_project/ID {"health":"yellow"} and gets 409 RECORD_LOCKED.
The member's own GET /api/v1/approvals/requests?object=showcase_project&recordId=ID answers 200 {"data":[]}.
Mechanism
objectui packages/react/src/context/InlineEditContext.tsxresolveApprovalLock (rule 2, objectui#12220) reads an answered approvals read with no open request as "no approval running":
if (openApproval) return { pending: true, locked: openApproval.locked };
if (readAnswered) return { pending: false, locked: false };
The approvals door serves a request only to its participants and the override actor (plugin-approvals request read gate; the record-reader tier is off on stock). So for a non-participant, an empty read does not mean "no approval running". The console unlocks the record, and the server then refuses the write. Nothing the non-participant can read tells them the record is locked.
The fix is a decision. One option is a lock signal a record reader can see without seeing the request. The other is the console refusing to infer "unlocked" from an empty read on an approval-enabled object. Either way the console stops offering an edit the server will refuse (objectui#2902's mislabel, for this persona).
A contributor member who is not on the slate opens a project record under a pending lockRecord: true request: 12 inline-edit affordances, Edit enabled, no lock band. Save → 409 RECORD_LOCKED. Reproduced on 5 fresh loads and on a second fresh-DB boot; the admin on the same record sees the lock correctly.
resolveApprovalLock in packages/react/src/context/InlineEditContext.tsx is unchanged at 6fb31dcc, so the fix is not in this pin.
Pause lifted · domain:ui seat 4 (vacant; written by its signed-off session session_01XWGTsmqrtrt1Zqh1ioWqsT) · 2026-10-25T14:11Z
The os-sales tenure held this card back for the planned objectui → objectstack repo move. The maintainer lifted that pause in this project on 2026-10-25T14:08Z: 「你放回队列就写」. The card already carries pm:queue and is dispatchable under the normal rules. Nothing dispatches it now, because the domain:ui lane is off duty, so a future seat picks it up from the queue.
Transferred 2026-10-26T04:48Z from objectstack-ai/objectui#13393, with its comments and timeline; the old URL redirects here. objectui moved into this repository as the ui/ workspace (epic #25677) and is being archived (#25854). The body is left exactly as filed.
Paths. Read every repository path in the body with a ui/ prefix: packages/react/src/context/InlineEditContext.tsx is now ui/packages/react/src/context/InlineEditContext.tsx.
⛔ Not a claim, ⛔ not a dispatch. First touch after the transfer from objectui. The card had no triage audit comment there or here, which is what the domain:ui seat (#25966) needs before it can pick it.
p3, as filed: the save is refused correctly (409 RECORD_LOCKED), so this is affordance only, not a lock hole. The fix: draw the lock from the record's lock state, not from whether the approvals door returns a row for this viewer.
规约契约:锁状态由服务器给,客户端不猜、不提供会被拒的编辑。packages/spec/src/contracts/approval-service.ts:320-336(lock_record 文档块):"Mirrors the lockRecord policy the record-lock beforeUpdate hook enforces … so a client never has to guess, and never disagrees with the server … should fail closed (assume locked) rather than offer an edit the server will reject with RECORD_LOCKED."
— 这条约定的本意今天对"非参与者"这类用户落空:lock_record 只放在请求行上,而这类用户读不到请求行。这是契约在载体上的缺口,不是代码偏离了规约。
维护者裁决(【能力需求】plugin-approvals 审批请求可见性:对业务记录有读权者应可只读查看审批动态(或提供可见性 hook) #8652,评论 5299823744,2026-08-15T01:29Z):能读记录的人可只读查看该记录的审批请求及完整动作历史,由每个对象各自的开关打开,默认关闭,"existing deployments' confidentiality posture is unchanged"。代码落点:packages/plugins/plugin-approvals/src/approval-service.ts:8312-8358(文档块,裁决在 :8316),规约面 packages/spec/src/data/object.zod.ts:350(approvalsVisibleToReaders 默认 false,"comments and attachments included")。
— 这条裁决管的是审批单内容(谁审、评论、附件、快照)的可见性;它没有裁过"锁住了"这个事实对读者是否可见。
服务器早已把"锁住了"告诉任何尝试保存的人:packages/plugins/plugin-approvals/src/lifecycle-hooks.ts:290-294recordLockRefusal → "<对象> '<标题>' is locked while an approval is in progress, and cannot be edited until that approval is complete",由 :578-582 抛出 409。
QA 清单的验收句不分人群:docs/qa/platform-checklist/areas/approvals.json:867 "a LOCKING pending approval hides the record's inline-edit affordances";:881 "an inline-edit affordance offered while a lockRecord:true approval is pending is a FAIL"。
服务器执法正确,而且拒绝文案已经说出"审批中已锁定"。 git show origin/main:packages/plugins/plugin-approvals/src/lifecycle-hooks.ts | grep -n "registerHook('beforeUpdate'\|lockedError(recordLockRefusal\|is locked while an approval is in progress" → 期望 :487、:582、:294(另一处 :791 是委托对象的守卫,不是本钩)
**③ 防 AI 犯错:**出错时谁看到什么?A 是服务器算一次,页面照抄,AI 写的页面没有猜的余地。B 让 AI 再写一条"对象是否启用审批"的推断,错了就静默误锁或静默放行。D 本身就是消费端容错(AGENTS.md Add comprehensive test suite for Zod schema validation #12 明禁的形态)。C 最危险:AI 为了修"按钮多显示了"会顺手把 approvalsVisibleToReaders: true 写进元数据,静默泄露评论和附件,而且看起来像一次正确的修复。
Director seat, summon #51 continuation (ledger objectstack#12708). Presented to the maintainer in batch #401 as item 4 of 5 (with #26149, batch #400's open item, re-presented beside it at the maintainer's word 「按照总监决裁格式详细写」). Seat increment (re-verified on origin/maincdd4eea9; the card's own analysis is not restated):
Premises re-run: rule 2 still reads an answered-empty approvals read as unlocked (InlineEditContext.tsx:107); both hosts call resolveApprovalLock (RecordDetailView.tsx, DetailView.tsx, 1 site each); approvalsVisibleToReaders defaults false (object.zod.ts:350); lock_record lives on the request row only (approval-service.ts:336); PR fix(app-shell,react,plugin-detail): fold an override-only admin's approval levers into one menu and draw Recall once #26053 (seat 1, same files) is open — A's client half serializes behind it.
Governing text re-read: the lock_record docblock ("a client never has to guess … should fail closed") — the contract's intent falls through for non-participants, as the analysis says; 【能力需求】plugin-approvals 审批请求可见性:对业务记录有读权者应可只读查看审批动态(或提供可见性 hook) #8652 ruling 5299823744 governs the visibility of request content, not of the lock fact. No ADR decides this.
The one choice the maintainer owes inside A (the analysis names it, the seat seconds it): disclosure of the lock fact to readers of the record (the 【能力需求】plugin-approvals 审批请求可见性:对业务记录有读权者应可只读查看审批动态(或提供可见性 hook) #8652 anchor; no "can the caller write this row" contract exists today) versus writers only. The seat recommends readers: it is the Salesforce shape (a lock icon for every viewer, history still governed by sharing), it reuses the existing anchor, and the payload is one boolean — no request id, node or participants.
Carrying out ruling 6422939157 (letter A, readers): the domain:services protocol card is filed as #26314. It covers the record-anchored { locked: boolean } read on IApprovalService, the plugin-approvals implementation, the REST route, and integration tests for a non-participant, a read-only reader and a non-reader (refused or empty, never leaking existence). It declares Clause-②: yes (widening) and Metadata-protocol: none, and reuses the lockRecord !== false reading.
This card moves pm:queue → pm:blocked. When #26314 lands, the domain:ui dispatch feeds the signal into resolveApprovalLock, after PR #26053 (same files). The dispatch carries premise re-checks 1–3, 6, 7 and 9 of 6419712936 and reports a fork if any fails. ⛔ No retreat to B or D.
2026-10-27T00:20Z · domain:server seat 2 · ⛔ Not a claim. The blocker has cleared: #26314 landed as PR #26361 (merge 6a2866af71), so GET /api/v1/approvals/records/:object/:recordId/lock and client.approvals.getRecordLock(object, recordId) are on main, answering { locked } to every caller who can read the record. This card moves from pm:blocked to pm:queue for the domain:ui lane.
Claim: PM loop round 1
Session: session_017iXjUm1rujwDk7f3KT1Pkk (seat); implementing card thread session_01ADHkmQYqTcUW7GgPkfWCzw
Account: os-steve
Branch: claude/project-thread-i81sbn (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)
Worktree: the card thread's own container
Domain: domain:ui
Seat: domain:ui#1
File surface: ui/packages/app-shell/src/hooks/useRecordApprovals.ts, ui/packages/app-shell/src/views/RecordDetailView.tsx (the lock input only), ui/packages/react/src/context/InlineEditContext.tsx (resolveApprovalLock / ApprovalLockInput, if the rule needs a new input), ui/packages/plugin-detail/src/DetailView.tsx only if the band computes its own lock input, their tests (including a new non-participant regression test; the four objectui#12220 tests must not flip), re-recorded sdui records for touched registry packages, and a root changeset. Out of bounds: ObjectView.tsx / objectView*.ts (#25992), the reaction remove path (#26346), and any server or packages/ change. Ruling 6422939157 letter A (readers); ⛔ no retreat to B or D (stop on breach; explain in the report). ⛔ ui/.github/** and the root .github/workflows/ci.yml are out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root .changeset/.
Container & model: small, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: yes (widening), provisional: ApprovalLockInput (exported from @object-ui/react) is expected to gain an optional server-lock input; re-declared no at hand-back if no exported type changes
Metadata-protocol: none
Responsibility: n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public Console
Thread-read: 6430539374
Premise: holds (read-only premise check at origin/main6390fe5, 2026-10-27T01:18Z: #26314 closed via PR #26361, which adds client.approvals.getRecordLock → { locked }; PR #26053 merged; RecordDetailView.tsx still feeds resolveApprovalLock only from the request-list read; spot-checked by the seat. The dev re-runs premises 1–3, 6, 7 and 9 of 6419712936 before any edit)
Serial constraints cleared: RecordDetailView.tsx lands after #26346 (seat 1, p2); react.json after seat 2's #26326, then #26363; plugin-detail.json (if touched) after seat 2's #26325. The seat sets the turn
Dispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.
Claim: PM loop round 1
Session: session_017iXjUm1rujwDk7f3KT1Pkk (seat); implementing card thread session_01ADHkmQYqTcUW7GgPkfWCzw
Account: os-steve
Branch: claude/project-thread-i81sbn (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)
Worktree: the card thread's own container
Domain: domain:ui
Seat: domain:ui#1
File surface: ui/packages/app-shell/src/hooks/useRecordApprovals.ts, ui/packages/app-shell/src/views/RecordDetailView.tsx (the lock input only), ui/packages/react/src/context/InlineEditContext.tsx (resolveApprovalLock / ApprovalLockInput, if the rule needs a new input), ui/packages/plugin-detail/src/DetailView.tsx only if the band computes its own lock input, their tests (including a new non-participant regression test; the four objectui#12220 tests must not flip), re-recorded sdui records for touched registry packages, and a root changeset. Out of bounds: ObjectView.tsx / objectView*.ts (#25992), the reaction remove path (#26346), and any server or packages/ change. Ruling 6422939157 letter A (readers); ⛔ no retreat to B or D (stop on breach; explain in the report). ⛔ ui/.github/** and the root .github/workflows/ci.yml are out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root .changeset/.
Container & model: small, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: yes (widening), provisional: ApprovalLockInput (exported from @object-ui/react) is expected to gain an optional server-lock input; re-declared no at hand-back if no exported type changes
Metadata-protocol: none
Responsibility: n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public Console
Thread-read: 6430539374
Premise: holds (read-only premise check at origin/main6390fe5, 2026-10-27T01:19Z: #26314 closed via PR #26361, which adds client.approvals.getRecordLock → { locked }; PR #26053 merged; RecordDetailView.tsx still feeds resolveApprovalLock only from the request-list read; spot-checked by the seat. The dev re-runs premises 1–3, 6, 7 and 9 of 6419712936 before any edit)
Serial constraints cleared: RecordDetailView.tsx lands after #26346 (seat 1, p2); react.json after seat 2's #26326, then #26363; plugin-detail.json (if touched) after seat 2's #26325. The seat sets the turn
Dispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.
{
"issue": 25934,
"status": "done",
"branch": "claude/project-thread-i81sbn",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26402",
"head": "fca25b0b5397d5e33d5c9ac74c50519930abd70c",
"session": "session_01ADHkmQYqTcUW7GgPkfWCzw",
"premise_still_valid": true,
"premises": "re-run on origin/main 61c07986 before any edit: P1 hold (InlineEditContext.tsx:107); P2 hold (RecordDetailView.tsx:1664, DetailView.tsx:1716; expected :1658/:1716, line drift only); P3 hold (useRecordApprovals.ts:480 is the only lock source; :326/:385 comments); P6 hold (lifecycle-hooks.ts beforeUpdate :538, lockedError(recordLockRefusal :633, refusal text :294; expected :487/:582/:294, drift only); P7 hold (approval-service.ts:336 lock_record?: boolean); P9 serial condition met: PR #26053 merged (054652bf), and the analysis expected it open. Blocker #26314 closed via PR #26361 (6a2866af): client.approvals.getRecordLock at packages/client/src/index.ts:6270, route at packages/rest/src/approvals-routes.ts:352 (501 without the service).",
"summary": "useRecordApprovals (app-shell) reads client.approvals.getRecordLock through the mounted adapter's client when the list read finds no pending request, and exposes recordLock. RecordDetailView feeds a locked answer to resolveApprovalLock as its openApproval: that is rule 1's input exactly, so ApprovalLockInput gains nothing. For a signal-only lock it threads approvalIsSubmitter false, so the band draws no recall. No service means 501, no signal and no open approval, so rule 2 and the four objectui#12220 tests are unchanged. DetailView.tsx is untouched: the band computes from the host's threaded locked/approvalPending/approvalIsSubmitter, it does not compute its own lock input. react and plugin-detail are untouched. Clause-② re-declared no: the only type that gains a member is the hook's non-exported result interface, and the hook is not re-exported from the app-shell entry (src/index.ts, measured on the source entry; the app-shell dist was not built).",
"tests": "Final head fca25b0b, one locked run: turbo build app-shell^... 64/64, then pnpm --filter @object-ui/app-shell type-check exit 0, then 4 files (new RecordDetailView.approvalLock-25934, RecordDetailView.approvalLock-12220, useRecordApprovals.answered-12220, RecordDetailView.approvalRecallGate): Test Files 4/4, Tests 25/25, VERDICT command-exit 0. Pre-merge e0f9e560: 82 neighbour files (all RecordDetailView.*, useRecordApprovals*, RecordApprovalsPanel*, AppContent.recordSaveToastNamesRecord-26172): 82/82 files, 551/551 tests, VERDICT 0. Narrowed by name; the full app-shell suite is left to CI Test UI. Ablation: with the fix committed, both source files were restored to base 61c07986 (on disk getRecordLock 3->0, lockedForReader 3->0). The non-participant case went red at :235 (expected 'false' to be 'true', locked); both lock-read cases went red because getRecordLock was never called; the participant control stayed green: 3 failed / 1 passed, VERDICT command-exit 1. Restored by git checkout HEAD --: git diff HEAD empty, blob hashes equal HEAD. Tests import src directly, so no dist leg applies.",
"gates": "fca25b0b: dispatch-gates --commands derived 35; all 35 exit 0, check:type-check-debt included (run with no other locked run; re-measure OK, 0 raw errors); --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Roster gate under .changeset: check-changeset-fixed.mjs exit 0. All other families (Lint & Repo Gates, Type Check UI, Test UI, Build & E2E UI, ...): PR #26402 check-runs, in_progress at report time.",
"line_budget": "+373/-7 over 4 files: source +81/-7 (useRecordApprovals.ts +65/-5, RecordDetailView.tsx +16/-2), test +278, changeset +14",
"files_changed": [
".changeset/25934-reader-record-lock.md",
"ui/packages/app-shell/src/hooks/useRecordApprovals.ts",
"ui/packages/app-shell/src/views/RecordDetailView.tsx",
"ui/packages/app-shell/src/views/RecordDetailView.approvalLock-25934.test.tsx"
],
"deviations": [
"Clause-② re-declared from the claim's provisional yes (widening) to no, in the PR body and the changeset, per the delta's own re-declare clause: no exported type changes.",
"RecordDetailView.tsx edit includes the approvalIsSubmitter line (:1691 area), next to the lock region: a signal-only lock must not light the band's recall lever. The reaction remove path (#26346) is untouched.",
"The PR body starts with the harness attribution comment and project-thread line, and Fixes #25934 is line 3 (os-dev: the harness line is kept and reported)."
],
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no mcp__github__ tool calls (transcript grep count 0); reads via gh api REST GET",
"api_writes": "3 relay REST writes + 3 git pushes. Relay: pr_create, 2 requests in 1 action (POST /repos/objectstack-ai/objectstack/pulls draft, POST /issues/26402/assignees os-steve; read-back identical 7942 bytes); this os-dev-report comment (POST /issues/25934/comments via post-stamped). Pushes: branch create, changeset, merge of origin/main.",
"open_questions": [
{
"question": "Should resolveApprovalLock's openApproval doc in @object-ui/react name the server record lock as a host input, alongside the pending request?",
"options": [
"A leave it: the rule and its input are unchanged, and the call site in RecordDetailView documents the feed",
"B add one sentence to ApprovalLockInput.openApproval, which costs a react package change and its serial line (#26326, #26363)"
],
"recommendation": "A: the signal is rule 1's input as written, and A keeps this PR off the react line."
}
],
"out_of_scope_findings": []
}
Activity
objectstack-fleet commentedon Oct 23, 2026
Re-measured on the new console pin: still reproduces.
maind44c893, console built at pin6fb31dcc9954(QA run QA run · release-18.1.0-browser-retest-new-pin (9/9) · d44c8936 · 2026-10-23 · 6 PASS / 2 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN #25517, itemapprovals.record-page-decisionsnegative[1]).lockRecord: truerequest: 12 inline-edit affordances, Edit enabled, no lock band. Save →409 RECORD_LOCKED. Reproduced on 5 fresh loads and on a second fresh-DB boot; the admin on the same record sees the lock correctly.resolveApprovalLockinpackages/react/src/context/InlineEditContext.tsxis unchanged at6fb31dcc, so the fix is not in this pin.objectstack-fleet commentedon Oct 25, 2026
Pause lifted ·
domain:uiseat 4 (vacant; written by its signed-off sessionsession_01XWGTsmqrtrt1Zqh1ioWqsT) · 2026-10-25T14:11ZThe
os-salestenure held this card back for the planned objectui → objectstack repo move. The maintainer lifted that pause in this project on 2026-10-25T14:08Z: 「你放回队列就写」. The card already carriespm:queueand is dispatchable under the normal rules. Nothing dispatches it now, because thedomain:uilane is off duty, so a future seat picks it up from the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Transferred 2026-10-26T04:48Z from objectstack-ai/objectui#13393, with its comments and timeline; the old URL redirects here. objectui moved into this repository as the
ui/workspace (epic #25677) and is being archived (#25854). The body is left exactly as filed.Paths. Read every repository path in the body with a
ui/prefix:packages/react/src/context/InlineEditContext.tsxis nowui/packages/react/src/context/InlineEditContext.tsx.No stale
pm:dispatchedor assignee to strip.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Triage: first touch —
bug·priority:p3·domain:ui·area:workflow·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T07:01Z⛔ Not a claim, ⛔ not a dispatch. First touch after the transfer from objectui. The card had no triage audit comment there or here, which is what the
domain:uiseat (#25966) needs before it can pick it.409 RECORD_LOCKED), so this is affordance only, not a lock hole. The fix: draw the lock from the record's lock state, not from whether the approvals door returns a row for this viewer.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
决策分析 ·
domain:ui席 2 · 本卡转入needs-user-decision背景
approvals.record-page-decisionsnegative[1]):showcase 项目预算 >500000 时进入exec_review(lockRecord: true)。admin 看到的是锁定;一个 contributor 成员能读能改该项目,但不在审批名单上,他看到 Edit 可点、12 个字段可双击编辑、没有锁定横幅。保存时PATCH返回409 RECORD_LOCKED。该成员自己的GET /api/v1/approvals/requests?object=showcase_project&recordId=ID返回200 {"data":[]}。ui/前缀(迁移评论 6408477511)。Governing text
packages/spec/src/contracts/approval-service.ts:320-336(lock_record文档块):"Mirrors thelockRecordpolicy the record-lockbeforeUpdatehook enforces … so a client never has to guess, and never disagrees with the server … should fail closed (assume locked) rather than offer an edit the server will reject withRECORD_LOCKED."— 这条约定的本意今天对"非参与者"这类用户落空:
lock_record只放在请求行上,而这类用户读不到请求行。这是契约在载体上的缺口,不是代码偏离了规约。packages/plugins/plugin-approvals/src/approval-service.ts:8312-8358(文档块,裁决在 :8316),规约面packages/spec/src/data/object.zod.ts:350(approvalsVisibleToReaders默认false,"comments and attachments included")。— 这条裁决管的是审批单内容(谁审、评论、附件、快照)的可见性;它没有裁过"锁住了"这个事实对读者是否可见。
packages/plugins/plugin-approvals/src/lifecycle-hooks.ts:290-294recordLockRefusal→ "<对象> '<标题>'is locked while an approval is in progress, and cannot be edited until that approval is complete",由 :578-582 抛出 409。docs/qa/platform-checklist/areas/approvals.json:867"a LOCKING pending approval hides the record's inline-edit affordances";:881 "an inline-edit affordance offered while a lockRecord:true approval is pending is a FAIL"。??回退去容错)。mode:subagentdev from its PM — they share one session, so the branch is the only distinguishing identity #12220 规则本身:ui/packages/react/src/context/InlineEditContext.tsx:82-100文档块,规则 2 有意如此——没有审批服务的组合也会回200 { data: [] },如果残留的pending列还当锁,记录会被锁死,而且没有可撤回的东西。**ADR 检索结论:没有 ADR 已裁本题。**逐条读过工具点名的 ADR-0002 Decision(环境级数据库隔离,"reader" 只是 "reader code" 的字面命中)、ADR-0018 Decision §3(执行器描述符注册表)、ADR-0019 D1–D4(审批收归 Flow 节点;"What must NOT be lost" 只要求锁钩子和状态镜像原样保留),以及另外三个审批 ADR:ADR-0042 SLA、ADR-0043 可操作链接、ADR-0044 退回修改(D6 只定锁的生命周期)。另外在
docs/adr全量 greprecord.reader|approvalsVisibleToReaders|8652|non-participant|RECORD_LOCKED,零命中。最接近的是上面第 2 条 #8652 裁决,但它管的是审批单内容,不是锁这个事实。所以本卡是真决策,不是执行卡。协议声明、是否改协议
IApprovalService新增一个以记录为锚的锁读取面,外加 REST 路由。只增不改,不破坏旧调用方。按规则另立协议卡(domain:services),本卡挂Blocked-by:。Metadata-protocol: none(作者不可写的服务契约);Clause-②: yes (widening),先例 #22660 自标同款enable.approvalsVisibleToReaders: true前提(核于 origin/main
dfaad4da,2026-10-26T14:37Z;每条附 re-check)git show origin/main:ui/packages/react/src/context/InlineEditContext.tsx | grep -n "if (readAnswered) return { pending: false, locked: false }"→ 期望 :107git grep -n "resolveApprovalLock({" origin/main -- ui/packages/app-shell/src/views/RecordDetailView.tsx ui/packages/plugin-detail/src/DetailView.tsx→ 期望 RecordDetailView.tsx:1658(至 :1664)、DetailView.tsx:1716(至 :1720)git show origin/main:ui/packages/app-shell/src/hooks/useRecordApprovals.ts | grep -n "approvals/requests?object="→ 期望含 :480(:326、:385 是注释)git show origin/main:packages/plugins/plugin-approvals/src/approval-service.ts | grep -n "if (!this.recordReaderTierOn(object)) return;\|approvalsVisibleToReaders === true"→ 期望 :8375、:8455(函数体 :8444-8456)git show origin/main:packages/spec/src/data/object.zod.ts | grep -n "approvalsVisibleToReaders: z.boolean().default(false)"→ 期望 :350git grep -n "approvalsVisibleToReaders\|recordReaderVisibleObjects" origin/main -- examples→ 期望零命中git show origin/main:packages/plugins/plugin-approvals/src/lifecycle-hooks.ts | grep -n "registerHook('beforeUpdate'\|lockedError(recordLockRefusal\|is locked while an approval is in progress"→ 期望 :487、:582、:294(另一处 :791 是委托对象的守卫,不是本钩)lock_record只放在请求行上。git show origin/main:packages/spec/src/contracts/approval-service.ts | grep -n "lock_record?: boolean"→ 期望 :336mode:subagentdev from its PM — they share one session, so the branch is the only distinguishing identity #12220 的"列回退"对它不起作用。git grep -n "approval_status" origin/main -- examples/app-showcase/src→ 期望零命中gh api repos/objectstack-ai/objectstack/pulls/26053 --jq .state→ 期望open;gh api repos/objectstack-ai/objectstack/pulls/26053/files --jq '.[].filename' | grep -c "InlineEditContext\|RecordDetailView.tsx\|DetailView.tsx"→ 期望 ≥3git grep -n "answered empty is neither pending nor locked\|answers 200 empty opens editable\|host read answered with no request" origin/main -- ui/packages→ 期望 resolveApprovalLock-12220.test.ts:25、RecordDetailView.approvalLock-12220.test.tsx:213、DetailView.approvalReadAnswered-12220.test.tsx:70GET /api/v1/approvals/requests?object=showcase_project&recordId=ID→ 期望200 {"data":[]},随后PATCH→409 RECORD_LOCKED。具体问题
"这条记录正被审批锁住"这个事实,是否默认告诉每一个能读这条记录的人(不连带审批单的内容)? 如果告诉,由服务器给出,页面照读;如果不告诉,页面只能继续猜,或者让作者把整份审批历史都打开。
选项 × 真实代价 × 工作量
IApprovalService加一个只回{ locked: boolean }的方法,另配 REST 路由)。准入沿用 #8652 的锚:以调用者身份能读到这条记录即可,不新增权限概念。页面把它当作resolveApprovalLock的锁输入domain:services1 PR(协议卡)→domain:ui1 PR(本卡),共 2 车道 2 PR,串行。服务端先例:#3815 把lock_record加到请求行上 +108/−1(5 文件,2026-07-28T14:15Z);#22660 按调用者读记录做锚加集成测试 +604/−30(20 文件,2026-10-10T11:36Z)。客户端:无先例(ui/在 #25825 以压平方式导入,本仓无 objectui 历史;objectui 仓本会话不可读)。须排在 #26053 之后domain:ui1 PR,外加那个不存在的信号(实际是另一项协议新增);翻转 4 个测试用例(前提 10)。无先例enable: { approvalsVisibleToReaders: true }409 RECORD_LOCKED后,把本页切到锁定态(横幅,无撤回)domain:ui1 PR。无先例。(现有 409 提示文案在行内保存路径上如何呈现:未验证)业务含义直译
四轴(业务立场)
Approval.isLocked(recordId),但审批历史仍按共享规则控制。A 正是把这两层拆开;它也补上了lock_record契约"客户端永不猜"在非参与者身上落空的那一块。B、D 是客户端推断或事后容错,和契约文档块"never has to guess"正相反。C 把两层重新绑在一起。读数:A 收窄特例,消掉"锁可见性 = 审批单可见性"这个隐式耦合。lockRecord: true节点(examples/app-showcase/src/automation/flows/index.ts:281/1815/1894、dynamic-approval.flow.ts:41/60)。【能力需求】plugin-approvals 审批请求可见性:对业务记录有读权者应可只读查看审批动态(或提供可见性 hook) #8652 当初的下游拉动(Titanwind EHR)实测的正是"持有记录 CRUD 却读到 200 空"的同一人群。没有现场客户就本症状报单,严重度如分诊所定:p3,只是显示问题。approvalsVisibleToReaders: true写进元数据,静默泄露评论和附件,而且看起来像一次正确的修复。推荐 + 置信缺口
推荐 A(只看①得出)。锁是记录的状态,由服务器给,页面不猜;审批单内容的机密级别维持 #8652 原状。
置信缺口(明说,不另荐):
packages/spec/src/contracts/*.ts中检索(check|has|can)*Permission*(零命中,今天没有现成的契约面可以问"调用者能否改这条",要么新造,要么接受"可读即可见"。请维护者在同一笔裁决里定。**自检:只看①选 A;②③④ 是否翻转:否。**②是 p3,只影响排期;④多一个布尔键,不足以翻转;③同向支持 A。
裁后执行段(裁 A 时;维护者只裁方向,下面由席位执行)
engine.find),或者只给可写者;(c) 只给locked,不给pending/请求 id/节点名(推荐)。domain:services立协议卡:IApprovalService新方法 +plugin-approvals实现(锁判定复用lifecycle-hooks.ts的lockRecord !== false同一读法,⛔ 不另写一份)+ REST 路由 + 集成测试(覆盖非参与者、只读者、不可读者三种人;不可读者 ⇒ 拒绝或空,⛔ 不泄露存在)+ changeset 带Clause-②: yes (widening)。pm:blocked和Blocked-by: <协议卡>,去掉needs-user-decision。协议卡落地后由domain:ui派发:useRecordApprovals.ts读新信号,在RecordDetailView.tsx:1658处并入resolveApprovalLock的锁输入。规则 2 对"没有审批服务的组合"的语义保持不变(没有服务就没有锁信号,仍判不锁),finding(skills): the dev claim comment's session ID cannot distinguish amode:subagentdev from its PM — they share one session, so the branch is the only distinguishing identity #12220 的 4 个测试不翻转;新增非参与者被锁的回归测试。**串行约束:**在 fix(app-shell,react,plugin-detail): fold an override-only admin's approval levers into one menu and draw Recall once #26053 合入之后开工。object.zod.ts:350描述中补一句"打开它也会让读者看到锁"(纯文档)。裁 D:本卡直接派domain:ui,在 fix(app-shell,react,plugin-detail): fold an override-only admin's approval levers into one menu and draw Recall once #26053 之后。裁 B:先立"对象是否启用锁定型审批"信号的协议卡。os-decision-facets
lock_record契约"客户端永不猜";B/D 是客户端推断或事后容错,C 把两层重新绑死。Prior rulings read: approval,lock,reader,lockrecord → 31 hits; ADR-0002 Decision §6, ADR-0018 Decision §3, ADR-0019 D1, ADR-0019 D2, ADR-0019 D3, ADR-0019 D4; thread: none
(逐条读毕:上述 ADR 无一裁读者对锁的可见性;另读 ADR-0042/0043/0044 亦无。人工补检:#8652 维护者裁决(评论 5299823744)只裁审批单内容的可见性,已列入 Governing text 第 2 条。)
相关单与 PR
lockRecord粒度;objectui#2618:状态列回退。approvalsVisibleToReaders)、approvals: 审批请求行不暴露节点的lockRecord,客户端只能假定「有 pending 就是锁死」 #3814 / PR feat(approvals): expose the pending node'slockRecordpolicy on the request row (#3814, objectui#2902) #3815(请求行上的lock_record)。docs/qa/platform-checklist/areas/approvals.json:821-881。ui/workspace:D1 裁定(A+)的执行父单 — 一个 PR 同时改 spec 与渲染器,删除两仓互钉,@object-ui 保留包名并与 @objectstack 同一版本组,objectui 仓归档为迁移说明 #25677(epic)、merge(ui): objectui becomes the ui/ workspace of this repository — subtree import at the frozen tip 9e1fe03286 plus the adaptation series (objectstack#25752) #25825(subtree 导入)、cutover(ui-merge): wind down the objectui repository — close its 6 bot PRs, transfer the open cards to objectstack, close the seat posts, migration-notice README, delete the 381claude/*branches; the maintainer archives (parent #25752 steps 7–8) #25854(归档)。Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Brief-claimed-by: session_01KrKXQVvgYk2AJ4VkqMobyb · batch #401 · 2026-10-26T15:44Z
Director seat, summon #51 continuation (ledger objectstack#12708). Presented to the maintainer in batch #401 as item 4 of 5 (with #26149, batch #400's open item, re-presented beside it at the maintainer's word 「按照总监决裁格式详细写」). Seat increment (re-verified on
origin/maincdd4eea9; the card's own analysis is not restated):InlineEditContext.tsx:107); both hosts callresolveApprovalLock(RecordDetailView.tsx,DetailView.tsx, 1 site each);approvalsVisibleToReadersdefaultsfalse(object.zod.ts:350);lock_recordlives on the request row only (approval-service.ts:336); PR fix(app-shell,react,plugin-detail): fold an override-only admin's approval levers into one menu and draw Recall once #26053 (seat 1, same files) is open — A's client half serializes behind it.lock_recorddocblock ("a client never has to guess … should fail closed") — the contract's intent falls through for non-participants, as the analysis says; 【能力需求】plugin-approvals 审批请求可见性:对业务记录有读权者应可只读查看审批动态(或提供可见性 hook) #8652 ruling 5299823744 governs the visibility of request content, not of the lock fact. No ADR decides this.IApprovalService+ REST route,{ locked }only; the console feeds it intoresolveApprovalLock; rule 2's no-service semantics unchanged). Not B (locks every non-participant on every approval-enabled object), not C (exposes the whole approval history to fix a button), not D (consumer-side tolerance; the user is still refused once). Workload: services S (precedent feat(approvals): expose the pending node'slockRecordpolicy on the request row (#3814, objectui#2902) #3815 +108/−1) → ui S, after fix(app-shell,react,plugin-detail): fold an override-only admin's approval levers into one menu and draw Recall once #26053.Prior rulings read:
lock_record·approvalsVisibleToReaders·#8652·RECORD_LOCKED→ the contract docblock, ruling 5299823744, the hook's refusal text; ADR none; thread: 6419712936.2 remaining items
objectstack-fleet commentedon Oct 26, 2026
席位核对 · Blocked-by · seat
domain:ui#1· 2026-10-26T20:32ZBlocked-by: #26314
Carrying out ruling 6422939157 (letter A, readers): the
domain:servicesprotocol card is filed as #26314. It covers the record-anchored{ locked: boolean }read onIApprovalService, theplugin-approvalsimplementation, the REST route, and integration tests for a non-participant, a read-only reader and a non-reader (refused or empty, never leaking existence). It declaresClause-②: yes (widening)andMetadata-protocol: none, and reuses thelockRecord !== falsereading.This card moves
pm:queue→pm:blocked. When #26314 lands, thedomain:uidispatch feeds the signal intoresolveApprovalLock, after PR #26053 (same files). The dispatch carries premise re-checks 1–3, 6, 7 and 9 of 6419712936 and reports a fork if any fails. ⛔ No retreat to B or D.objectstack-fleet commentedon Oct 27, 2026
2026-10-27T00:20Z ·
domain:serverseat 2 · ⛔ Not a claim. The blocker has cleared: #26314 landed as PR #26361 (merge6a2866af71), soGET /api/v1/approvals/records/:object/:recordId/lockandclient.approvals.getRecordLock(object, recordId)are onmain, answering{ locked }to every caller who can read the record. This card moves frompm:blockedtopm:queuefor thedomain:uilane.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_017iXjUm1rujwDk7f3KT1Pkk(seat); implementing card threadsession_01ADHkmQYqTcUW7GgPkfWCzwAccount:
os-steveBranch:
claude/project-thread-i81sbn(platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)Worktree: the card thread's own container
Domain:
domain:uiSeat:
domain:ui#1File surface:
ui/packages/app-shell/src/hooks/useRecordApprovals.ts,ui/packages/app-shell/src/views/RecordDetailView.tsx(the lock input only),ui/packages/react/src/context/InlineEditContext.tsx(resolveApprovalLock/ApprovalLockInput, if the rule needs a new input),ui/packages/plugin-detail/src/DetailView.tsxonly if the band computes its own lock input, their tests (including a new non-participant regression test; the four objectui#12220 tests must not flip), re-recorded sdui records for touched registry packages, and a root changeset. Out of bounds:ObjectView.tsx/objectView*.ts(#25992), the reaction remove path (#26346), and any server orpackages/change. Ruling 6422939157 letter A (readers); ⛔ no retreat to B or D (stop on breach; explain in the report). ⛔ui/.github/**and the root.github/workflows/ci.ymlare out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root.changeset/.Container & model:
small,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②:
yes (widening), provisional:ApprovalLockInput(exported from@object-ui/react) is expected to gain an optional server-lock input; re-declarednoat hand-back if no exported type changesMetadata-protocol: none
Responsibility:
n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public ConsoleThread-read: 6430539374
Premise: holds (read-only premise check at
origin/main6390fe5, 2026-10-27T01:18Z: #26314 closed via PR #26361, which addsclient.approvals.getRecordLock→{ locked }; PR #26053 merged;RecordDetailView.tsxstill feedsresolveApprovalLockonly from the request-list read; spot-checked by the seat. The dev re-runs premises 1–3, 6, 7 and 9 of 6419712936 before any edit)Serial constraints cleared:
RecordDetailView.tsxlands after #26346 (seat 1, p2);react.jsonafter seat 2's #26326, then #26363;plugin-detail.json(if touched) after seat 2's #26325. The seat sets the turnDispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_017iXjUm1rujwDk7f3KT1Pkk(seat); implementing card threadsession_01ADHkmQYqTcUW7GgPkfWCzwAccount:
os-steveBranch:
claude/project-thread-i81sbn(platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)Worktree: the card thread's own container
Domain:
domain:uiSeat:
domain:ui#1File surface:
ui/packages/app-shell/src/hooks/useRecordApprovals.ts,ui/packages/app-shell/src/views/RecordDetailView.tsx(the lock input only),ui/packages/react/src/context/InlineEditContext.tsx(resolveApprovalLock/ApprovalLockInput, if the rule needs a new input),ui/packages/plugin-detail/src/DetailView.tsxonly if the band computes its own lock input, their tests (including a new non-participant regression test; the four objectui#12220 tests must not flip), re-recorded sdui records for touched registry packages, and a root changeset. Out of bounds:ObjectView.tsx/objectView*.ts(#25992), the reaction remove path (#26346), and any server orpackages/change. Ruling 6422939157 letter A (readers); ⛔ no retreat to B or D (stop on breach; explain in the report). ⛔ui/.github/**and the root.github/workflows/ci.ymlare out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root.changeset/.Container & model:
small,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②:
yes (widening), provisional:ApprovalLockInput(exported from@object-ui/react) is expected to gain an optional server-lock input; re-declarednoat hand-back if no exported type changesMetadata-protocol: none
Responsibility:
n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public ConsoleThread-read: 6430539374
Premise: holds (read-only premise check at
origin/main6390fe5, 2026-10-27T01:19Z: #26314 closed via PR #26361, which addsclient.approvals.getRecordLock→{ locked }; PR #26053 merged;RecordDetailView.tsxstill feedsresolveApprovalLockonly from the request-list read; spot-checked by the seat. The dev re-runs premises 1–3, 6, 7 and 9 of 6419712936 before any edit)Serial constraints cleared:
RecordDetailView.tsxlands after #26346 (seat 1, p2);react.jsonafter seat 2's #26326, then #26363;plugin-detail.json(if touched) after seat 2's #26325. The seat sets the turnDispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.
objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 25934, "status": "done", "branch": "claude/project-thread-i81sbn", "pr": "https://github.com/objectstack-ai/objectstack/pull/26402", "head": "fca25b0b5397d5e33d5c9ac74c50519930abd70c", "session": "session_01ADHkmQYqTcUW7GgPkfWCzw", "premise_still_valid": true, "premises": "re-run on origin/main 61c07986 before any edit: P1 hold (InlineEditContext.tsx:107); P2 hold (RecordDetailView.tsx:1664, DetailView.tsx:1716; expected :1658/:1716, line drift only); P3 hold (useRecordApprovals.ts:480 is the only lock source; :326/:385 comments); P6 hold (lifecycle-hooks.ts beforeUpdate :538, lockedError(recordLockRefusal :633, refusal text :294; expected :487/:582/:294, drift only); P7 hold (approval-service.ts:336 lock_record?: boolean); P9 serial condition met: PR #26053 merged (054652bf), and the analysis expected it open. Blocker #26314 closed via PR #26361 (6a2866af): client.approvals.getRecordLock at packages/client/src/index.ts:6270, route at packages/rest/src/approvals-routes.ts:352 (501 without the service).", "summary": "useRecordApprovals (app-shell) reads client.approvals.getRecordLock through the mounted adapter's client when the list read finds no pending request, and exposes recordLock. RecordDetailView feeds a locked answer to resolveApprovalLock as its openApproval: that is rule 1's input exactly, so ApprovalLockInput gains nothing. For a signal-only lock it threads approvalIsSubmitter false, so the band draws no recall. No service means 501, no signal and no open approval, so rule 2 and the four objectui#12220 tests are unchanged. DetailView.tsx is untouched: the band computes from the host's threaded locked/approvalPending/approvalIsSubmitter, it does not compute its own lock input. react and plugin-detail are untouched. Clause-② re-declared no: the only type that gains a member is the hook's non-exported result interface, and the hook is not re-exported from the app-shell entry (src/index.ts, measured on the source entry; the app-shell dist was not built).", "tests": "Final head fca25b0b, one locked run: turbo build app-shell^... 64/64, then pnpm --filter @object-ui/app-shell type-check exit 0, then 4 files (new RecordDetailView.approvalLock-25934, RecordDetailView.approvalLock-12220, useRecordApprovals.answered-12220, RecordDetailView.approvalRecallGate): Test Files 4/4, Tests 25/25, VERDICT command-exit 0. Pre-merge e0f9e560: 82 neighbour files (all RecordDetailView.*, useRecordApprovals*, RecordApprovalsPanel*, AppContent.recordSaveToastNamesRecord-26172): 82/82 files, 551/551 tests, VERDICT 0. Narrowed by name; the full app-shell suite is left to CI Test UI. Ablation: with the fix committed, both source files were restored to base 61c07986 (on disk getRecordLock 3->0, lockedForReader 3->0). The non-participant case went red at :235 (expected 'false' to be 'true', locked); both lock-read cases went red because getRecordLock was never called; the participant control stayed green: 3 failed / 1 passed, VERDICT command-exit 1. Restored by git checkout HEAD --: git diff HEAD empty, blob hashes equal HEAD. Tests import src directly, so no dist leg applies.", "gates": "fca25b0b: dispatch-gates --commands derived 35; all 35 exit 0, check:type-check-debt included (run with no other locked run; re-measure OK, 0 raw errors); --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Roster gate under .changeset: check-changeset-fixed.mjs exit 0. All other families (Lint & Repo Gates, Type Check UI, Test UI, Build & E2E UI, ...): PR #26402 check-runs, in_progress at report time.", "line_budget": "+373/-7 over 4 files: source +81/-7 (useRecordApprovals.ts +65/-5, RecordDetailView.tsx +16/-2), test +278, changeset +14", "files_changed": [ ".changeset/25934-reader-record-lock.md", "ui/packages/app-shell/src/hooks/useRecordApprovals.ts", "ui/packages/app-shell/src/views/RecordDetailView.tsx", "ui/packages/app-shell/src/views/RecordDetailView.approvalLock-25934.test.tsx" ], "deviations": [ "Clause-② re-declared from the claim's provisional yes (widening) to no, in the PR body and the changeset, per the delta's own re-declare clause: no exported type changes.", "RecordDetailView.tsx edit includes the approvalIsSubmitter line (:1691 area), next to the lock region: a signal-only lock must not light the band's recall lever. The reaction remove path (#26346) is untouched.", "The PR body starts with the harness attribution comment and project-thread line, and Fixes #25934 is line 3 (os-dev: the harness line is kept and reported)." ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 — no mcp__github__ tool calls (transcript grep count 0); reads via gh api REST GET", "api_writes": "3 relay REST writes + 3 git pushes. Relay: pr_create, 2 requests in 1 action (POST /repos/objectstack-ai/objectstack/pulls draft, POST /issues/26402/assignees os-steve; read-back identical 7942 bytes); this os-dev-report comment (POST /issues/25934/comments via post-stamped). Pushes: branch create, changeset, merge of origin/main.", "open_questions": [ { "question": "Should resolveApprovalLock's openApproval doc in @object-ui/react name the server record lock as a host input, alongside the pending request?", "options": [ "A leave it: the rule and its input are unchanged, and the call site in RecordDetailView documents the feed", "B add one sentence to ApprovalLockInput.openApproval, which costs a react package change and its serial line (#26326, #26363)" ], "recommendation": "A: the signal is rule 1's input as written, and A keeps this PR off the react line." } ], "out_of_scope_findings": [] }Generated by Claude Code