Skip to content

automation: ActionDescriptor.supportsPause / isAsync 是零读者的声明字段 —— 引擎判定「暂停」的唯一事实是 execute() 返回的 suspend: true #5703

Closed as not planned

Description

@os-zhuang

发现于 #5561 第一步(注册时对未声明 resumeAuthority 的 pausing executor 打告警)的前提重验。observation-class:今天没有用户会撞到,单独立单只为把事实记下来。

事实(origin/main @ 6ddb2ec)

  • ActionDescriptor.supportsPause 与 ActionDescriptor.isAsync(均在 packages/spec/src/automation/node-executor.zod.ts,均 .default(false))在 framework / objectui / cloud 三仓没有任何非测试读者。仓库范围内对 .supportsPause / .isAsync 的读取只有 3 处断言:
    • packages/spec/src/automation/node-executor.test.ts:174(读默认值)、:218(读 legacy WAIT_EXECUTOR_DESCRIPTOR)
    • packages/plugins/plugin-approvals/src/approval-node.test.ts:100
  • 引擎判定「这个节点暂停了」的唯一事实是执行结果,不是描述符声明:packages/services/service-automation/src/engine.ts:4256 的 if (result.suspend)。
  • automation: the generic run-resume route needs an authorization gate keyed on the suspended node #3801 的 resume 闸门也不看它:resolveResumeAuthority(engine.ts:2708)只沿 alias 走到 canonical 后读 resumeAuthority,supportsPause 全程不参与。

为何是 observation-class

今天全部 6 个 pausing 执行器都在本仓内,且 supportsPause: true 声明都正确 —— screen(builtin/screen-nodes.ts:50)、wait(builtin/wait-node.ts:166)、subflow(builtin/subflow-node.ts:54)、map(builtin/map-node.ts:53)、approval(plugin-approvals/src/approval-node.ts:114)、approval_revise(approval-revise-node.ts:79)。仓内没有第三方 pausing 插件,示例应用也不注册任何节点执行器,所以这个字段今天错不了。

为何仍值得记

#5561 第一步的裁决把告警的触发条件定在 supportsPause: true。一旦有代码开始按这个字段下判词,它就从「无人读的声明」变成安全判定的输入 —— 而它当前不被任何执行路径校验:一个 execute() 返回 suspend: true、却把 supportsPause 留在默认 false 的执行器,既不会被那条告警点名,又照样拿到 resumeAuthority 的 fail-open 默认。这就是 declared 不等于 enforced(Prime Directive #10)在同一个节点上的第二层,且正好落在 #5561 想堵的那条缝上。

两条路(只记录,不在本单裁)

按 ADR-0049 enforce-or-remove 择一:

  1. enforce:在 engine.ts:4256 的 result.suspend 处对照该节点类型描述符的 supportsPause,不一致就拒绝/告警(把描述符声明变成运行时可反驳的事实)。
  2. 承认它是纯设计器面字段:它确实已经进了 content/docs/references/automation/node-executor.mdx,那就在 TSDoc 里写清「声明供设计器/文档使用,运行时不校验」,别让下一个作者以为它是闸门输入。

Refs #5561、#3801、#3823。

