From 00a425eeef428a5724f2550ff49a880b24e88edf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 06:20:01 +0000 Subject: [PATCH 1/3] wip: correct cron attribution at four sites --- packages/spec/src/ai/knowledge-source.test.ts | 19 ++++++++++++------- packages/spec/src/ai/knowledge-source.zod.ts | 15 +++++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/spec/src/ai/knowledge-source.test.ts b/packages/spec/src/ai/knowledge-source.test.ts index df0f805c65..ca1523985e 100644 --- a/packages/spec/src/ai/knowledge-source.test.ts +++ b/packages/spec/src/ai/knowledge-source.test.ts @@ -17,10 +17,13 @@ // slot's own path whose message names the fix — the shared dialect fixed its // envelope arm and learned to trim at #15028 / #15035; the sentences are // `TYPED_EXPRESSION_SOURCE_REQUIRED.cron` / `TYPED_EXPRESSION_DIALECT_ONLY.cron`; -// - cron SYNTAX is not judged at parse time. `'not a cron'` normalizes like -// any other string: the syntax verdict belongs to the `cron` dialect engine -// (`@objectstack/formula` cron-engine — 5- or 6-field, or an `@` alias) when -// the expression is evaluated. That pin is deliberate: it is what keeps the +// - cron SYNTAX is not judged at parse time, and no engine judges it later +// either. `'not a cron'` normalizes like any other string, and this slot is +// parsed and reaches no engine: `croner` judges a cron pattern only where a +// schedule is wired (`CronSchedule.expression`, a different slot), and +// `@objectstack/formula`'s registered `cron` engine has no caller outside +// that package. The syntax verdict belongs to whatever external scheduler +// the author hands the value to. That pin is deliberate: it is what keeps the // schema's describe honest. If the shared dialect ever gains parse-time // syntax validation, this pin flips, and the describe on the slot must be // rewritten in the same commit. @@ -103,9 +106,11 @@ describe('KnowledgeRefreshPolicySchema.cron — the typed cron slot (#14825)', ( }); it('does NOT judge cron syntax at parse time — measured, and the describe promises no more (declared = enforced)', () => { - // The syntax verdict is the `cron` dialect engine's at evaluate time: - // `@objectstack/formula` cron-engine accepts 5- or 6-field expressions and - // the `@yearly`…`@reboot` aliases. The parse only normalizes. If this case + // Nothing evaluates this slot, so no engine ever issues a syntax verdict on + // it: `croner` judges `CronSchedule.expression`, a different slot, and + // `@objectstack/formula`'s registered `cron` engine has no caller outside + // that package. The verdict belongs to whatever external scheduler the + // author hands the value to. The parse only normalizes. If this case // ever goes red because the shared dialect learned to refuse syntax, update // the slot's describe in the same commit — do not weaken this pin. for (const source of ['not a cron', '0 0 3 * * *', '@daily']) { diff --git a/packages/spec/src/ai/knowledge-source.zod.ts b/packages/spec/src/ai/knowledge-source.zod.ts index 97caa4664d..4d8eb8d1dd 100644 --- a/packages/spec/src/ai/knowledge-source.zod.ts +++ b/packages/spec/src/ai/knowledge-source.zod.ts @@ -33,10 +33,13 @@ export const KnowledgeRefreshPolicySchema = lazySchema(() => z.object({ * `integration/connector.zod.ts`). A bare string is shorthand for * `{ dialect: 'cron', source }`; the parse enforces a non-empty string or an * expression envelope and normalizes to the envelope. It does NOT judge cron - * syntax: that verdict is the `cron` dialect engine's (`@objectstack/formula` - * cron-engine — 5- or 6-field, or an `@yearly`…`@reboot` alias) when the - * expression is evaluated, so the describe below promises exactly what the - * parse enforces (ADR-0049 declared = enforced; #14825). + * syntax, and neither does anything downstream: this slot is parsed and + * reaches no engine. `croner` judges a cron pattern only where a schedule is + * wired (`CronSchedule.expression`, a different slot), and + * `@objectstack/formula`'s registered `cron` engine has no caller outside + * that package — so the syntax verdict belongs to whatever external + * scheduler the author hands this value to, and the describe below promises + * exactly what the parse enforces (ADR-0049 declared = enforced; #14825). * * `service-knowledge` does not schedule the cron itself — it merely * surfaces the value so an automation flow / external scheduler can @@ -45,8 +48,8 @@ export const KnowledgeRefreshPolicySchema = lazySchema(() => z.object({ cron: CronExpressionInputSchema.optional().describe( 'Cron-dialect expression for a periodic full reindex. A bare string is shorthand for ' + '`{ dialect: \'cron\', source }`; the parse enforces a non-empty string or an expression ' - + 'envelope and normalizes to the envelope — cron syntax (5- or 6-field, or an `@` alias) is ' - + 'the `cron` dialect engine\'s verdict when the expression is evaluated, not checked here. ' + + 'envelope and normalizes to the envelope — cron syntax is not checked here, and no engine ' + + 'evaluates this slot: the verdict belongs to whatever external scheduler you hand the value to. ' + '`service-knowledge` does not schedule it: the value is surfaced so an automation flow / ' + 'external scheduler can trigger `reindexSource`.', ), From 4157daecb13674a96cab9d64f2893ba222b96131 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 06:51:30 +0000 Subject: [PATCH 2/3] wip: regenerate knowledge-source reference page --- content/docs/references/ai/knowledge-source.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/references/ai/knowledge-source.mdx b/content/docs/references/ai/knowledge-source.mdx index 0d839a946b..15d366659b 100644 --- a/content/docs/references/ai/knowledge-source.mdx +++ b/content/docs/references/ai/knowledge-source.mdx @@ -65,7 +65,7 @@ const result = FileKnowledgeSourceSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **onRecordChange** | `boolean` | optional (default: `true`) | | -| **cron** | `string \| { dialect: 'cron'; source?: string; ast?: any; meta?: object }` | optional | Cron-dialect expression for a periodic full reindex. A bare string is shorthand for `{ dialect: 'cron', source }`; the parse enforces a non-empty string or an expression envelope and normalizes to the envelope — cron syntax (5- or 6-field, or an `@` alias) is the `cron` dialect engine's verdict when the expression is evaluated, not checked here. `service-knowledge` does not schedule it: the value is surfaced so an automation flow / external scheduler can trigger `reindexSource`. | +| **cron** | `string \| { dialect: 'cron'; source?: string; ast?: any; meta?: object }` | optional | Cron-dialect expression for a periodic full reindex. A bare string is shorthand for `{ dialect: 'cron', source }`; the parse enforces a non-empty string or an expression envelope and normalizes to the envelope — cron syntax is not checked here, and no engine evaluates this slot: the verdict belongs to whatever external scheduler you hand the value to. `service-knowledge` does not schedule it: the value is surfaced so an automation flow / external scheduler can trigger `reindexSource`. | --- @@ -130,7 +130,7 @@ const result = FileKnowledgeSourceSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **onRecordChange** | `boolean` | optional (default: `true`) | | -| **cron** | `string \| { dialect: 'cron'; source?: string; ast?: any; meta?: object }` | optional | Cron-dialect expression for a periodic full reindex. A bare string is shorthand for `{ dialect: 'cron', source }`; the parse enforces a non-empty string or an expression envelope and normalizes to the envelope — cron syntax (5- or 6-field, or an `@` alias) is the `cron` dialect engine's verdict when the expression is evaluated, not checked here. `service-knowledge` does not schedule it: the value is surfaced so an automation flow / external scheduler can trigger `reindexSource`. | +| **cron** | `string \| { dialect: 'cron'; source?: string; ast?: any; meta?: object }` | optional | Cron-dialect expression for a periodic full reindex. A bare string is shorthand for `{ dialect: 'cron', source }`; the parse enforces a non-empty string or an expression envelope and normalizes to the envelope — cron syntax is not checked here, and no engine evaluates this slot: the verdict belongs to whatever external scheduler you hand the value to. `service-knowledge` does not schedule it: the value is surfaced so an automation flow / external scheduler can trigger `reindexSource`. | --- From f7a9b0f901101efa494602c48fc702d2bea1b7f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 07:11:13 +0000 Subject: [PATCH 3/3] fix(spec): `KnowledgeRefreshPolicy.cron` no longer attributes the syntax verdict to a cron engine that never sees it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docblock, the shipped `.describe()` and the slot's two pin-test comments all said cron syntax was "the `cron` dialect engine's verdict when the expression is evaluated". Both halves were false: nothing evaluates `refresh.cron` (`service-knowledge` reads `refresh.onRecordChange` and never `refresh.cron`), and `@objectstack/formula`'s registered `cron` engine has no caller outside that package. The claim shipped to authors through the generated reference page. All four sites now match the already-corrected Expression Protocol dialect table: no engine evaluates this slot — `croner` judges a cron pattern only where a schedule is wired (`CronSchedule.expression`, a different slot) — so the verdict belongs to whatever external scheduler the author hands the value to. The pin test's behaviour is untouched; only its comments were wrong, and the self-maintaining rule that block ends with is preserved verbatim. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno --- .changeset/knowledge-refresh-cron-attribution.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/knowledge-refresh-cron-attribution.md diff --git a/.changeset/knowledge-refresh-cron-attribution.md b/.changeset/knowledge-refresh-cron-attribution.md new file mode 100644 index 0000000000..be55586da5 --- /dev/null +++ b/.changeset/knowledge-refresh-cron-attribution.md @@ -0,0 +1,7 @@ +--- +"@objectstack/spec": patch +--- + +`KnowledgeRefreshPolicy.cron` no longer tells authors that the `cron` dialect engine judges their syntax "when the expression is evaluated". Both halves of that sentence were false: nothing evaluates `refresh.cron` — `service-knowledge` reads `refresh.onRecordChange` and never `refresh.cron` — and `@objectstack/formula`'s registered `cron` engine has no caller outside that package, so it was never going to issue that verdict either. The claim shipped to authors through the generated reference page (`content/docs/references/ai/knowledge-source.mdx`), naming both an engine that never sees the value and an event that never happens. + +The docblock, the `.describe()` and the slot's two pin-test comments now say what is true today, matching the wording of the already-corrected Expression Protocol dialect table: cron syntax is not checked at parse time and no engine evaluates this slot — `croner` judges a cron pattern only where a schedule is wired (`CronSchedule.expression`, a different slot) — so the verdict belongs to whatever external scheduler the author hands the value to. Documentation only: no exported symbol, no authorable key and no accept-set movement; the parse behaviour is byte-for-byte unchanged, and the pin that proves `'not a cron'` still normalizes is untouched.