Skip to content

defineJob 解析后的 cron schedule 是表达式信封,CronJobAdapter 直接喂给 croner —— 声明式 cron job 静默调度失败 #4567

Description

@os-zhuang

#4538 的三仓 import 级扫描中发现的运行时缺陷(与该 issue 的类型收敛无关,按 Prime Directive #10 单独立案)。

现象

defineJob(JobSchema.parse)产出的 cron schedule 里,expression 已被 CronExpressionInputSchema 的 transform 变换成 ADR 表达式信封:

// packages/spec (built dist) 实测:
defineJob({ name: 'x_job', schedule: { type: 'cron', expression: '0 1 * * *' }, handler: 'h' }).schedule
// => {"type":"cron","expression":{"dialect":"cron","source":"0 1 * * *"},"timezone":"UTC"}

AppPlugin(packages/runtime/src/app-plugin.ts,kernel:ready 钩子里的 declarative-jobs 注册段)把 job.schedule 原样传给 IJobService.schedule,CronJobAdapter(packages/services/service-job/src/cron-job-adapter.ts)又把 schedule.expression 原样传给 croner:

new Cron({dialect:'cron',source:'0 1 * * *'}, {timezone:'UTC'}, ...)
// => throws: "CronPattern: Pattern has to be of type string."

该异常被 AppPlugin 的 per-job try/catch 吞掉,只留一条 warn('[AppPlugin] Failed to schedule job', …) —— 通过 defineJob 授权的 cron job 永远不会真正被调度,而作者看到的是一次成功的 build 和一次成功的 boot。examples/app-showcaseHealthSweepJob(src/automation/jobs/index.ts,schedule: { type: 'cron', expression: '0 1 * * *' })正中此路径。

interval/once 分支不受影响(无 transform);trigger-schedule 一侧自己归一化出裸字符串 expression,也不受影响。

成因判定

declared ≠ enforced 的典型分裂:JobSchema.schedule 的解析产物(信封)与 IJobService.schedule 的消费约定(裸字符串,见 @objectstack/spec/contractsJobSchedule#4538 后是该名字唯一声明)之间没有任何一层做信封 → 字符串的降解,adapter 也从未声明它能吃信封。

修复方向(按 contract-first,不建议在 adapter 里加 typeof === 'object' 容错)

候选:

  1. AppPlugin 注册声明式 job 时(与 retryPolicy/timeout 同一处)把 Schedule(authoring tier)降解为 contracts JobSchedule(boundary tier):信封 expression.source → 裸字符串。降解点单一、两个 tier 的边界正好是 [#4535·A3] contracts 手写 interface 与域内 zod 推导类型收敛(3 簇 9 条) #4538 划清的那条线。
  2. 或者让 IJobService.schedule 正式接受 authoring Schedule 并由各 adapter 统一经 @objectstack/formula cron engine 解包/校验 —— 改动面大,但把信封作为一等公民贯穿到执行层。

无论取哪条,失败路径都应从 warn 升级为更响亮的信号(至少 error + 计数),"调度失败"不该和"缺 handler"共用一条静默 warn。

复现:任一 defineJob cron job → boot → 观察 warn 日志与 listJobs()

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions