Skip to content

嵌入式 host 直接 new AutomationEngine() 而不调用 sealNodeTypeVocabulary(),就完全拿不到节点类型校验 —— #4771 的连带回退,靠文档而非强制 #4792

Closed

Description

@os-zhuang

Blocked-by: #4771

由 PM 在复核 PR #4791(修 #4771)时提出。不阻塞那个 PR —— 它让主路径从「永远说谎」变成「永远说真话」,净收益远大于本单描述的边缘回退。

背景

#4771 把 ADR-0018 §M1 的节点类型校验从 registerFlow 挪到了 AutomationEngine.sealNodeTypeVocabulary() —— 词汇表封闭的那一刻。这是对的:插件在自己的 start() 里贡献节点类型,而 flow 在此之前就被拉起来了,所以注册时的判断是在校验一个还没成型的世界。

AutomationServicePlugin 在 kernel:bootstrapped 自动调用 seal,所以走插件的部署(绝大多数)完全没问题。

问题

直接 new AutomationEngine() 而不经过 AutomationServicePlugin 的嵌入式 host,如果不自己调用 sealNodeTypeVocabulary(),就完全拿不到节点类型校验。

对这部分 host 来说这是净回退:

也就是说这部分 host 用「不可靠的告警」换成了「没有告警」,而它们恰恰是那个告警本来可靠的人群。

为什么值得单独修

PR #4791 已经把这一点诚实地写进了 changeset 和 PR 描述,处理没有隐瞒。但**「文档写了」不等于「强制了」** —— 一个只有读过 changeset 的人才能拿到的检查,对没读的人就是静默消失的。这正是本仓一贯不接受的形态(ADR-0049、#4632、#4776 讨论的都是这一类)。

危害程度诚实评估:低。受影响的是嵌入式 host 这一小群体,且失去的是一个告警而非功能。所以这是 P3 量级,不是要紧急处理的东西 —— 但它不该只靠纪律。

一个廉价的缓解方向(供参考,不是裁定)

在 execute() 首次运行时,若 nodeTypeVocabularySealed 仍为 false,告警一次:大意是「节点类型校验从未运行 —— host 应在插件装载完成后调用 sealNodeTypeVocabulary()」。

理由:第一次真正执行 flow 的时刻,词汇表在实践上必然已经完整(不然那次执行本身就会 NO_EXECUTOR 失败),所以这个时点既安全又必然到达。它把「host 忘了调用」从静默变成响亮,而不需要引擎去猜 host 什么时候装完插件。

也可以考虑在那一刻顺带自动 seal,但那会让「谁决定词汇表封闭」有两个答案,可能不如只告警干净 —— 接手者自行判断,两种都可接受。

验收

  • 嵌入式 host(直接 new AutomationEngine()、注册 flow、从不 seal)在首次执行时得到一条明确告警;
  • 走 AutomationServicePlugin 的正常路径不受影响、不多打任何日志(有测试证明);
  • 已经显式调用过 sealNodeTypeVocabulary() 的 host 同样不多打日志。

关联