Activity

  1. added
    domain:specDomain lane — seat card indexed by label:pm:seat
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 6, 2026
  2. claude commented on Aug 6, 2026

    @claude
    Contributor

    发现分诊轮判级(#4949 纪律):持有(finding 留)。

    为什么还留着:事实成立(ActionDescriptor.supportsPause / isAsync 三仓零非测试读者,引擎判"暂停"的唯一事实是 engine.ts:4256 的 if (result.suspend),#3801 的 resume 闸门也不看它),但处置是退役一个已发布的 spec 声明字段 —— 属 breaking,须走 ADR-0049 enforce-or-remove 的完整路线(ADR-0087 转换 + 生成基线 + 文档 + pin 测试),而 v17 的退役窗已随 #4938 批次收口。今天无用户可达伤害:全部 6 个 pausing 执行器都在本仓内且声明都正确,声明与事实暂时同向。

    重启条件:v18 列车的退役批开列时一并测量与判定 —— 与 #5295(ServerEvent* / ServerCapabilities / ServerStatus 零消费者,08-05 已按同一理由持有、同一重启条件)同批处理,两单是同一形态(spec 侧幸存声明 + 零读者 + 退役属 breaking),分开判会得出不一致的口径。

    ⚠️ 另一条可先行的路(若 services 车道认为值得):把「声明与事实不同向」变成门可见 —— 注册时校验 supportsPause 与执行器实际是否返回 suspend 的一致性(即 #5561 那条告警的同族),这不需要退役任何字段,可独立立单。本轮不代拆。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    存量裁决轮(维护者 2026-08-06 委托,session_01LeEfA7CFwbJb7JJmXm2KM3):维持持有,但记录一处前提更正——「零读者」已过时:#5561(PR #5725,06:03Z 前合入)已在 engine.ts:1388 落地按 supportsPause 下判词的告警,其 TSDoc 明写「#5703 tracks the runtime half」。本单现追踪的是 runtime 半边(execute 返回 suspend 而声明 false 时告警静默 fail-open);今天仓内 6 个 pausing 执行器声明皆正确、无第三方执行器,无人可撞。重启条件:services 车道投资一致性校验,或第三方 pausing 执行器面出现。


    Generated by Claude Code

  4. claude commented on Aug 7, 2026

    @claude
    Contributor

    Findings triage (#4949 discipline): hold — finding retained, domain:spec unchanged.

    Stale-premise check. The 08-06 08:06Z re-grading already corrected the body's "zero readers" premise: #5561 / PR #5725 landed a registration-time warning that does judge on supportsPause (engine.ts:1388), so what this issue now tracks is strictly the runtime half — an executor whose execute() returns suspend: true while its descriptor leaves supportsPause at the .default(false), which the warning cannot see and which still reaches resumeAuthority's fail-open default. That scoping still holds this round; no new commit changes it.

    One state change worth recording, because it moves the restart condition: #5561 is now pm:on-hold (it was open and active when the previous restart condition was written). Both of that condition's clauses therefore need re-reading:

    Restart condition, re-anchored to the surviving carrier: the v18 retirement batch, processed together with #5295 (ServerEvent* / ServerCapabilities / ServerStatus, held 08-05 on the same grounds). Both are the same shape — a surviving packages/spec declaration with no production reader, whose removal is breaking and so owes the full ADR-0049 enforce-or-remove route (ADR-0087 conversion, generated baselines, docs, pin tests). Judging them apart would produce inconsistent verdicts on one question, which is why they stay batched. The v17 retirement window closed with the #4938 batch, so neither can move before that train.

    Unchanged and still available as an independent path if the services lane wants it: making declaration-vs-fact gate-visible (validate at registration that a descriptor claiming no pause never returns suspend) retires nothing and needs no window. ⛔ Not split out by this seat — that is the lane's call, and the transfer protocol runs through the target queue, not through this thread.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  5. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue as a retirement card. ActionDescriptor.supportsPause / isAsync have zero non-test readers across three repos while the engine's only pause fact is result.suspend — declared-but-inert fields under the maintainer's standing tight default (today's family rulings: #6247 retire, #5488 flip-to-false). ADR-0049 remove side unless a reader is found at claim time. finding → pm:queue.


    Generated by Claude Code

  6. added
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 8, 2026
  7. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Bundled into sweep card #6486 (ADR-0049 zero-consumer retirements, 5 members). Keeps pm:queue but is excluded from batch selection while #6486 is open — claim the sweep card, not this one; it closes via Fixes when that PR merges.


    Generated by Claude Code

  8. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling (2026-08-08): Option A — close this card; successor filed as #6667.

    The card's premise ("zero readers in all three repos") expired while it sat: #5561 / PR #5725 landed warnIfResumeAuthorityUndeclared (engine-side wording keyed on supportsPause), the check-resume-authority-declared.mjs CI gate, dedicated tests, and TSDoc. supportsPause is now an enforced input, not an inert declaration — retiring it would delete an enforcement surface the maintainer just shipped. isAsync, taken alone, still has zero readers; its disposition moves to the successor so it gets decided on fresh measurements at pickup time, not today's reading.

    Successor #6667 carries the two remaining pieces: the runtime consistency check (executor suspend result vs declared supportsPause — the blind spot the authoring-time gate does not cover) and the isAsync decision.

    Closing as not planned: the retirement this card proposed will not happen.

    Maintainer directive (verbatim, covering all 14 inbox cards): 「你的建议全部接受」. Recorded by PM session session_01JaVVMrSxt7Tgi1uwEuDtH7.


    Generated by Claude Code

  9. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specDomain lane — seat card indexed by label:pm:seat

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions