From 12f6c37b415128f27be53813d1db5957eed559c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 17:18:16 +0000 Subject: [PATCH 1/2] feat(spec): type `KnowledgeRefreshPolicy.cron` with `CronExpressionInputSchema` (#14825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `refresh.cron` on a knowledge source was a bare `z.string()` under a doc comment promising a 5-field cron — a constraint nothing checked. It now carries the shared cron-dialect input the three sibling cron fields use, with a describe that promises exactly what the parse enforces (a non-empty string or an expression envelope, normalized to the envelope; syntax is the cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` / `KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two isomorphism pins they replace leave the registry. Pins measured, not assumed: `'not a cron'` normalizes rather than being refused. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i --- .../knowledge-source-cron-expression.md | 43 +++++++ packages/spec/src/ai/knowledge-source.test.ts | 121 ++++++++++++++++++ packages/spec/src/ai/knowledge-source.zod.ts | 27 +++- .../src/type-alias-convention.pin.test.ts | 18 ++- 4 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 .changeset/knowledge-source-cron-expression.md create mode 100644 packages/spec/src/ai/knowledge-source.test.ts diff --git a/.changeset/knowledge-source-cron-expression.md b/.changeset/knowledge-source-cron-expression.md new file mode 100644 index 0000000000..a12f61d885 --- /dev/null +++ b/.changeset/knowledge-source-cron-expression.md @@ -0,0 +1,43 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec): type `KnowledgeRefreshPolicy.cron` with the shared cron dialect — `CronExpressionInputSchema`, a describe that promises what the parse enforces (#14825) + +**BREAKING** accept-set narrowing and parsed-shape change on a published +authorable key, shipped as `minor` under the repo's launch-window convention +for breaking changes. + +`KnowledgeRefreshPolicySchema.cron` (`refresh.cron` on a knowledge source) was +a bare `z.string()` under a doc comment promising a 5-field cron expression — +a constraint nothing checked (ADR-0049 declared ≠ enforced). It now carries +`CronExpressionInputSchema`, the cron-dialect input the spec's other +cron-shaped fields already use (`ScheduledExport.schedule.cronExpression`, +`ScheduleState.cronExpression`, `Connector.schedule`). + +What changes for authored metadata, measured rather than assumed: + +- A bare non-empty string is still the shorthand — `refresh: { cron: '0 3 * * *' }` + keeps parsing; stored `sys_metadata` rows re-parse unchanged. +- The expression envelope `{ dialect: 'cron', source }` is now accepted too. +- An **empty string** is now refused (`invalid_union` at `refresh.cron`); it + named no schedule before, so the only remedy is to delete the key. +- The **parsed** value is now the `{ dialect: 'cron', source }` envelope + rather than the bare string — the same shape the three sibling cron fields + produce. Zero readers of the parsed value were measured in `objectstack` and + the pinned `objectui` (`service-knowledge` reads only `refresh.onRecordChange`; + it never schedules the cron). +- `KnowledgeRefreshPolicyParsed` and `KnowledgeSourceParsed` are new exported + aliases naming the parsed state (ADR-0122); the bare `KnowledgeRefreshPolicy` + / `KnowledgeSource` aliases stay the author state and still accept a string. + +What the schema now promises is exactly what the parse enforces: a non-empty +string or an expression envelope, normalized to the envelope. Cron **syntax** +is not judged at parse time by the shared dialect — `'not a cron'` normalizes +like any other string, and the syntax verdict (5- or 6-field, or an `@yearly`… +`@reboot` alias) is the `cron` dialect engine's when the expression is +evaluated. The describe says so instead of restating "5-field", and the pin +file records the measured behaviour so a later change to the shared dialect +surfaces here. + + diff --git a/packages/spec/src/ai/knowledge-source.test.ts b/packages/spec/src/ai/knowledge-source.test.ts new file mode 100644 index 0000000000..539b33b967 --- /dev/null +++ b/packages/spec/src/ai/knowledge-source.test.ts @@ -0,0 +1,121 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// `KnowledgeRefreshPolicySchema.cron` — pins for the typed cron slot (#14825). +// +// The slot was a bare `z.string()` under a doc comment promising a 5-field +// cron, so the promise was enforced nowhere (ADR-0049 declared ≠ enforced). It +// now carries `CronExpressionInputSchema`, the shared cron-dialect input the +// other cron-shaped fields already use (`api/export.zod.ts`, +// `automation/execution.zod.ts`, `integration/connector.zod.ts`). What that +// schema ENFORCES was measured before these pins were written, and the pins +// state exactly that — no more: +// +// - a bare non-empty string normalizes to `{ dialect: 'cron', source }`; +// - an expression envelope passes through; +// - an empty string, a non-string, or an envelope naming an unknown dialect +// is refused with `invalid_union` at the slot's own path; +// - 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 +// 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. + +import { describe, expect, it } from 'vitest'; +import { + KnowledgeRefreshPolicySchema, + KnowledgeSourceSchema, + type KnowledgeRefreshPolicy, + type KnowledgeRefreshPolicyParsed, + type KnowledgeSource, + type KnowledgeSourceParsed, +} from './knowledge-source.zod'; + +const SOURCE: KnowledgeSource = { + id: 'kb_articles', + label: 'KB articles', + adapter: 'memory', + source: { kind: 'object', object: 'kb_article', contentFields: ['title', 'body'] }, +}; + +const CRON_5_FIELD = '0 3 * * *'; + +describe('KnowledgeRefreshPolicySchema.cron — the typed cron slot (#14825)', () => { + it('positive control: a 5-field cron on a full knowledge source parses and normalizes to the cron envelope', () => { + const r = KnowledgeSourceSchema.safeParse({ ...SOURCE, refresh: { cron: CRON_5_FIELD } }); + expect(r.success, r.success ? '' : JSON.stringify(r.error.issues)).toBe(true); + if (!r.success) return; + expect(r.data.refresh?.cron).toEqual({ dialect: 'cron', source: CRON_5_FIELD }); + }); + + it('accepts the expression envelope form and passes it through', () => { + const envelope = { dialect: 'cron' as const, source: '@daily' }; + const r = KnowledgeRefreshPolicySchema.safeParse({ cron: envelope }); + expect(r.success).toBe(true); + if (!r.success) return; + expect(r.data.cron).toEqual(envelope); + }); + + it('absent stays absent — no `cron` key is fabricated by the parse', () => { + const withEmptyRefresh = KnowledgeSourceSchema.safeParse({ ...SOURCE, refresh: {} }); + expect(withEmptyRefresh.success).toBe(true); + if (withEmptyRefresh.success) expect(withEmptyRefresh.data.refresh?.cron).toBeUndefined(); + + const withoutRefresh = KnowledgeSourceSchema.safeParse(SOURCE); + expect(withoutRefresh.success).toBe(true); + if (withoutRefresh.success) expect(withoutRefresh.data.refresh?.cron).toBeUndefined(); + }); + + it('refuses an empty string with `invalid_union` at `refresh.cron`', () => { + const r = KnowledgeSourceSchema.safeParse({ ...SOURCE, refresh: { cron: '' } }); + expect(r.success).toBe(false); + if (r.success) return; + const issue = r.error.issues.find((i) => i.path.join('.') === 'refresh.cron'); + expect(issue, JSON.stringify(r.error.issues)).toBeDefined(); + expect(issue?.code).toBe('invalid_union'); + expect(issue?.message.split('.')[0]).toBe('Invalid input'); + }); + + it('refuses a non-string value with `invalid_union` at `cron`', () => { + const r = KnowledgeRefreshPolicySchema.safeParse({ cron: 42 }); + expect(r.success).toBe(false); + if (r.success) return; + expect(r.error.issues.map((i) => [i.code, i.path.join('.')])).toEqual([['invalid_union', 'cron']]); + }); + + it('refuses an envelope naming a dialect the protocol does not declare', () => { + // `js` was retired from `ExpressionDialect` (#3278, ADR-0058 addendum). + const r = KnowledgeRefreshPolicySchema.safeParse({ cron: { dialect: 'js', source: 'x' } }); + expect(r.success).toBe(false); + if (r.success) return; + expect(r.error.issues.map((i) => [i.code, i.path.join('.')])).toEqual([['invalid_union', 'cron']]); + }); + + 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 + // 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']) { + const r = KnowledgeRefreshPolicySchema.safeParse({ cron: source }); + expect(r.success, `${JSON.stringify(source)} should normalize, not be refused`).toBe(true); + if (r.success) expect(r.data.cron).toEqual({ dialect: 'cron', source }); + } + }); + + it('names both states (ADR-0122): the bare alias is the author state, `XParsed` the parsed state', () => { + // Author state: a bare string is what an author writes. + const authored: KnowledgeRefreshPolicy = { cron: CRON_5_FIELD }; + // Parsed state: the envelope is what a consumer holds after the parse. + const parsed: KnowledgeRefreshPolicyParsed = KnowledgeRefreshPolicySchema.parse(authored); + expect(parsed.cron).toEqual({ dialect: 'cron', source: CRON_5_FIELD }); + // @ts-expect-error — a bare string is the AUTHOR shape, not the parsed one. + const notParsed: KnowledgeRefreshPolicyParsed = { cron: CRON_5_FIELD }; + expect(notParsed).toBeDefined(); + + const parsedSource: KnowledgeSourceParsed = KnowledgeSourceSchema.parse({ ...SOURCE, refresh: { cron: CRON_5_FIELD } }); + expect(parsedSource.refresh?.cron).toEqual({ dialect: 'cron', source: CRON_5_FIELD }); + }); +}); diff --git a/packages/spec/src/ai/knowledge-source.zod.ts b/packages/spec/src/ai/knowledge-source.zod.ts index 1b24e1c3ec..43fe743b65 100644 --- a/packages/spec/src/ai/knowledge-source.zod.ts +++ b/packages/spec/src/ai/knowledge-source.zod.ts @@ -1,6 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { z } from 'zod'; +import { CronExpressionInputSchema } from '../shared/expression.zod'; import { lazySchema } from '../shared/lazy-schema'; import { EmbeddingModelSchema, VectorStoreSchema } from './embedding.zod'; @@ -25,12 +26,30 @@ export const KnowledgeRefreshPolicySchema = lazySchema(() => z.object({ */ onRecordChange: z.boolean().default(true).optional(), /** - * Cron expression (5-field) for periodic full reindex. Optional. + * Cron-dialect expression for a periodic full reindex. Optional. + * + * `CronExpressionInputSchema` — the shared cron-dialect input the other + * cron-shaped fields carry (`api/export.zod.ts`, `automation/execution.zod.ts`, + * `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). + * * `service-knowledge` does not schedule the cron itself — it merely * surfaces the value so an automation flow / external scheduler can * trigger `reindexSource`. */ - cron: z.string().optional(), + 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. ' + + '`service-knowledge` does not schedule it: the value is surfaced so an automation flow / ' + + 'external scheduler can trigger `reindexSource`.', + ), })); /** Source backed by an ObjectQL object — each record becomes a document. */ @@ -116,8 +135,12 @@ export const KnowledgeSourceSchema = lazySchema(() => z.object({ })); export type KnowledgeRefreshPolicy = z.input; +/** Post-parse shape of {@link KnowledgeRefreshPolicy} — defaults applied, transforms run (ADR-0122): `cron` is the `{ dialect: 'cron', source }` envelope. */ +export type KnowledgeRefreshPolicyParsed = z.infer; export type ObjectKnowledgeSource = z.input; export type FileKnowledgeSource = z.input; export type HttpKnowledgeSource = z.input; export type KnowledgeSourceKind = z.input; export type KnowledgeSource = z.input; +/** Post-parse shape of {@link KnowledgeSource} — defaults applied, transforms run (ADR-0122): `refresh.cron` is the cron envelope. */ +export type KnowledgeSourceParsed = z.infer; diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index dc84566ec5..a49264887e 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -269,7 +269,7 @@ import type * as M170 from './ui/component.zod.js'; import type * as M183 from './api/sortability.zod.js'; // --------------------------------------------------------------------------- -// 831 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. +// 829 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. // // That number is machine-checked, not hand-kept. The runtime companion at the // bottom of this file recomputes the pin count from the source and asserts that @@ -303,12 +303,10 @@ export type Iso13 = Assert, z.infe export type Iso14 = Assert, z.infer< typeof M3.KnowledgeHitSchema > >>; // ai/knowledge-source.zod.ts -export type Iso15 = Assert, z.infer< typeof M4.KnowledgeRefreshPolicySchema > >>; export type Iso16 = Assert, z.infer< typeof M4.ObjectKnowledgeSourceSchema > >>; export type Iso17 = Assert, z.infer< typeof M4.FileKnowledgeSourceSchema > >>; export type Iso18 = Assert, z.infer< typeof M4.HttpKnowledgeSourceSchema > >>; export type Iso19 = Assert, z.infer< typeof M4.KnowledgeSourceKindSchema > >>; -export type Iso20 = Assert, z.infer< typeof M4.KnowledgeSourceSchema > >>; // ai/mcp.zod.ts export type Iso21 = Assert, z.infer< typeof M5.MCPTransportSchema > >>; @@ -1682,7 +1680,7 @@ describe('ADR-0122 type-alias convention', () => { // this title and the section header above the pin list — are now asserted // against the recomputed count below, so neither can go stale without a red // test naming it. - it('still declares all 831 isomorphic pins', () => { + it('still declares all 829 isomorphic pins', () => { // The truth of each pin is proved by tsc, not here — an `Assert>` // that stops holding is a compile error with the alias named. What tsc // cannot notice is a pin that was DELETED: removing the assertion removes @@ -2095,9 +2093,19 @@ describe('ADR-0122 type-alias convention', () => { // `Iso188` left with it. `CrudOperation` (`Iso187`) stays — the enum is // still read by `GeneratedEndpointSchema.operation`. -1 removed; the Iso // number stays vacant. + // + // 831 -> 829 is #14825's typing of `KnowledgeRefreshPolicySchema.cron` + // (ai/knowledge-source.zod.ts) with `CronExpressionInputSchema`: its + // bare-string arm transforms to the `{ dialect: 'cron', source }` envelope, + // so input ≠ infer by construction — for the policy itself (`Iso15`) and + // for the `KnowledgeSourceSchema` that nests it under `refresh` (`Iso20`). + // Both aliases gained their `XParsed` (`KnowledgeRefreshPolicyParsed`, + // `KnowledgeSourceParsed`) in the same commit — the ADR-0122 D6 order: + // declare the parsed name, THEN delete the pin. -2 removed; the Iso + // numbers stay vacant. const self = readFileSync(fileURLToPath(import.meta.url), 'utf8'); const pins = self.match(/^export type Iso\d+ = Assert Date: Thu, 3 Sep 2026 17:29:34 +0000 Subject: [PATCH 2/2] chore(spec): regenerate artifacts for the typed `refresh.cron`; declare the projection-only default move (#14825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:generated --fix` regenerated the three artifacts it proved stale (references doc, api-surface, export-origins — the two new `XParsed` exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime default of `refresh` is unchanged (a source omitting it still parses to `{}`, measured at base and head), but `ai/KnowledgeSource` now publishes as the input shape and zod's input-mode projection carries no `default` keyword for a `.default()` whose object holds a transform — declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh` description states the materialized default in words. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i --- content/docs/references/ai/knowledge-source.mdx | 11 +++++++++-- packages/spec/api-surface/ai.json | 2 ++ packages/spec/authorable-defaults/ai.json | 1 - packages/spec/export-origins/ai.json | 2 ++ packages/spec/scripts/lib/default-changes.ts | 17 +++++++++++++++++ packages/spec/src/ai/knowledge-source.zod.ts | 11 +++++++++-- 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/content/docs/references/ai/knowledge-source.mdx b/content/docs/references/ai/knowledge-source.mdx index 29275b0f1f..f5726eea87 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` | optional | | +| **cron** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; 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`. | --- @@ -84,7 +84,7 @@ const result = FileKnowledgeSourceSchema.parse(data); | **source** | `{ kind: 'object'; object: string; contentFields: string[]; metadataFields?: string[]; … } \| { kind: 'file'; prefix: string; mimeTypes?: string[] } \| { kind: 'http'; urls: string[]; userAgent?: string }` | ✅ | | | **embedding** | `{ provider: Enum<'openai' \| 'cohere' \| 'azure_openai' \| 'huggingface' \| 'local' \| 'custom'>; model: string; dimensions: integer; endpoint?: string; … }` | optional | | | **vectorStore** | `{ provider: Enum<'pgvector' \| 'chroma' \| 'qdrant' \| 'pinecone' \| 'weaviate' \| 'milvus' \| 'redis' \| …>; collection: string; endpoint?: string; secretRef?: string; … }` | optional | | -| **refresh** | `{ onRecordChange?: boolean; cron?: string }` | optional (default: `{}`) | | +| **refresh** | `{ onRecordChange?: boolean; cron?: string \| object }` | optional | Refresh / sync configuration; omitted parses to `{}` (the runtime default — the published input-shape JSON Schema cannot state it beside `cron`'s transform). | | **aiExposed** | `boolean` | optional (default: `true`) | | ### Nested Shape: `KnowledgeSource.source[kind='object']` @@ -125,6 +125,13 @@ const result = FileKnowledgeSourceSchema.parse(data); | **secretRef** | `string` | optional | Reference to stored credential secret | | **dimensions** | `integer` | optional | | +### Nested Shape: `KnowledgeSource.refresh` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **onRecordChange** | `boolean` | optional (default: `true`) | | +| **cron** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; 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`. | + --- diff --git a/packages/spec/api-surface/ai.json b/packages/spec/api-surface/ai.json index 6a8122026f..9d7c970b3d 100644 --- a/packages/spec/api-surface/ai.json +++ b/packages/spec/api-surface/ai.json @@ -64,10 +64,12 @@ "KnowledgeHit (type)", "KnowledgeHitSchema (const)", "KnowledgeRefreshPolicy (type)", + "KnowledgeRefreshPolicyParsed (type)", "KnowledgeRefreshPolicySchema (const)", "KnowledgeSource (type)", "KnowledgeSourceKind (type)", "KnowledgeSourceKindSchema (const)", + "KnowledgeSourceParsed (type)", "KnowledgeSourceSchema (const)", "MCPApprovalPolicy (type)", "MCPApprovalPolicySchema (const)", diff --git a/packages/spec/authorable-defaults/ai.json b/packages/spec/authorable-defaults/ai.json index 6e00f55851..b5c8f97d09 100644 --- a/packages/spec/authorable-defaults/ai.json +++ b/packages/spec/authorable-defaults/ai.json @@ -23,7 +23,6 @@ "ai/KnowledgeRefreshPolicy:onRecordChange = true", "ai/KnowledgeSource:adapterConfig = {}", "ai/KnowledgeSource:aiExposed = true", - "ai/KnowledgeSource:refresh = {}", "ai/MCPServerRef:active = true", "ai/MCPToolBinding:approval = \"never\"", "ai/ModelCapability:codeGeneration = false", diff --git a/packages/spec/export-origins/ai.json b/packages/spec/export-origins/ai.json index 13e696d5c3..1cf29b92e7 100644 --- a/packages/spec/export-origins/ai.json +++ b/packages/spec/export-origins/ai.json @@ -64,10 +64,12 @@ "KnowledgeHit": "src/ai/knowledge-document.zod.ts#KnowledgeHit (type)", "KnowledgeHitSchema": "src/ai/knowledge-document.zod.ts#KnowledgeHitSchema (const)", "KnowledgeRefreshPolicy": "src/ai/knowledge-source.zod.ts#KnowledgeRefreshPolicy (type)", + "KnowledgeRefreshPolicyParsed": "src/ai/knowledge-source.zod.ts#KnowledgeRefreshPolicyParsed (type)", "KnowledgeRefreshPolicySchema": "src/ai/knowledge-source.zod.ts#KnowledgeRefreshPolicySchema (const)", "KnowledgeSource": "src/ai/knowledge-source.zod.ts#KnowledgeSource (type)", "KnowledgeSourceKind": "src/ai/knowledge-source.zod.ts#KnowledgeSourceKind (type)", "KnowledgeSourceKindSchema": "src/ai/knowledge-source.zod.ts#KnowledgeSourceKindSchema (const)", + "KnowledgeSourceParsed": "src/ai/knowledge-source.zod.ts#KnowledgeSourceParsed (type)", "KnowledgeSourceSchema": "src/ai/knowledge-source.zod.ts#KnowledgeSourceSchema (const)", "MCPApprovalPolicy": "src/ai/mcp.zod.ts#MCPApprovalPolicy (type)", "MCPApprovalPolicySchema": "src/ai/mcp.zod.ts#MCPApprovalPolicySchema (const)", diff --git a/packages/spec/scripts/lib/default-changes.ts b/packages/spec/scripts/lib/default-changes.ts index 464eb7de9c..97f759c2d4 100644 --- a/packages/spec/scripts/lib/default-changes.ts +++ b/packages/spec/scripts/lib/default-changes.ts @@ -313,5 +313,22 @@ export const DEFAULT_CHANGES_BY_MAJOR: Readonly z.object({ * adapters own their own backend. */ vectorStore: VectorStoreSchema.optional(), - /** Refresh / sync configuration. */ - refresh: KnowledgeRefreshPolicySchema.default({}).optional(), + /** + * Refresh / sync configuration. Omitted ⇒ parses to `{}` — the runtime + * default, stated here in words because the published input-shape JSON + * Schema cannot carry it beside `cron`'s transform (#14825). + */ + refresh: KnowledgeRefreshPolicySchema.default({}).optional().describe( + 'Refresh / sync configuration; omitted parses to `{}` (the runtime default — the published ' + + "input-shape JSON Schema cannot state it beside `cron`'s transform).", + ), /** Whether `search_knowledge` may expose this source to AI agents. */ aiExposed: z.boolean().default(true).optional(), }));