Skip to content

Commit 778c59f

Browse files
feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) (#15029)
* feat(spec): type `KnowledgeRefreshPolicy.cron` with `CronExpressionInputSchema` (#14825) `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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i * chore(spec): regenerate artifacts for the typed `refresh.cron`; declare the projection-only default move (#14825) `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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0fb944b commit 778c59f

9 files changed

Lines changed: 241 additions & 12 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): type `KnowledgeRefreshPolicy.cron` with the shared cron dialect — `CronExpressionInputSchema`, a describe that promises what the parse enforces (#14825)
6+
7+
**BREAKING** accept-set narrowing and parsed-shape change on a published
8+
authorable key, shipped as `minor` under the repo's launch-window convention
9+
for breaking changes.
10+
11+
`KnowledgeRefreshPolicySchema.cron` (`refresh.cron` on a knowledge source) was
12+
a bare `z.string()` under a doc comment promising a 5-field cron expression —
13+
a constraint nothing checked (ADR-0049 declared ≠ enforced). It now carries
14+
`CronExpressionInputSchema`, the cron-dialect input the spec's other
15+
cron-shaped fields already use (`ScheduledExport.schedule.cronExpression`,
16+
`ScheduleState.cronExpression`, `Connector.schedule`).
17+
18+
What changes for authored metadata, measured rather than assumed:
19+
20+
- A bare non-empty string is still the shorthand — `refresh: { cron: '0 3 * * *' }`
21+
keeps parsing; stored `sys_metadata` rows re-parse unchanged.
22+
- The expression envelope `{ dialect: 'cron', source }` is now accepted too.
23+
- An **empty string** is now refused (`invalid_union` at `refresh.cron`); it
24+
named no schedule before, so the only remedy is to delete the key.
25+
- The **parsed** value is now the `{ dialect: 'cron', source }` envelope
26+
rather than the bare string — the same shape the three sibling cron fields
27+
produce. Zero readers of the parsed value were measured in `objectstack` and
28+
the pinned `objectui` (`service-knowledge` reads only `refresh.onRecordChange`;
29+
it never schedules the cron).
30+
- `KnowledgeRefreshPolicyParsed` and `KnowledgeSourceParsed` are new exported
31+
aliases naming the parsed state (ADR-0122); the bare `KnowledgeRefreshPolicy`
32+
/ `KnowledgeSource` aliases stay the author state and still accept a string.
33+
34+
What the schema now promises is exactly what the parse enforces: a non-empty
35+
string or an expression envelope, normalized to the envelope. Cron **syntax**
36+
is not judged at parse time by the shared dialect — `'not a cron'` normalizes
37+
like any other string, and the syntax verdict (5- or 6-field, or an `@yearly`
38+
`@reboot` alias) is the `cron` dialect engine's when the expression is
39+
evaluated. The describe says so instead of restating "5-field", and the pin
40+
file records the measured behaviour so a later change to the shared dialect
41+
surfaces here.
42+
43+
<!-- adr-0087: not-required (no-migration-prescription) No authorable key is removed, renamed or re-homed and the bare-string shorthand keeps parsing byte-identically, so `objectstack migrate meta` has nothing to rewrite and no tombstone exists; the one newly refused input, an empty string, never named a schedule and has no conversion target; the parsed-side envelope has zero measured readers in objectstack and the pinned objectui. -->

content/docs/references/ai/knowledge-source.mdx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const result = FileKnowledgeSourceSchema.parse(data);
6565
| Property | Type | Required | Description |
6666
| :--- | :--- | :--- | :--- |
6767
| **onRecordChange** | `boolean` | optional (default: `true`) | |
68-
| **cron** | `string` | optional | |
68+
| **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`. |
6969

7070

7171
---
@@ -84,7 +84,7 @@ const result = FileKnowledgeSourceSchema.parse(data);
8484
| **source** | `{ kind: 'object'; object: string; contentFields: string[]; metadataFields?: string[]; … } \| { kind: 'file'; prefix: string; mimeTypes?: string[] } \| { kind: 'http'; urls: string[]; userAgent?: string }` || |
8585
| **embedding** | `{ provider: Enum<'openai' \| 'cohere' \| 'azure_openai' \| 'huggingface' \| 'local' \| 'custom'>; model: string; dimensions: integer; endpoint?: string; … }` | optional | |
8686
| **vectorStore** | `{ provider: Enum<'pgvector' \| 'chroma' \| 'qdrant' \| 'pinecone' \| 'weaviate' \| 'milvus' \| 'redis' \| …>; collection: string; endpoint?: string; secretRef?: string; … }` | optional | |
87-
| **refresh** | `{ onRecordChange?: boolean; cron?: string }` | optional (default: `{}`) | |
87+
| **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). |
8888
| **aiExposed** | `boolean` | optional (default: `true`) | |
8989

9090
### Nested Shape: `KnowledgeSource.source[kind='object']`
@@ -125,6 +125,13 @@ const result = FileKnowledgeSourceSchema.parse(data);
125125
| **secretRef** | `string` | optional | Reference to stored credential secret |
126126
| **dimensions** | `integer` | optional | |
127127

128+
### Nested Shape: `KnowledgeSource.refresh`
129+
130+
| Property | Type | Required | Description |
131+
| :--- | :--- | :--- | :--- |
132+
| **onRecordChange** | `boolean` | optional (default: `true`) | |
133+
| **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`. |
134+
128135

129136
---
130137

packages/spec/api-surface/ai.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@
6464
"KnowledgeHit (type)",
6565
"KnowledgeHitSchema (const)",
6666
"KnowledgeRefreshPolicy (type)",
67+
"KnowledgeRefreshPolicyParsed (type)",
6768
"KnowledgeRefreshPolicySchema (const)",
6869
"KnowledgeSource (type)",
6970
"KnowledgeSourceKind (type)",
7071
"KnowledgeSourceKindSchema (const)",
72+
"KnowledgeSourceParsed (type)",
7173
"KnowledgeSourceSchema (const)",
7274
"MCPApprovalPolicy (type)",
7375
"MCPApprovalPolicySchema (const)",

packages/spec/authorable-defaults/ai.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
"ai/KnowledgeRefreshPolicy:onRecordChange = true",
2424
"ai/KnowledgeSource:adapterConfig = {}",
2525
"ai/KnowledgeSource:aiExposed = true",
26-
"ai/KnowledgeSource:refresh = {}",
2726
"ai/MCPServerRef:active = true",
2827
"ai/MCPToolBinding:approval = \"never\"",
2928
"ai/ModelCapability:codeGeneration = false",

packages/spec/export-origins/ai.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@
6464
"KnowledgeHit": "src/ai/knowledge-document.zod.ts#KnowledgeHit (type)",
6565
"KnowledgeHitSchema": "src/ai/knowledge-document.zod.ts#KnowledgeHitSchema (const)",
6666
"KnowledgeRefreshPolicy": "src/ai/knowledge-source.zod.ts#KnowledgeRefreshPolicy (type)",
67+
"KnowledgeRefreshPolicyParsed": "src/ai/knowledge-source.zod.ts#KnowledgeRefreshPolicyParsed (type)",
6768
"KnowledgeRefreshPolicySchema": "src/ai/knowledge-source.zod.ts#KnowledgeRefreshPolicySchema (const)",
6869
"KnowledgeSource": "src/ai/knowledge-source.zod.ts#KnowledgeSource (type)",
6970
"KnowledgeSourceKind": "src/ai/knowledge-source.zod.ts#KnowledgeSourceKind (type)",
7071
"KnowledgeSourceKindSchema": "src/ai/knowledge-source.zod.ts#KnowledgeSourceKindSchema (const)",
72+
"KnowledgeSourceParsed": "src/ai/knowledge-source.zod.ts#KnowledgeSourceParsed (type)",
7173
"KnowledgeSourceSchema": "src/ai/knowledge-source.zod.ts#KnowledgeSourceSchema (const)",
7274
"MCPApprovalPolicy": "src/ai/mcp.zod.ts#MCPApprovalPolicy (type)",
7375
"MCPApprovalPolicySchema": "src/ai/mcp.zod.ts#MCPApprovalPolicySchema (const)",

packages/spec/scripts/lib/default-changes.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,5 +313,22 @@ export const DEFAULT_CHANGES_BY_MAJOR: Readonly<Record<number, readonly Declared
313313
+ 'sequence that never identifies a record), write `unique: false` explicitly; to keep '
314314
+ 'what the schema now does for you, change nothing. Nothing moves for any other type.',
315315
},
316+
{
317+
key: 'ai/KnowledgeSource:refresh',
318+
from: '{}',
319+
to: '(none)',
320+
reason:
321+
'The runtime default did NOT move: a knowledge source that omits `refresh` still parses to '
322+
+ '`refresh: {}` — measured with `KnowledgeSourceSchema.parse` on a minimal source at the '
323+
+ 'base and after the change, byte-identical. What vanished is the `default` keyword in '
324+
+ 'the PUBLISHED JSON Schema. Typing `KnowledgeRefreshPolicy.cron` with the shared cron '
325+
+ 'dialect (`CronExpressionInputSchema`, #14825) puts a transform inside the object that '
326+
+ '`refresh` defaults to, so `ai/KnowledgeSource` now publishes as the input shape, and '
327+
+ "zod's input-mode projection carries no `default` for a `.default()` whose object holds a "
328+
+ 'transform (isolated: the same wrapper around a transform-free object keeps it). Nothing '
329+
+ 'deployed changes behaviour; the consumer affected is one outside this repo who reads '
330+
+ "the published JSON Schema's `default` himself — the `refresh` description now states "
331+
+ 'the materialized default in words, the `data/Field:unique` precedent.',
332+
},
316333
],
317334
};
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// `KnowledgeRefreshPolicySchema.cron` — pins for the typed cron slot (#14825).
4+
//
5+
// The slot was a bare `z.string()` under a doc comment promising a 5-field
6+
// cron, so the promise was enforced nowhere (ADR-0049 declared ≠ enforced). It
7+
// now carries `CronExpressionInputSchema`, the shared cron-dialect input the
8+
// other cron-shaped fields already use (`api/export.zod.ts`,
9+
// `automation/execution.zod.ts`, `integration/connector.zod.ts`). What that
10+
// schema ENFORCES was measured before these pins were written, and the pins
11+
// state exactly that — no more:
12+
//
13+
// - a bare non-empty string normalizes to `{ dialect: 'cron', source }`;
14+
// - an expression envelope passes through;
15+
// - an empty string, a non-string, or an envelope naming an unknown dialect
16+
// is refused with `invalid_union` at the slot's own path;
17+
// - cron SYNTAX is not judged at parse time. `'not a cron'` normalizes like
18+
// any other string: the syntax verdict belongs to the `cron` dialect engine
19+
// (`@objectstack/formula` cron-engine — 5- or 6-field, or an `@` alias) when
20+
// the expression is evaluated. That pin is deliberate: it is what keeps the
21+
// schema's describe honest. If the shared dialect ever gains parse-time
22+
// syntax validation, this pin flips, and the describe on the slot must be
23+
// rewritten in the same commit.
24+
25+
import { describe, expect, it } from 'vitest';
26+
import {
27+
KnowledgeRefreshPolicySchema,
28+
KnowledgeSourceSchema,
29+
type KnowledgeRefreshPolicy,
30+
type KnowledgeRefreshPolicyParsed,
31+
type KnowledgeSource,
32+
type KnowledgeSourceParsed,
33+
} from './knowledge-source.zod';
34+
35+
const SOURCE: KnowledgeSource = {
36+
id: 'kb_articles',
37+
label: 'KB articles',
38+
adapter: 'memory',
39+
source: { kind: 'object', object: 'kb_article', contentFields: ['title', 'body'] },
40+
};
41+
42+
const CRON_5_FIELD = '0 3 * * *';
43+
44+
describe('KnowledgeRefreshPolicySchema.cron — the typed cron slot (#14825)', () => {
45+
it('positive control: a 5-field cron on a full knowledge source parses and normalizes to the cron envelope', () => {
46+
const r = KnowledgeSourceSchema.safeParse({ ...SOURCE, refresh: { cron: CRON_5_FIELD } });
47+
expect(r.success, r.success ? '' : JSON.stringify(r.error.issues)).toBe(true);
48+
if (!r.success) return;
49+
expect(r.data.refresh?.cron).toEqual({ dialect: 'cron', source: CRON_5_FIELD });
50+
});
51+
52+
it('accepts the expression envelope form and passes it through', () => {
53+
const envelope = { dialect: 'cron' as const, source: '@daily' };
54+
const r = KnowledgeRefreshPolicySchema.safeParse({ cron: envelope });
55+
expect(r.success).toBe(true);
56+
if (!r.success) return;
57+
expect(r.data.cron).toEqual(envelope);
58+
});
59+
60+
it('absent stays absent — no `cron` key is fabricated by the parse', () => {
61+
const withEmptyRefresh = KnowledgeSourceSchema.safeParse({ ...SOURCE, refresh: {} });
62+
expect(withEmptyRefresh.success).toBe(true);
63+
if (withEmptyRefresh.success) expect(withEmptyRefresh.data.refresh?.cron).toBeUndefined();
64+
65+
const withoutRefresh = KnowledgeSourceSchema.safeParse(SOURCE);
66+
expect(withoutRefresh.success).toBe(true);
67+
if (withoutRefresh.success) expect(withoutRefresh.data.refresh?.cron).toBeUndefined();
68+
});
69+
70+
it('refuses an empty string with `invalid_union` at `refresh.cron`', () => {
71+
const r = KnowledgeSourceSchema.safeParse({ ...SOURCE, refresh: { cron: '' } });
72+
expect(r.success).toBe(false);
73+
if (r.success) return;
74+
const issue = r.error.issues.find((i) => i.path.join('.') === 'refresh.cron');
75+
expect(issue, JSON.stringify(r.error.issues)).toBeDefined();
76+
expect(issue?.code).toBe('invalid_union');
77+
expect(issue?.message.split('.')[0]).toBe('Invalid input');
78+
});
79+
80+
it('refuses a non-string value with `invalid_union` at `cron`', () => {
81+
const r = KnowledgeRefreshPolicySchema.safeParse({ cron: 42 });
82+
expect(r.success).toBe(false);
83+
if (r.success) return;
84+
expect(r.error.issues.map((i) => [i.code, i.path.join('.')])).toEqual([['invalid_union', 'cron']]);
85+
});
86+
87+
it('refuses an envelope naming a dialect the protocol does not declare', () => {
88+
// `js` was retired from `ExpressionDialect` (#3278, ADR-0058 addendum).
89+
const r = KnowledgeRefreshPolicySchema.safeParse({ cron: { dialect: 'js', source: 'x' } });
90+
expect(r.success).toBe(false);
91+
if (r.success) return;
92+
expect(r.error.issues.map((i) => [i.code, i.path.join('.')])).toEqual([['invalid_union', 'cron']]);
93+
});
94+
95+
it('does NOT judge cron syntax at parse time — measured, and the describe promises no more (declared = enforced)', () => {
96+
// The syntax verdict is the `cron` dialect engine's at evaluate time:
97+
// `@objectstack/formula` cron-engine accepts 5- or 6-field expressions and
98+
// the `@yearly`…`@reboot` aliases. The parse only normalizes. If this case
99+
// ever goes red because the shared dialect learned to refuse syntax, update
100+
// the slot's describe in the same commit — do not weaken this pin.
101+
for (const source of ['not a cron', '0 0 3 * * *', '@daily']) {
102+
const r = KnowledgeRefreshPolicySchema.safeParse({ cron: source });
103+
expect(r.success, `${JSON.stringify(source)} should normalize, not be refused`).toBe(true);
104+
if (r.success) expect(r.data.cron).toEqual({ dialect: 'cron', source });
105+
}
106+
});
107+
108+
it('names both states (ADR-0122): the bare alias is the author state, `XParsed` the parsed state', () => {
109+
// Author state: a bare string is what an author writes.
110+
const authored: KnowledgeRefreshPolicy = { cron: CRON_5_FIELD };
111+
// Parsed state: the envelope is what a consumer holds after the parse.
112+
const parsed: KnowledgeRefreshPolicyParsed = KnowledgeRefreshPolicySchema.parse(authored);
113+
expect(parsed.cron).toEqual({ dialect: 'cron', source: CRON_5_FIELD });
114+
// @ts-expect-error — a bare string is the AUTHOR shape, not the parsed one.
115+
const notParsed: KnowledgeRefreshPolicyParsed = { cron: CRON_5_FIELD };
116+
expect(notParsed).toBeDefined();
117+
118+
const parsedSource: KnowledgeSourceParsed = KnowledgeSourceSchema.parse({ ...SOURCE, refresh: { cron: CRON_5_FIELD } });
119+
expect(parsedSource.refresh?.cron).toEqual({ dialect: 'cron', source: CRON_5_FIELD });
120+
});
121+
});

packages/spec/src/ai/knowledge-source.zod.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { z } from 'zod';
4+
import { CronExpressionInputSchema } from '../shared/expression.zod';
45
import { lazySchema } from '../shared/lazy-schema';
56
import { EmbeddingModelSchema, VectorStoreSchema } from './embedding.zod';
67

@@ -25,12 +26,30 @@ export const KnowledgeRefreshPolicySchema = lazySchema(() => z.object({
2526
*/
2627
onRecordChange: z.boolean().default(true).optional(),
2728
/**
28-
* Cron expression (5-field) for periodic full reindex. Optional.
29+
* Cron-dialect expression for a periodic full reindex. Optional.
30+
*
31+
* `CronExpressionInputSchema` — the shared cron-dialect input the other
32+
* cron-shaped fields carry (`api/export.zod.ts`, `automation/execution.zod.ts`,
33+
* `integration/connector.zod.ts`). A bare string is shorthand for
34+
* `{ dialect: 'cron', source }`; the parse enforces a non-empty string or an
35+
* expression envelope and normalizes to the envelope. It does NOT judge cron
36+
* syntax: that verdict is the `cron` dialect engine's (`@objectstack/formula`
37+
* cron-engine — 5- or 6-field, or an `@yearly`…`@reboot` alias) when the
38+
* expression is evaluated, so the describe below promises exactly what the
39+
* parse enforces (ADR-0049 declared = enforced; #14825).
40+
*
2941
* `service-knowledge` does not schedule the cron itself — it merely
3042
* surfaces the value so an automation flow / external scheduler can
3143
* trigger `reindexSource`.
3244
*/
33-
cron: z.string().optional(),
45+
cron: CronExpressionInputSchema.optional().describe(
46+
'Cron-dialect expression for a periodic full reindex. A bare string is shorthand for '
47+
+ '`{ dialect: \'cron\', source }`; the parse enforces a non-empty string or an expression '
48+
+ 'envelope and normalizes to the envelope — cron syntax (5- or 6-field, or an `@` alias) is '
49+
+ 'the `cron` dialect engine\'s verdict when the expression is evaluated, not checked here. '
50+
+ '`service-knowledge` does not schedule it: the value is surfaced so an automation flow / '
51+
+ 'external scheduler can trigger `reindexSource`.',
52+
),
3453
}));
3554

3655
/** Source backed by an ObjectQL object — each record becomes a document. */
@@ -109,15 +128,26 @@ export const KnowledgeSourceSchema = lazySchema(() => z.object({
109128
* adapters own their own backend.
110129
*/
111130
vectorStore: VectorStoreSchema.optional(),
112-
/** Refresh / sync configuration. */
113-
refresh: KnowledgeRefreshPolicySchema.default({}).optional(),
131+
/**
132+
* Refresh / sync configuration. Omitted ⇒ parses to `{}` — the runtime
133+
* default, stated here in words because the published input-shape JSON
134+
* Schema cannot carry it beside `cron`'s transform (#14825).
135+
*/
136+
refresh: KnowledgeRefreshPolicySchema.default({}).optional().describe(
137+
'Refresh / sync configuration; omitted parses to `{}` (the runtime default — the published '
138+
+ "input-shape JSON Schema cannot state it beside `cron`'s transform).",
139+
),
114140
/** Whether `search_knowledge` may expose this source to AI agents. */
115141
aiExposed: z.boolean().default(true).optional(),
116142
}));
117143

118144
export type KnowledgeRefreshPolicy = z.input<typeof KnowledgeRefreshPolicySchema>;
145+
/** Post-parse shape of {@link KnowledgeRefreshPolicy} — defaults applied, transforms run (ADR-0122): `cron` is the `{ dialect: 'cron', source }` envelope. */
146+
export type KnowledgeRefreshPolicyParsed = z.infer<typeof KnowledgeRefreshPolicySchema>;
119147
export type ObjectKnowledgeSource = z.input<typeof ObjectKnowledgeSourceSchema>;
120148
export type FileKnowledgeSource = z.input<typeof FileKnowledgeSourceSchema>;
121149
export type HttpKnowledgeSource = z.input<typeof HttpKnowledgeSourceSchema>;
122150
export type KnowledgeSourceKind = z.input<typeof KnowledgeSourceKindSchema>;
123151
export type KnowledgeSource = z.input<typeof KnowledgeSourceSchema>;
152+
/** Post-parse shape of {@link KnowledgeSource} — defaults applied, transforms run (ADR-0122): `refresh.cron` is the cron envelope. */
153+
export type KnowledgeSourceParsed = z.infer<typeof KnowledgeSourceSchema>;

0 commit comments

Comments
 (0)