fix(core)!: kernel:ready 抛错在两个内核上一律失败 boot(#5170) - #5258
Merged
Conversation
…er fails the boot on LiteKernel too (#5170) The two kernels ran one hook name through two dispatchers with opposite failure semantics: ObjectKernel's `context.trigger` is a bare awaited loop that never catches (a throwing `kernel:ready` handler fails the boot), while LiteKernel's `triggerHook` wraps every handler in try/catch and continues ("Continue with other handlers even if one fails"), so the same plugin code got one error log and a "✅ Bootstrap complete". `kernel:ready` is the only correct moment for a plugin to assert that a precondition it DECLARED was actually delivered — the service registry is still filling during init() — so every "declare it and we refuse to start if we cannot honour it" gate lives there. On LiteKernel (vitest/serverless/edge) those gates were silently downgraded to a log line while the process came up and served traffic without the guarantee it had announced. `triggerHookOrThrow` is the propagating dispatcher, used by LiteKernel for `kernel:ready` only; the failed boot leaves the kernel 'stopped' rather than 'running', mirroring ObjectKernel.bootstrap()'s catch. The other lifecycle hooks keep LiteKernel's isolating dispatch, pinned by a test so widening it has to be a deliberate change (filed as #5257). Blast radius surveyed before the change and verified after: core (422), client, runtime, http-conformance, connector-{rest,mcp,slack}, service-automation (665) all pass unchanged — nothing relied on the swallow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 26 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 4, 2026 13:11
os-zhuang
enabled auto-merge
August 4, 2026 13:12
This was referenced Aug 4, 2026
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 #5170
按单上记录的决策落 方案 A(统一为传播):
kernel:readyhandler 抛错在ObjectKernel与LiteKernel上一律失败 boot。B/C 未实现。证据闸(改行为之前先做的摸底)
派发词要求先摸清 blast radius,结论是 小到零,可以推进:
kernel:readyhandler:全仓 56 处非测试注册点(git grep -n "hook('kernel:ready'" origin/main),其中能出现在 LiteKernel 组合里的是ObjectQLPlugin、HonoServerPlugin、AppPlugin、AutomationServicePlugin、dispatcher/rest 系列。逐个读过:它们要么是 warn-and-skip,要么自带 try/catch(例如 automation 的syncFlowsFromProtocol在没有 objectql 时只 warn —— 实测日志里就有[Automation] flow pull from ObjectQL registry failed: [Kernel] Service 'objectql' not found,并不抛)。没有一个在健康 boot 上抛错。git grep -ln "LiteKernel"命中的全部 booting 套件都跑过了(见下),零失败——即没有任何既有测试是"靠吞异常才跑得起来"的。改法
内核基类新增
triggerHookOrThrow(传播版分发器),LiteKernel只在kernel:ready这一处用它:context.trigger:triggerHook那句logger.debug('Triggering hook: ...')是 LiteKernel boot 日志的一部分,换成context.trigger会静默丢掉它;新增一个并列的具名分发器还能把"哪些钩子该 fail-loud、哪些该 fail-soft"这件事写在类型/命名上,而不是靠调用点的注释。triggerHook加参数:两种语义是两种契约,不是同一函数的开关;两个分发器各自带上"何时用我"的 docblock,下一个人加钩子时要做的是选一个,而不是记住一个布尔。state = 'stopped',对齐ObjectKernel.bootstrap()的 catch —— 失败的 boot 不该继续读作running。原始错误原样抛出,不包装。kernel:bootstrapped/kernel:listening/kernel:shutdown)在 LiteKernel 上语义不变,按派发词"本单只裁kernel:ready"。测试
两边对称的回归用例(补在既有 lifecycle ordering 用例旁),外加一条"其余钩子仍 fail-soft"的 pin,让将来任何扩大化都必须是显式改动:
kernel.test.ts— 抛错的 ready handler ⇒bootstrap()reject 原始错误、后续 ready handler 不跑、bootstrapped/listening不触发、getState() === 'stopped'。lite-kernel.test.ts— 同上一条(这条在改动前是失败的:AssertionError: promise resolved "undefined" instead of rejecting,即原先 boot 照常成功);外加keeps fail-soft dispatch for hooks other than kernel:ready。顺带订正的措辞(仅此两处)
packages/plugins/plugin-email/src/email-plugin.ts—— plugin-email: 邮件投递接入持久化队列 —— send 走 email.send.async / sys_job_queue(重试+DLQ),可配置开关 #5160/PR feat(plugin-email): 邮件投递接入持久化队列 —— send 走 email.send.async / sys_job_queue,三门可配置 (#5160) #5173 里写的"akernel:readyhandler that throws is silently swallowed on LiteKernel"现在不成立。改为说明该 sweep 自 catch 的真实理由(它是脱离 handler 的 detached 任务,逃逸的 rejection 会变成 unhandled rejection;且滞留行报告不该否决一个本来健康的 boot)。行为零变化。content/docs/kernel/events.mdx—— 补一段把统一后的语义写成真话,并诚实写明其余钩子仍有内核差异。⛔content/docs/releases/**未碰。验证(全部在容器共享锁下跑)
changeset
.changeset/kernel-ready-unified-failure.md,按行为变更来写:点名受影响的人(vitest / serverless / edge 上 ready handler 会抛的宿主),写明"以前也不是完全静默 —— 是一条Hook handler failed: kernel:ready的 ERROR 日志",并给出自查方法(在既有日志里搜这句)与出路(工作若真的可选,就在 handler 内部自己 catch)。范围外发现
kernel:listening/kernel:bootstrapped/kernel:shutdown在 LiteKernel 上仍被吞;其中HonoServerPlugin的await this.server.listen(port)没有自己的 try/catch,失败时 LiteKernel 会打印「✅ Bootstrap complete」而实际没有监听端口。同源不同钩子,本单派发词明确只裁kernel:ready,故另开、未在本 PR 修。🤖 Generated with Claude Code
https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7
Generated by Claude Code