发现于 #5529 的实测过程(PR 见该 issue),与该修复相邻但不同面 —— 这是 job 域的审计面问题,不是 automation 域的。
观察(实测,非推断)
DbJobAdapter.wrap()(packages/services/service-job/src/db-job-adapter.ts L161-176)只按 handler 是否抛错判定运行结果:
try { await handler(ctx); finishRun(runId, 'success'); bumpJob(name, 'success'); }
catch { finishRun(runId, 'failed', msg); bumpJob(name, 'failed', msg); throw err; }
对于「内部自行处理失败、不抛错」的 handler,这条路径把它记成成功。实测(临时 harness,已删除):一个 once job 的 handler 正常返回但什么也没完成,sys_job 行是
{"name":"flow-wait:run1:pause","active":true,"schedule_expression":"2026-08-05T16:45:20.837Z","last_status":"success","run_count":1}
sys_job_run 只有一行 status: 'success'。
#5529 的 wait 唤醒正是这种 handler:engine.resume() 用返回码报错(AutomationResult.code)而不抛错,所以 STORE_UNAVAILABLE(这一枪打空、run 仍挂在 wait 节点)在 sys_job_run 上和「成功唤醒」完全一样。
为什么可能值得看
sys_job / sys_job_run 是机器可读面(Studio 的作业界面读它)。一种读法是 sys_job_run.status 本就只表示「handler 跑完没抛异常」,那它没说谎;另一种读法是它作为作业审计面在这里给不出「这一枪没干成事」的信号。两种读法都成立,所以这里只做记录、不预判严重度。
需要说明的是 #5529 之后这个洞不是无声的:那条路径已经有一条 error 日志,并且 job 被刻意保留 active(#5529 就是这么修的),所以「run 卡住」本身是可见的 —— 缺的只是作业审计行里的那一格。因此按观察类归档(finding,不进 pm:queue),严重度交分诊判定。
可能的方向(未决,不在本 issue 范围内实施)
佐证
发现于 #5529 的实测过程(PR 见该 issue),与该修复相邻但不同面 —— 这是 job 域的审计面问题,不是 automation 域的。
观察(实测,非推断)
DbJobAdapter.wrap()(packages/services/service-job/src/db-job-adapter.tsL161-176)只按 handler 是否抛错判定运行结果:对于「内部自行处理失败、不抛错」的 handler,这条路径把它记成成功。实测(临时 harness,已删除):一个 once job 的 handler 正常返回但什么也没完成,
sys_job行是sys_job_run只有一行status: 'success'。#5529 的 wait 唤醒正是这种 handler:
engine.resume()用返回码报错(AutomationResult.code)而不抛错,所以STORE_UNAVAILABLE(这一枪打空、run 仍挂在 wait 节点)在sys_job_run上和「成功唤醒」完全一样。为什么可能值得看
sys_job/sys_job_run是机器可读面(Studio 的作业界面读它)。一种读法是sys_job_run.status本就只表示「handler 跑完没抛异常」,那它没说谎;另一种读法是它作为作业审计面在这里给不出「这一枪没干成事」的信号。两种读法都成立,所以这里只做记录、不预判严重度。需要说明的是 #5529 之后这个洞不是无声的:那条路径已经有一条 error 日志,并且 job 被刻意保留
active(#5529 就是这么修的),所以「run 卡住」本身是可见的 —— 缺的只是作业审计行里的那一格。因此按观察类归档(finding,不进pm:queue),严重度交分诊判定。可能的方向(未决,不在本 issue 范围内实施)
IJobService的失败语义(第三方实现可能据此重试),超出该 issue 派发范围。IJobService的 handler 一个「跑完了但没干成」的回报方式(而不是只有 throw / 不 throw 两态),由适配器映射到一个区别于success的sys_job_run.status。这会动IJobService契约面,属公开契约决定。sys_job_run.status的语义就是「handler 未抛错」,并把这一点写进契约注释,免得下一个读者重新推一遍。佐证
packages/services/service-job/src/db-job-adapter.tsL161-176(wrap),L277-299(bumpJob)。packages/spec/src/contracts/job-service.ts——JobHandler返回Promise< void >,没有第三态。STORE_UNAVAILABLE时不取消 + 记 error,是本条观察的具体触发场景。