fix(approvals): 无自动化引擎时审批决策不再静默搁浅流程 run (#4420) - #4969
Merged
Conversation
…g the run silently (#4420) #4420's fix closed every path by which an approval decision could be recorded while its flow stayed parked — except the one where none of its guards could run. `assertRunResumable`, the `RESUME_TARGET_LOST` refusal and the `RESUME_FAILED` throw all hang off the automation engine, and in a process with no engine attached every one of them was skipped by the same `typeof this.automation?.resume === 'function'` condition that wrapped the resume itself. The decision was written, the mirrored status field advanced, and the call answered HTTP 200 / `resumed: false` with nothing logged — #4420's reported symptom, in the one composition its fix could not see. Reachable exactly the way the original bug was: a run parks at an `approval` node in a process that has the automation service, and the decision arrives in one that does not. The row still carries a `flow_run_id`, which is its own declaration that a run is parked on the decision. The decision still stands — it is durable by then, and refusing every such call would break the standalone compositions the pre-flight deliberately protects — so `finalized` / `resumed` are unchanged for existing callers. The gap is now reported: `error` level per AGENTS.md's durability rule (persisted state and runtime state disagree while nothing looks broken), plus `resumeError` on the response naming the stranded run. Reuses the registered `RESUME_FAILED` code and the existing resume message shape rather than adding a vocabulary. Applied at all five recorded-outcome resume sites: decide, the revision-limit auto-rejection, sendBack, resubmit, and both branches of recall (whose revise-window path needs `cancelRun`). A request naming no run stays quiet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
Fixes #4420
origin/main上都已交付派发词要求先诊断再动手。诊断结果是:issue 描述的 rc.1 现场已经被修好了,修复随
17.0.0-rc.2出货(changeset2826d1e),并非「#1518 未落地」。实测证据(全部跑在origin/main=6bc93dc,未含本 PR 改动):AutomationServicePlugin.start()(plugin.ts:596)。默认suspendedRunStore: 'auto'即启用,默认路径已接线sys_automation_run让 schema sync 建表?init()向manifest注册(registerRunObject),start()补注册一次;并声明了optionalDependencies: ['com.objectstack.engine.objectql']保证顺序approval-restart-resume.test.tsresume(runId)找不到 run 也早已响亮:引擎返回RUN_NOT_FOUND/STORE_UNAVAILABLE,serviceResume把success:false抛成异常,assertRunResumable在任何写入之前预检,REST 映射 409 / 503 / 500。所以 #4420 主体无需重做。 维护者可据此确认是否直接关单。
本 PR 修的是:那次修复唯一没能覆盖到的一条静默路径
#4420 的修复加的每一道闸门 ——
assertRunResumable预检、RESUME_TARGET_LOST拒绝、RESUME_FAILED抛错 —— 全都挂在 automation 引擎上。而在一个没有挂引擎的进程里,它们被包在同一个条件里一起跳过了:于是:决策落库、镜像状态字段推进、接口返回 HTTP 200 +
resumed: false,且零日志。这正是 #4420 报告的症状本身 —— 只是发生在它的修复看不见的那个 composition 里。可达路径与原 bug 同源:流程在有 automation 的进程里停在
approval节点,决策却到达一个没有 automation 的进程(插件 init 失败,或跨版本重新 composition)。此时请求行仍带着flow_run_id—— 那是这行数据自己声明有一个 run 正停在这个决策上。原有测试其实把这个行为钉住了(
leaves a composition with no automation engine exactly as it was),理由是「这里没有任何东西能 resume 它,预检不该无中生有地造一个失败」。该理由混淆了两件事:「没挂引擎」和「没有 run 在等」是不同的事实,只有后者是良性的。改动
决策依然成立,
finalized/resumed对所有既有调用方保持不变 —— 决策落库时人确实已经批了,回滚不在选项内;而对每个这类调用都报错会打断预检刻意保护的 standalone composition。变的只是「不再静默」:error级日志 —— 依 AGENTS.md 的 durability 规则:持久化状态与运行时状态不一致,而系统从外面看毫无异样,正是该规则针对的那一类;resumeError—— 让resumed: false带着原因和被搁浅的 run id 到达调用方,而不是让对方猜「到底试没试过 resume」。复用已注册的
RESUME_FAILED码(ADR-0112 ledger)与既有 resume 消息形状,不新造词汇 —— 因为要报告的事实是同一个:一个已记录的结果,其 run 没有推进。覆盖全部 5 个 recorded-outcome resume 点:
decide、改版次数超限的自动拒绝、sendBack、resubmit,以及recall的两个分支(revise 窗口那支用的是cancelRun而非resume)。没有过度报告:不带 run 的请求保持安静 —— 它上面没有东西被搁浅,在那里报
error正是会训练运维忽略error的镜像错误,而那恰恰是 #4420 当初那条warn没人读的原因。测试
新增 2 条回归测试(替换那条把静默钉住的旧测试):
still records the decision with no automation engine attached — but never silentlyreports the same gap on a request that names no run — by staying quiet约束遵守
packages/spec/**零改动(故复用已注册的RESUME_FAILED,不新增 ledger 码)、metadata-protocol/src/protocol.ts零改动、content/docs/releases/零改动。改动面仅packages/plugins/plugin-approvals/src/**+ 一个 changeset。待维护者定夺(见下)
本 PR 取的是「决策保留 + 响亮报告」。另一种读法是「拒绝这个决策」(视为坏 composition,返回 409)。后者语义更强,但会翻转上述被刻意钉住的设计决定,且可能打断合法的 standalone 部署 —— 故未擅自采用,列为 open question。
🤖 Generated with Claude Code
https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
Generated by Claude Code