Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/knowledge-refresh-cron-attribution.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions content/docs/references/ai/knowledge-source.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`. |


---
Expand Down Expand Up @@ -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`. |


---
Expand Down
19 changes: 12 additions & 7 deletions packages/spec/src/ai/knowledge-source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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']) {
Expand Down
15 changes: 9 additions & 6 deletions packages/spec/src/ai/knowledge-source.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`.',
),
Expand Down
Loading