Skip to content

[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

Description

@huangyiirene

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 signalcan_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

<!-- os-decision-facets -->

一句话问题:管理员能不能撤回一张「已退回给提交人修改」的审批单?代码说能,产品文档和界面判据都说不能 —— 三处对同一个问题给了两个答案,而且没人知道哪个是当初想要的。

  • ① 项目长远合理性:B 让闸门、散文、视图旗标在一个点上一致,消除三方分歧;A 也能一致,但方向相反且要新增一个声明信号;C 把矛盾永久化 —— 它这一轮已经让一次分诊裁决翻车,下一个读代码的人还会再量一次。
  • ② 实际业务拉动:⚠️ 未测,而且这是本卡最关键的空白 —— 不知道有没有运维流程真的在用「管理员撤回已退回单」。但有一条反向证据:returned 的记录锁已经释放(approval-revise.test.ts:293/:313 pin 了 returned → unlocked),而 Approval routed to an empty position permanently locks the record (no admin override, no recovery) #3424 的原始动机场景(审批路由到无人岗位、单子卡死)按定义是 pending。⇒ 「管理员要来解救」这个理由在 returned 上不成立。
  • ③ 防 AI 犯错:C 最差 —— 三个面对同一问题给三个答案,任何按约定读代码的 agent 都会推断错(本轮已实证)。B 让代码与散文一致且可被一条 pin 钉住;A 新增一个 recall 专用信号,面更大、更容易再漂。
  • ④ 创业阶段不扩散:B 是收窄,不新增面;A 新增一个永久维护的声明信号。

推荐:B,附硬前置条件 —— 落地前先从审计留痕读出「是否真有 override actor 在 returned 上撤回过」。读数为零才动手;非零则当场改判 A,⛔ 不许带着非零读数硬做。回退:A

置信缺口(本分析看不见什么):看不见生产部署里 override-recall-on-returned 的实际调用量。本席只能读代码,读不到运行数据 —— 而这恰恰是 A 与 B 之间唯一的真判据,也是 B 唯一的静默破坏风险所在。

裁后我会怎么执行(你不用管):裁 B ⇒ 翻 pm:queuedomain:services 席,派发令强制先跑审计读数、零才动手、非零停手回报;裁 A ⇒ 新增 recall 专用视图信号属扩大公开面,走条款②契约复审档位;裁 C ⇒ 关 not planned,但必须把三方分歧写进 approval-service.ts 的散文,免得第三次被人重新量出来。

Refs

⚠️ Dedup run by this seat over the override / recall / revise-window family before filing: no existing card covers this scope question.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions