You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[finding] The Expression Protocol dialect table tells authors the cron engine is cron-parser — that package is not a dependency of this repo at all; the library is croner, and the table ships to customers via generated docs #15527
Recorded by the os-dev seat on #15027 (session session_01D47qPfEWVPmhguWgBZCi5N, branch claude/issue-15027-expression-ledger-cron-template), measured while hunting the reader of each CronExpressionInputSchema slot to classify it in the ADR-0058 D7 ledger. Unassigned, bare, for triage. Out of #15027's scope: that card changes packages/qa/dogfood/test/** only.
What
packages/spec/src/shared/expression.zod.ts:22, in the # Expression Protocol docblock's dialect table:
| `cron` | `cron-parser` | job schedules |
Measured at origin/main5023630b178:
grep -rn '"cron-parser"' --include=package.json over the repo (excluding node_modules): zero hits. It is not a dependency of any package, direct or otherwise.
The cron library this repo actually uses is croner (^10.0.1), declared by three packages: packages/services/service-job, packages/triggers/trigger-schedule, packages/plugins/plugin-reports.
The one cron slot with a live path reaches it as croner: system/job.zod.tsCronSchedule.expression → runtime/job-schedule.tstoBoundaryJobSchedule → service-job/cron-job-adapter.tsCronJobAdapter → Cron from croner.
Why it matters
⭐ This is not an internal comment. The docblock is the SOURCE of a customer-facing page: the identical row appears in content/docs/references/shared/expression.mdx:24, generated from it. So an author asking "what evaluates my cron expression" is told the name of a package that is nowhere in the product, and the name they would need in order to check syntax rules, alias support, or second-field semantics against real behaviour is not given anywhere.
The two engines differ in ways an author would actually trip on — field counts and alias vocabularies are not identical across cron implementations — so this is not a cosmetic naming slip. The table's other two rows are accurate (cel names @objectstack/formula, template describes the interpolation), which makes the wrong one read as equally load-bearing.
A second inaccuracy in the same row, measured on the same walk
The column is headed engine, and for cron there are two candidates in the tree — neither of which is cron-parser:
@objectstack/formulacronEngine (packages/formula/src/cron-engine.ts) — validates 5/6-field patterns and the @yearly…@reboot aliases. Measured: zero consumers outside packages/formula. Nothing routes any spec cron slot through it.
croner — the library that actually fires schedules, reached only by the job path above.
⇒ For 6 of the 7 distinct cron ratchet keys in the spec there is no engine on either side, and for the 7th it is croner. ⚠️ This bears directly on #15035, which is open and says the syntax verdict "is the formula cron-engine's at evaluate time": that evaluation does not happen for these slots, because nothing calls that engine. Worth reading the two together — this card does not change #15035's scope, and #15035 remains open.
Likely resolution, not a decision
One line in the docblock, then regenerate the reference page (the .mdx is generated, so ⛔ do not hand-edit it). The honest row probably names croner for firing and states that no syntax verdict is reached at parse time, but the wording interacts with #15035 and #15028, so it wants writing once with those in view rather than a find-and-replace.
Recorded by the os-dev seat on #15027 (session
session_01D47qPfEWVPmhguWgBZCi5N, branchclaude/issue-15027-expression-ledger-cron-template), measured while hunting the reader of eachCronExpressionInputSchemaslot to classify it in the ADR-0058 D7 ledger. Unassigned, bare, for triage. Out of #15027's scope: that card changespackages/qa/dogfood/test/**only.What
packages/spec/src/shared/expression.zod.ts:22, in the# Expression Protocoldocblock's dialect table:Measured at
origin/main5023630b178:grep -rn '"cron-parser"' --include=package.jsonover the repo (excludingnode_modules): zero hits. It is not a dependency of any package, direct or otherwise.croner(^10.0.1), declared by three packages:packages/services/service-job,packages/triggers/trigger-schedule,packages/plugins/plugin-reports.system/job.zod.tsCronSchedule.expression→runtime/job-schedule.tstoBoundaryJobSchedule→service-job/cron-job-adapter.tsCronJobAdapter→Cronfromcroner.Why it matters
⭐ This is not an internal comment. The docblock is the SOURCE of a customer-facing page: the identical row appears in
content/docs/references/shared/expression.mdx:24, generated from it. So an author asking "what evaluates my cron expression" is told the name of a package that is nowhere in the product, and the name they would need in order to check syntax rules, alias support, or second-field semantics against real behaviour is not given anywhere.The two engines differ in ways an author would actually trip on — field counts and alias vocabularies are not identical across cron implementations — so this is not a cosmetic naming slip. The table's other two rows are accurate (
celnames@objectstack/formula,templatedescribes the interpolation), which makes the wrong one read as equally load-bearing.A second inaccuracy in the same row, measured on the same walk
The column is headed engine, and for
cronthere are two candidates in the tree — neither of which iscron-parser:@objectstack/formulacronEngine(packages/formula/src/cron-engine.ts) — validates 5/6-field patterns and the@yearly…@rebootaliases. Measured: zero consumers outsidepackages/formula. Nothing routes any spec cron slot through it.croner— the library that actually fires schedules, reached only by the job path above.⇒ For 6 of the 7 distinct cron ratchet keys in the spec there is no engine on either side, and for the 7th it is croner.⚠️ This bears directly on #15035, which is open and says the syntax verdict "is the formula cron-engine's at evaluate time": that evaluation does not happen for these slots, because nothing calls that engine. Worth reading the two together — this card does not change #15035's scope, and #15035 remains open.
Likely resolution, not a decision
One line in the docblock, then regenerate the reference page (the .mdx is generated, so ⛔ do not hand-edit it). The honest row probably names
cronerfor firing and states that no syntax verdict is reached at parse time, but the wording interacts with #15035 and #15028, so it wants writing once with those in view rather than a find-and-replace.Verified
search_issues, control satisfied in-session: the same query returned ADR-0058 D7 expression conformance ledger discovers onlyExpressionInputSchema/SettingsVisibilityInputSchemapositions — the 8CronExpressionInputSchemaand 3TemplateExpressionInputSchemasites sit outside the ratchet, unclassified #15027 and [finding]CronExpressionInputSchema/TemplateExpressionInputSchemafix the dialect only on the bare-string arm — the envelope arm accepts any declared dialect, so a cron-typed slot parses{ dialect: 'cel', source }green #15028, both known-certain hits, so the absence of a match for this one is a reading):total_count4, nearest are [finding]CronExpressionInputSchema/TemplateExpressionInputSchemafix the dialect only on the bare-string arm — the envelope arm accepts any declared dialect, so a cron-typed slot parses{ dialect: 'cel', source }green #15028 (envelope arm accepts any dialect), [finding] The shared cron dialect judges no cron syntax at parse time — after #14825 all 9CronExpressionInputSchemasites accept'not a cron'green; the verdict is the formula cron-engine's at evaluate time #15035 (no cron syntax judged at parse time) and closed defineJob 解析后的 cron schedule 是表达式信封,CronJobAdapter 直接喂给 croner —— 声明式 cron job 静默调度失败 #4567 (the adapter fed croner an envelope). None names the wrong engine in the protocol table.--include=package.jsonscan that returns zero forcron-parserreturns the threecronerdeclarations above.Refs: ADR-0058 · ADR-0032 · #15035 · #15028.