fix(automation,approvals): 节点类型校验推迟到插件词汇表封闭之后 —— approval flow 不再被误报「运行时会失败」 - #4791
Merged
Merged
Conversation
…plugin vocabulary is closed (#4771) ADR-0018 makes the flow node-type vocabulary open and runtime-extensible: a plugin contributes types via registerNodeExecutor() from its own init()/start(). AutomationServicePlugin.start() pulls flows from the ObjectQL registry and validated each node type on the spot — ~0.8s before ApprovalsServicePlugin registered the `approval` executor. Every showcase cold boot therefore asserted that eight ADR-0019 approval flows "will fail at execution time", and all eight were false. Worse, a deployment that genuinely lacks the approvals plugin produced the identical eight, so the warning could not distinguish the two. - AutomationEngine.sealNodeTypeVocabulary() declares the vocabulary closed and runs the authoritative audit, warning once per offending flow. Idempotent. - AutomationEngine.getUnknownNodeTypeAudit() exposes the same finding as state (mirrors getTriggerBindingAudit) for hosts that read the engine, not the log. - AutomationServicePlugin seals at kernel:bootstrapped — strictly after every plugin's start() AND every kernel:ready handler (its own registers more flows). - After sealing, registerFlow warns inline again: a Studio publish / dev reload into a running server IS judged against a complete vocabulary. Timing fix, not a mute. - Disabled flows (obsolete/invalid) are skipped: a flow that cannot run cannot fail at run time. Same defect class, second half: ApprovalsServicePlugin logged "no automation engine — approval node not registered" at info while dev defaults to warn, so the real degradation was invisible exactly when it happened (#4632). Now warn, naming consequence and remedy; the catch is narrowed to the service lookup so a failure inside registerApprovalNode surfaces as itself; and the service-exists-but-takes-no-executors branch, which logged nothing at all, warns too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 6 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
表格原文说节点 type「is checked against the live action registry at `registerFlow()`」。本 PR 之后这句不再为真:启动期注册的 flow 在词汇表封闭 (所有插件 start() 完毕)时统一校验,封闭之后的注册才回到即时校验。顺带写明 这条检查是 warn 而非拒绝,以及执行时的 NO_EXECUTOR 后果。 同文件其余提到 `registerFlow()` 的句子(config 键、条件表达式、ADR-0031 区域) 描述的是没有改动的校验,保持原样。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
os-zhuang
marked this pull request as ready for review
August 3, 2026 07:03
This was referenced Aug 3, 2026
os-zhuang
pushed a commit
that referenced
this pull request
Aug 3, 2026
`AuditAbortSignal` 从包根导出,加上 `DanglingReferenceAuditOptions.signal` 与 `DanglingReferenceReport.aborted` 两个新字段:新增公开面按仓内先例 (#4791 因新增 `sealNodeTypeVocabulary()` 等公共 API 定 minor)是 minor, 不是 patch。`@objectstack/cli` 只动了内部的 schema-migrate.ts,维持 patch。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
os-zhuang
pushed a commit
that referenced
this pull request
Aug 3, 2026
`fillEmptyGroups` 从包根 index.ts 导出,属于新增公共 API,按 #4815 (objectql / AuditAbortSignal)与 #4791(service-automation / sealNodeTypeVocabulary)确立的同一条 bar,changeset 从 patch 改为 minor, 并在正文点名这个新导出。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
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 #4771
问题
AutomationServicePlugin.start()从 ObjectQL registry 拉起 flow 并当场校验节点类型;ApprovalsServicePlugin.start()在 0.8 秒后才注册approval执行器。校验器在词汇表还没成型的时候就下了结论 —— showcase 每次冷启的那 8 条「will fail at execution time」全是假的。
而 ADR-0018 明确把节点词汇表定义为开放、可运行时扩展的(插件通过
registerNodeExecutor(type)贡献类型)。在扩展完成前拍板的校验器,是在校验一个还没成型的世界。
真正的代价不是噪音,是信号丢失:真的没装 approvals 插件的部署会得到一模一样的 8 条告警,
这条 warn 的信噪比因此是 0。本 PR 之后它才第一次具备拦截价值。
改动(PM 裁定的修法 1)
packages/services/service-automationAutomationEngine.sealNodeTypeVocabulary()—— 宣告词汇表封闭,对所有已启用的已注册 flow跑一次权威校验,每个有问题的 flow warn 一条。幂等:只有第一次会打日志,之后仍返回当前 audit。
AutomationEngine.getUnknownNodeTypeAudit(): UnknownNodeTypeAuditEntry[]—— 同一发现的状态形态(供 CLI 启动摘要 / 健康检查直接读引擎,而不是 grep 日志)。与
getTriggerBindingAudit()同一套路。AutomationServicePlugin在kernel:bootstrapped调用它。registerFlow恢复即时告警:Studio 发布 / dev reload 进正在运行的服务器时,词汇表确实完整,那句断言此时为真。这是时序修复,不是把告警静音。
status: 'obsolete'/'invalid')不再上报 —— 跑不起来的 flow 谈不上「运行时失败」,这正是本 issue 要消灭的那类假断言(与
getTriggerBindingAudit的口径一致)。为什么是
kernel:bootstrapped而不是kernel:ready裁定的语义是「所有插件
start()完成之后」,kernel:bootstrapped严格更晚且更安全:kernel:readyhandler 还会再注册一批 flow(syncFlowsFromProtocol,即inline app flow 的冷启绑定);
kernel:ready里贡献执行器;packages/core/src/kernel.ts对这个 hook 的定义原文就是:「Reconcile/backfill work thatconsumes data produced by a later-starting plugin's
kernel:readyhandler belongs here, not inkernel:ready(where handler order would race the data)」。本审计正是这种消费者。告警可见性不受影响:CLI 的 boot-quiet 窗口会截获整个 boot 期的
logger.warn并在 banner 下重放(#4012),
kernel:bootstrapped在bootstrap()之内。packages/plugins/plugin-approvals(同一缺陷类的另一半)info改为warn—— dev 默认日志级别是warn,原来真降级发生时反而看不见([convention] best-effort 降级导致"看起来正常、实则不持久"时不应记 warn——把 #4460 的点状修复定成规则 #4632)。文案写明后果(该部署里每个 ADR-0019 approval flow 都会以
NO_EXECUTOR失败)与补救(装
@objectstack/service-automation)。按 AGENTS.md「Degradation log levels」,这是功能性降级(能力没装上,不是数据没落盘),
warn是正确档位。catch收窄到「服务查找」这一步:registerApprovalNode内部真出错时以自己的身份抛出,不再被贴上 "no automation engine" 的错误标签。
automation服务存在但不接受节点执行器的分支,从前一条日志都不打,现在同样 warn。测试(两种部署必须可区分 —— 验收标准 1 & 2)
packages/services/service-automation/src/flow-node-type-audit.test.ts(新增,LiteKernel 真启动)复现条件是断言出来的,不是假设的:每个用例都从 boot 日志证明
Flow registered: …出现在Node executor registered: approval之前 —— 即修复前误报的那个窗口确实发生了。getUnknownNodeTypeAudit()为空approval+ 补救动作;audit 有对应条目0 !== 1—— 修复前这两个 boot 打印的东西完全一样反向验证:把
nodeTypeVocabularySealed临时改成初值true(等价于修复前的即时校验),该文件 4 个用例里 3 个失败(其中「没装」那条从 2 变成 4 条告警 —— 即注册时 + 封闭时各报一次)。
engine.test.ts增补:封闭前不告警 / 封闭时按 flow 报 / 封闭后即时报 / 禁用 flow 不报 /幂等封闭只 warn 一次 / 「插件后注册」不再误报。
plugin-approvals/src/approval-node-degradation.test.ts(新增):无 automation 服务 →warn且不再是
info;服务在但不收执行器 → 同样warn;正常时注册approval且不告警。service-automation没有typecheck脚本(ratchet 里是 DEBT 条目),直接跑tsc --noEmit:改动前后都是同样的 5 个既有报错(
engine.test.ts2 个 TS2739 +nested-region-parity.test.ts3 个 TS2341,后者访问私有
flows,与本 PR 无关),本 PR 零新增。另跑:
check-durability-degradation-log-level、check-changeset-fixed、check-changeset-no-major、check-adr-anchors全绿;改动文件 eslint 无输出。范围
只碰
packages/service-automation+packages/plugins/plugin-approvals(+ 一个 changeset),未触碰本轮并行批的
objectql/service-storage/plugin-auth。嵌入式 host 注意
直接
new AutomationEngine()而不经过AutomationServicePlugin的宿主,需要在自己的插件都装好之后调用一次
sealNodeTypeVocabulary(),才能拿到这条告警(以及之后的即时校验)。已写进 changeset。🤖 Generated with Claude Code
https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
Generated by Claude Code