现象
本地 magic-flow rig(cloud main 3f2884c4 × framework main cb43296ef,2026-08-13)实测:一个 timeRelative flow 首次绑定成功后,每一次 kernel 重建都无法重绑,且永不自愈(连续 4 次重建复现):
INFO [time-relative] bound flow 'xqao_contract_expiry_reminder_flow' → sweep 'xqao_contract.expiry_date' offsets [3]d on cron '0 8 * * *'
(kernel 被 freshness probe 驱逐、重建后)
WARN [time-relative] failed to schedule flow 'xqao_contract_expiry_reminder_flow': Cron: Tried to initialize new named job 'flow-time-relative:xqao_contract_expiry_reminder_flow', but name already taken.
之后 sweep 绑在空处,自动化静默死亡——verify_build、UI、DB 全部显示健康,唯一信号是这条 WARN。同一批重建里 record-change trigger 每次都正常重绑,对照明显。
两个对照实验钉住根因是残留的进程级 job 名:
- 把 start 节点 schedule 换成
{type:'interval'} → 重绑成功(interval 走 setInterval,不经过 croner 命名注册表);换回 cron 型立刻 name already taken。 - 在
sys_metadata 里改 flow 名(= 换 job 名)→ 下一次重建绑定成功。
根因链(四层)
- 绑定层
packages/triggers/trigger-schedule/src/time-relative-trigger.ts:214:job 名 flow-time-relative:<flowName>,不带 env/kernel 标识;绑定前的 this.stop(flowName) 读实例级 this.bound Map——新 kernel 的新实例 Map 为空,清理 no-op。schedule-trigger.ts(flow-schedule:<flowName>)结构完全相同,同样中招。 - 适配层
cron-job-adapter.ts:schedule() 先 await this.cancel(name),但也只查自己实例的 this.jobs Map。 - croner 层:
new Cron(expression, { name }) 登记进 croner 进程级全局命名注册表——旧 kernel 的 Cron 对象还在,新建同名直接 throw。 - 销毁层(单点根因)
db-job-adapter.ts:kernel 驱逐链路本身是完整的(KernelManager.evict() → kernel.shutdown() → plugin.destroy() → JobServicePlugin.destroy() → dbAdapter.destroy()),但 DbJobAdapter.destroy() 只有 await this.inner.destroy()——漏掉 this.cron(CronJobAdapter)。旧 kernel 的 croner 任务永不 stop,永久占名。
另外 trigger 的 schedule() 是 fire-and-forget(void Promise.resolve(...)),失败被降为一条 WARN。
为什么严重
- 多租户云运行时里 kernel 驱逐是常规操作:freshness probe ~10s 一次,AI 每次 auto-publish 都会 bump freshness → 驱逐。所以「AI 建好定时自动化 → 用户改一次元数据 → 自动化死了」是常态路径。
- job 名不带 env id:同一容器里两个环境同名 flow 不用等驱逐就冲突(AI 生成的 flow 名极易撞,如
contract_expiry_reminder_flow)。 - 失败静默:唯一信号是没人看的 WARN,属于 valid-but-inert(ADR-0078)一类。
- 旧 kernel 的 cron 任务不只占名字,它还活着——闭包握着已 shutdown 的旧 kernel 引擎(目前正因新绑定失败才没有双写;单修命名不修销毁会把静默死亡变成 zombie 双写)。
期望修复
DbJobAdapter.destroy() 补上 this.cron 的销毁(CronJobAdapter.destroy() 已存在,只是没人调)。- job 名加 environment/kernel 命名空间,杜绝跨 kernel/跨环境冲突。
- 重绑改为 replace 语义(bind 撞名时替换旧 job),而非 warn-and-give-up。
- 绑定失败升级为可观测状态(不只 WARN)。
- 回归测试:bind → 新插件实例模拟 kernel 重建 → 再 bind → job 恰好调度一次且能触发;覆盖
time-relative-trigger 与 schedule-trigger 两处。
注意:即使全部修完,「kernel 不驻留时定时任务不跑」仍是架构层问题(时钟应上移到常驻层),cloud 侧另行跟踪。
现象
本地 magic-flow rig(cloud main
3f2884c4× framework maincb43296ef,2026-08-13)实测:一个timeRelativeflow 首次绑定成功后,每一次 kernel 重建都无法重绑,且永不自愈(连续 4 次重建复现):之后 sweep 绑在空处,自动化静默死亡——
verify_build、UI、DB 全部显示健康,唯一信号是这条 WARN。同一批重建里record-changetrigger 每次都正常重绑,对照明显。两个对照实验钉住根因是残留的进程级 job 名:
{type:'interval'}→ 重绑成功(interval 走setInterval,不经过 croner 命名注册表);换回 cron 型立刻name already taken。sys_metadata里改 flow 名(= 换 job 名)→ 下一次重建绑定成功。根因链(四层)
packages/triggers/trigger-schedule/src/time-relative-trigger.ts:214:job 名flow-time-relative:<flowName>,不带 env/kernel 标识;绑定前的this.stop(flowName)读实例级this.boundMap——新 kernel 的新实例 Map 为空,清理 no-op。schedule-trigger.ts(flow-schedule:<flowName>)结构完全相同,同样中招。cron-job-adapter.ts:schedule()先await this.cancel(name),但也只查自己实例的this.jobsMap。new Cron(expression, { name })登记进 croner 进程级全局命名注册表——旧 kernel 的 Cron 对象还在,新建同名直接 throw。db-job-adapter.ts:kernel 驱逐链路本身是完整的(KernelManager.evict()→kernel.shutdown()→plugin.destroy()→JobServicePlugin.destroy()→dbAdapter.destroy()),但DbJobAdapter.destroy()只有await this.inner.destroy()——漏掉this.cron(CronJobAdapter)。旧 kernel 的 croner 任务永不 stop,永久占名。另外 trigger 的
schedule()是 fire-and-forget(void Promise.resolve(...)),失败被降为一条 WARN。为什么严重
contract_expiry_reminder_flow)。期望修复
DbJobAdapter.destroy()补上this.cron的销毁(CronJobAdapter.destroy()已存在,只是没人调)。time-relative-trigger与schedule-trigger两处。注意:即使全部修完,「kernel 不驻留时定时任务不跑」仍是架构层问题(时钟应上移到常驻层),cloud 侧另行跟踪。