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
[Decision] Override-recall of a returned approval: an ADR-0044 side effect to retire, or a capability to keep? The gate, the prose and the viewer flag disagree three ways #12775
Filed by the triage seat (session session_01Aujz2zykf5LXt3T98gRsGe) splitting the maintainer-floor half out of #12716, so that card is not held hostage to it. #12716 is queued for its own fix (surface the override-recall button on pending) and ⛔ does not depend on this ruling.
Measured by the #12716 dev and independently re-verified by the domain:services PM on origin/main at 86df0c99e — full evidence chain in #12716 comment 5442740007. ⛔ Not re-measured by this seat; re-verify before acting.
The three-way disagreement
surface
what it says about override-recall on a returned request
the gate — approval-service.ts:2763 + the status-blind override short-circuit at :2774
admitted
the viewer flag — attachViewers:4848, can_override: row.status === 'pending' && isOverrideActor(...) (ANDed)
pending-only
the prose — :808"may always act on a PENDING request … or recall it", plus :2767, :4833
pending-only
Two surfaces say pending-only; the gate is the odd one out, and isOverrideActor(context, requestOrg?)structurally cannot be status-aware — there is no status parameter.
⭐ The reach looks like a side effect, not a grant. ADR-0044 widened recall's state check to cover the submitter's revise window; the override short-circuit sitting above it was never re-scoped alongside. Recall is the only one of the four override levers that accepts a non-pending status at all — decideNode:2308 and loadPendingRow:796 make approve / reject / reassign pending-only at the endpoint.
Why it is the maintainer's and not triage's
Retiring the reach narrows a permission on a live endpoint and removes a capability that exists in shipped code and is pinned by nothing. Both the dev and the services PM reached that boundary and handed it up rather than taking it; this seat agrees and is ⛔ not ruling it.
Options
shape
cost
A
Keep the reach; make prose and viewer flag agree with the gate
To actually surface it needs a new, recall-specific viewer signal — can_override cannot be relaxed, since it also feeds the three sibling predicates whose endpoints are pending-only. New declared surface, permanent obligation
B
Retire the reach — re-scope the :2774 short-circuit to pending so all three agree at one point
Narrows a live permission. ⚠️ If any operator workflow relies on override-recalling a returned request, B breaks it silently
C
Leave all three as they are
The disagreement persists, undocumented. It has already cost one wrong triage ruling this round
③ 防 AI 犯错:C 最差 —— 三个面对同一问题给三个答案,任何按约定读代码的 agent 都会推断错(本轮已实证)。B 让代码与散文一致且可被一条 pin 钉住;A 新增一个 recall 专用信号,面更大、更容易再漂。
④ 创业阶段不扩散:B 是收窄,不新增面;A 新增一个永久维护的声明信号。
推荐:B,附硬前置条件 —— 落地前先从审计留痕读出「是否真有 override actor 在 returned 上撤回过」。读数为零才动手;非零则当场改判 A,⛔ 不许带着非零读数硬做。回退:A。
置信缺口(本分析看不见什么):看不见生产部署里 override-recall-on-returned 的实际调用量。本席只能读代码,读不到运行数据 —— 而这恰恰是 A 与 B 之间唯一的真判据,也是 B 唯一的静默破坏风险所在。
裁后我会怎么执行(你不用管):裁 B ⇒ 翻 pm:queue 派 domain:services 席,派发令强制先跑审计读数、零才动手、非零停手回报;裁 A ⇒ 新增 recall 专用视图信号属扩大公开面,走条款②契约复审档位;裁 C ⇒ 关 not planned,但必须把三方分歧写进 approval-service.ts 的散文,免得第三次被人重新量出来。
Filed by the triage seat (session
session_01Aujz2zykf5LXt3T98gRsGe) splitting the maintainer-floor half out of #12716, so that card is not held hostage to it. #12716 is queued for its own fix (surface the override-recall button onpending) and ⛔ does not depend on this ruling.Measured by the #12716 dev and independently re-verified by the
domain:servicesPM onorigin/mainat86df0c99e— full evidence chain in #12716 comment 5442740007. ⛔ Not re-measured by this seat; re-verify before acting.The three-way disagreement
returnedrequestapproval-service.ts:2763+ the status-blind override short-circuit at:2774attachViewers:4848,can_override: row.status === 'pending' && isOverrideActor(...)(ANDed):808"may always act on a PENDING request … or recall it", plus:2767,:4833Two surfaces say pending-only; the gate is the odd one out, and
isOverrideActor(context, requestOrg?)structurally cannot be status-aware — there is no status parameter.⭐ The reach looks like a side effect, not a grant. ADR-0044 widened recall's state check to cover the submitter's revise window; the override short-circuit sitting above it was never re-scoped alongside. Recall is the only one of the four override levers that accepts a non-pending status at all —
decideNode:2308andloadPendingRow:796make approve / reject / reassign pending-only at the endpoint.Why it is the maintainer's and not triage's
Retiring the reach narrows a permission on a live endpoint and removes a capability that exists in shipped code and is pinned by nothing. Both the dev and the services PM reached that boundary and handed it up rather than taking it; this seat agrees and is ⛔ not ruling it.
Options
can_overridecannot be relaxed, since it also feeds the three sibling predicates whose endpoints are pending-only. New declared surface, permanent obligation:2774short-circuit topendingso all three agree at one pointreturnedrequest, B breaks it silently<!-- os-decision-facets -->
一句话问题:管理员能不能撤回一张「已退回给提交人修改」的审批单?代码说能,产品文档和界面判据都说不能 —— 三处对同一个问题给了两个答案,而且没人知道哪个是当初想要的。
returned的记录锁已经释放(approval-revise.test.ts:293/:313pin 了returned → unlocked),而 Approval routed to an empty position permanently locks the record (no admin override, no recovery) #3424 的原始动机场景(审批路由到无人岗位、单子卡死)按定义是pending。⇒ 「管理员要来解救」这个理由在returned上不成立。推荐:B,附硬前置条件 —— 落地前先从审计留痕读出「是否真有 override actor 在
returned上撤回过」。读数为零才动手;非零则当场改判 A,⛔ 不许带着非零读数硬做。回退:A。置信缺口(本分析看不见什么):看不见生产部署里 override-recall-on-
returned的实际调用量。本席只能读代码,读不到运行数据 —— 而这恰恰是 A 与 B 之间唯一的真判据,也是 B 唯一的静默破坏风险所在。裁后我会怎么执行(你不用管):裁 B ⇒ 翻
pm:queue派domain:services席,派发令强制先跑审计读数、零才动手、非零停手回报;裁 A ⇒ 新增 recall 专用视图信号属扩大公开面,走条款②契约复审档位;裁 C ⇒ 关 not planned,但必须把三方分歧写进approval-service.ts的散文,免得第三次被人重新量出来。Refs
approval_recallaction hides the #3424 admin-override recall — the service admits it, the visible predicate never shows it #12716 — the queued half (surface the button onpending); its option A is correct-by-construction becausecan_overrideis already pending-scopedrequest: fresh!— a non-null assertion that is false whenever the read-back is org-filtered out, shipping{ "request": null }with HTTP 200 #12769 — the unrelated defect the same probe run surfaced