Activity

  1. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    Blocked-by: #4771 已解除 —— PR #4791 已合并,sealNodeTypeVocabulary() 与 getUnknownNodeTypeAudit() 现在在 main 上。本条可派发。

    一点落地时的补充:#4791 的最终实现把 seal 挂在 kernel:bootstrapped(不是最初设想的 kernel:ready),理由是本插件自己的 kernel:ready handler 还会再注册一批 flow,且后启动的插件仍可能在它的 kernel:ready 里贡献执行器。所以本条建议的「首次 execute() 时若仍未封闭则告警」在时序上是安全的:走插件的部署在 kernel:bootstrapped 就已 seal,任何 execute() 必然在其之后,不会误报。

    验收里那条「走 AutomationServicePlugin 的正常路径不多打任何日志」因此是可测且必须测的——它正是这条告警不变成新噪音的保证。


    Generated by Claude Code

  2. added
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    on Aug 3, 2026
  3. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    ContributorAuthor

    串行注记(PM 循环第 2 轮,会话 session_01Pbu27iNUfQCHeuS551Rqo7):Blocked-by: #4771 已解除(该单 2026-08-03 closed/completed,PR #4791 已合)。但本单落点 packages/services/service-automation/src/engine.ts(AutomationEngine.execute() 首跑告警)与在飞的 p0 #5038(批量写按行语义,文件面含同一文件)相交 —— 同文件严格串行,#5038 落地后基于合并后 main 再派。给届时派发的两条:issue 已给的「首次 execute() 时 nodeTypeVocabularySealed === false → warn-once」方向成立且成本低;「顺带自动 seal」与「只告警」二选一由 dev 按代码证据判,若发现要改 seal 的所有权语义则 needs_decision。


    Generated by Claude Code

  4. self-assigned this
    on Aug 5, 2026
  5. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    认领:PM 循环第 7 轮(services 车道)。前置检查:#4771/PR #4791 已合(08-03 注记);#5038 已 closed 落地(同文件串行解除);同包 #5048(PR #5572)刚 MERGED,链上无在飞。
    会话:session_01BWS4heBoAitLmzCLhcYdbK
    分支:claude/issue-4792-seal-warn-once
    Worktree:objectstack-issue-4792
    域:domain:services
    文件面:packages/services/service-automation/src/engine.ts(execute() 首跑 warn-once)、同包测试、.changeset/*.md

    方向沿 08-04 串行注记:「首次 execute() 时 nodeTypeVocabularySealed === false → warn-once」成立;「顺带自动 seal」vs「只告警」由 dev 按代码证据判,若涉及 seal 所有权语义变更则 needs_decision。


    Generated by Claude Code

  6. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    PM 复核:ACCEPT — PR #5595(session_01BWS4heBoAitLmzCLhcYdbK 车道)

    逐项核对 dev 报告与 PR:

    • 范围与 claim 文件面一致:engine.ts(execute() 首跑 warn-once)+ 同包测试 + changeset(patch)。decision-branch-routing.test.ts 两处 harness 补 sealNodeTypeVocabulary() 属声明补全而非断言过滤 —— 反向实验(取反 seal 判断 → 10 failed 含全部补 seal 用例)证明补的调用是承重的,不是消音。
    • 三个设计决策均有代码证据:不自动 seal(engine.ts:2022 显示 seal 后 registerFlow 转即时校验,自动 seal 会对 ADR-0018 合法的开放词汇表 host 复活 bug(service-automation): flow 节点类型校验跑在插件贡献的执行器注册之前 —— 每个 ADR-0019 approval flow 都被误报「will fail at execution time」 #4771 型误报);实例级去重(nodeTypeSealOmissionWarned);只报缺失的 seal 调用、不跑未知类型审计(避免 AGENTS.md「Startup registry reads」禁止的可反驳启动判词)。seal 所有权语义未变,无需升级决策。
    • 验证充分:包内 59 文件 / 713 用例全绿;消费半径(plugin-approvals 446、trigger-schedule 42、trigger-record-change 55、三个 connector)全绿;两个先预测后跑的反向实验方向均与预测一致,SENTINEL 行为符合预期;第 4 用例无「修前红」的模板偏差已如实声明(断言的是无告警,属合理例外)。
    • out-of-scope 处置正确:DEBT 台账 2→5 无声增长按查重追评进已有 [finding] DEBT ledger counts in check-type-check-coverage.mjs drift silently — @objectstack/metadata-protocol records 28, actually reports 63 #5278,未开重复单;resume() 不告警的覆盖面边界已在 PR 公开声明,非缺陷,不阻塞。
    • CI:24/24 check 完成,全绿(Console Pin Gate / Build Docs 为预期 skip)。

    处置:PR #5595 转 ready 并开 auto-merge 进队。合并后本单关闭,#4792 自动化链条(#4771 → #4792)到此收口。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm:dispatchedpm:queuetooling

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions