@happyvertical/smrt-core 0.55.16 → 0.55.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/cookbook.md +9 -0
- package/dist/cookbook/engine/document.d.ts +1 -0
- package/dist/cookbook/engine/document.d.ts.map +1 -1
- package/dist/cookbook/engine/document.js +6 -0
- package/dist/cookbook/engine/document.js.map +1 -1
- package/dist/cookbook/engine/engine.d.ts +2 -0
- package/dist/cookbook/engine/engine.d.ts.map +1 -1
- package/dist/cookbook/engine/engine.js +1 -1
- package/dist/cookbook/engine/engine.js.map +1 -1
- package/dist/cookbook/engine/schemas.d.ts.map +1 -1
- package/dist/cookbook/engine/schemas.js +7 -0
- package/dist/cookbook/engine/schemas.js.map +1 -1
- package/dist/cookbook/engine/types.d.ts +1 -0
- package/dist/cookbook/engine/types.d.ts.map +1 -1
- package/dist/cookbook/engine/types.js.map +1 -1
- package/dist/cookbook/v1.schema.json +2 -0
- package/dist/cookbook/validate.d.ts.map +1 -1
- package/dist/cookbook/validate.js +5 -0
- package/dist/cookbook/validate.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../src/cookbook/engine/types.ts"],"sourcesContent":["/**\n * Cookbook command engine, contract v1 (#3753).\n *\n * Plain data in, plain data out: no SvelteKit, browser storage, planner\n * singleton or inference dependency, and no Node imports, so the same engine\n * runs in a browser planner, a CLI, an MCP server or a website. Adapters own\n * navigation/focus, persistence and transport; the engine owns the document.\n */\nimport type { Cookbook, RecipeDefinition } from '@happyvertical/smrt-types';\n\n/**\n * The command set version. A change an older client could not send or read\n * bumps it; adding a command or an optional field does not.\n */\nexport const COOKBOOK_COMMANDS_VERSION = 1;\n\n/** The part of a manifest recipe the engine reads (a `RecipeDefinition` fits). */\nexport type EngineRecipe = Pick<\n RecipeDefinition,\n 'id' | 'label' | 'models' | 'nav' | 'requires'\n> &\n Partial<\n Pick<RecipeDefinition, 'requiresAny' | 'group' | 'section' | 'options'>\n >;\n\n/** One field of a catalog model, for `set_policy` and settings defaults. */\nexport interface EngineField {\n name: string;\n /** System fields (id, timestamps) cannot be given a policy. */\n system?: boolean;\n /** The field's own starting value, so a setting equal to it writes no row. */\n default?: unknown;\n}\n\n/** One catalog model. Same shape as smrt-svelte's `CookbookModelInfo`, plus fields. */\nexport interface EngineModel {\n /** Qualified name, `@scope/pkg:Class`. */\n id: string;\n name: string;\n /** Package id used in item ids: `commerce` for `@happyvertical/smrt-commerce`. */\n packageId: string;\n description?: string;\n /** Has an API, MCP or CLI surface; only exposed models can be added as features. */\n exposed?: boolean;\n /** When given, `set_policy` rejects a field not listed. */\n fields?: readonly EngineField[];\n}\n\n/** A ready-made cookbook the catalog offers (a library entry). */\nexport interface EngineLibraryCookbook {\n id: string;\n name: string;\n document: Cookbook;\n}\n\n/** Which model fields each app setting is the default of. */\nexport type SettingTargets = Record<\n 'currency' | 'taxRate' | 'paymentTerms',\n readonly { model: string; field: string }[]\n>;\n\n/**\n * Everything the engine resolves ids against, as data: recipes and models from\n * published manifests, optionally a library of cookbooks. Nothing here is\n * bundled with the engine.\n */\nexport interface EngineCatalog {\n recipes: readonly EngineRecipe[];\n models: readonly EngineModel[];\n cookbooks?: readonly EngineLibraryCookbook[];\n /** Movable shell item ids resolved by the host (`dock:`, `slot:`, recipe, or host ids). */\n placementItems?: readonly string[];\n /** Theme preset names; default {@link DEFAULT_THEME_PRESETS}. */\n themePresets?: readonly string[];\n /** ISO 4217 codes; default any three capital letters. */\n currencies?: readonly string[];\n /** Default {@link COMMERCE_SETTING_TARGETS}. */\n settingTargets?: SettingTargets;\n}\n\n/** Input of each command, keyed by the command's (tool) name. */\nexport interface CommandInputs {\n /** Switch recipes on, with everything they `require`. */\n add_recipes: { ids: string[] };\n /** Switch recipes off; ones another recipe needs are kept (and reported). */\n remove_recipes: { ids: string[] };\n /** Add models no recipe covers, by qualified name. */\n add_features: { ids: string[] };\n remove_features: { ids: string[] };\n /** Add the recipes and features of a library cookbook to what is there. */\n add_cookbook: { id: string };\n /** Remove what a library cookbook added (recipes others need are kept). */\n remove_cookbook: { id: string };\n /** Select a library cookbook, replacing the app (needs `replace` if non-empty). */\n apply_cookbook: { id: string; replace?: boolean };\n /** Import a cookbook document, replacing the app (needs `replace` if non-empty). */\n import_cookbook: { document: unknown; replace?: boolean };\n /** App settings; tax rate in percent, 0 to 100. */\n set_settings: { currency?: string; taxRate?: number; paymentTerms?: string };\n /** Set (or clear with null) one field's policy row. App scope. */\n set_policy: {\n model: string;\n field: string;\n defaultValue?: unknown;\n visibility?: 'basic' | 'advanced' | 'hidden' | null;\n label?: string | null;\n help?: string | null;\n };\n set_theme: {\n preset?: string;\n primary?: string;\n colorScheme?: 'light' | 'dark' | 'system';\n };\n reset_theme: Record<string, never>;\n /** Name (and describe) the app; null clears. */\n set_name: { name?: string | null; description?: string | null };\n rename_section: { id: string; label: string };\n /** `null` restores the entry's own name. */\n rename_item: { id: string; label: string | null };\n hide: { id: string };\n show: { id: string };\n /** Check the current document and report typed diagnostics. Changes nothing. */\n validate: Record<string, never>;\n /** The cookbook as `<name>.cookbook.json` text. Changes nothing. */\n export_cookbook: { name?: string };\n /** Undo an earlier change by the `undoId` its receipt carried. */\n undo: { undoId: string; force?: boolean };\n}\n\nexport type CommandName = keyof CommandInputs;\n\n/** A command as a client sends it. */\nexport type CookbookCommand = {\n [N in CommandName]: {\n name: N;\n input: CommandInputs[N];\n /** Retry key: a repeated id returns the first receipt and applies nothing. */\n id?: string;\n /** Refuse with `conflict` unless the plan is still at this revision. */\n expectedRevision?: number;\n };\n}[CommandName];\n\n/** Stable strings a client can switch on. */\nexport type CommandErrorCode =\n | 'unknown_command'\n | 'invalid_input'\n | 'not_found'\n /** A destructive command needs `replace: true` (or `force: true`). */\n | 'confirmation_required'\n /** The plan moved on since the caller looked (`expectedRevision`, undo). */\n | 'conflict'\n /** A command id was reused with a different command. */\n | 'id_reuse'\n /** An atomic batch failed; `details.index` names the command, nothing applied. */\n | 'batch_failed'\n | 'failed';\n\nexport interface CommandError {\n code: CommandErrorCode;\n message: string;\n /** Where the problem is, as a path into the input (`input.ids[1]`). */\n path?: string;\n details?: Record<string, unknown>;\n}\n\n/** What a recipe/feature command did, after `requires` ran. */\nexport interface RecipeChanges {\n added: string[];\n removed: string[];\n /** Asked to remove, but another recipe still needs them. */\n kept: string[];\n}\n\nexport interface PlanSnapshot {\n version: typeof COOKBOOK_COMMANDS_VERSION;\n /** Counts applied changes; equal histories give equal revisions. */\n revision: number;\n app: { name: string; description: string | null; cookbook: string | null };\n recipes: { id: string; label: string }[];\n features: string[];\n /** Recipe and feature ids this catalog lacks; kept on export. */\n unavailable: string[];\n settings: { currency: string; taxRate: number; paymentTerms: string };\n theme: {\n text: string;\n preset: string | null;\n primary: string | null;\n colorScheme: string;\n };\n sections: {\n id: string;\n label: string;\n hidden: boolean;\n items: { id: string; label: string; hidden: boolean }[];\n }[];\n policies: number;\n /** Undo ids still available, newest last. */\n undo: string[];\n}\n\nexport interface CommandReceipt {\n name: CommandName;\n /** The command id, when one was sent. */\n id?: string;\n summary: string;\n changed: boolean;\n revisionBefore: number;\n revisionAfter: number;\n undoId?: string;\n changes?: RecipeChanges;\n /** Non-fatal notes (an import's legacy `$schema` migration). */\n warnings?: string[];\n}\n\nexport type CommandResult =\n | {\n ok: true;\n id?: string;\n /** True when this is the stored result of an earlier run of the same id. */\n replayed?: boolean;\n snapshot: PlanSnapshot;\n receipt: CommandReceipt;\n data?: unknown;\n }\n | { ok: false; id?: string; replayed?: boolean; error: CommandError };\n\nexport interface RunOptions {\n expectedRevision?: number;\n}\n\n/** Several commands applied all-or-nothing. */\nexport interface CommandBatch {\n id?: string;\n commands: readonly unknown[];\n expectedRevision?: number;\n}\n\nexport type BatchResult =\n | {\n ok: true;\n id?: string;\n replayed?: boolean;\n snapshot: PlanSnapshot;\n receipt: {\n id?: string;\n count: number;\n changed: boolean;\n revisionBefore: number;\n revisionAfter: number;\n undoId?: string;\n };\n /** One result per command, in order. */\n results: Extract<CommandResult, { ok: true }>[];\n }\n | {\n ok: false;\n id?: string;\n replayed?: boolean;\n error: CommandError;\n /** The state is exactly as before the batch. */\n snapshot: PlanSnapshot;\n };\n\n/** One diagnostic from `validate`. */\nexport interface Diagnostic {\n severity: 'error' | 'warning';\n code: string;\n path: string;\n message: string;\n}\n\nexport interface ValidateData {\n ok: boolean;\n diagnostics: Diagnostic[];\n}\n\nexport interface ExportData {\n fileName: string;\n text: string;\n cookbook: Cookbook;\n}\n\n/** A journal row: enough to answer a retry of `id` after a restart. */\nexport interface JournalEntry {\n id: string;\n fingerprint: string;\n result: CommandResult | BatchResult;\n}\n\n/** The engine's whole state as plain JSON, to persist and resume from. */\nexport interface EngineState {\n version: typeof COOKBOOK_COMMANDS_VERSION;\n revision: number;\n /** The library cookbook the plan was last set up from, if any. */\n applied: string | null;\n cookbook: Cookbook;\n journal: JournalEntry[];\n}\n\nexport interface JsonSchema {\n [key: string]: unknown;\n}\n\n/** One command as an LLM or MCP tool definition. */\nexport interface CommandTool {\n name: CommandName;\n description: string;\n inputSchema: JsonSchema;\n}\n"],"mappings":";;;;;AAcA,IAAa,4BAA4B"}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../src/cookbook/engine/types.ts"],"sourcesContent":["/**\n * Cookbook command engine, contract v1 (#3753).\n *\n * Plain data in, plain data out: no SvelteKit, browser storage, planner\n * singleton or inference dependency, and no Node imports, so the same engine\n * runs in a browser planner, a CLI, an MCP server or a website. Adapters own\n * navigation/focus, persistence and transport; the engine owns the document.\n */\nimport type { Cookbook, RecipeDefinition } from '@happyvertical/smrt-types';\n\n/**\n * The command set version. A change an older client could not send or read\n * bumps it; adding a command or an optional field does not.\n */\nexport const COOKBOOK_COMMANDS_VERSION = 1;\n\n/** The part of a manifest recipe the engine reads (a `RecipeDefinition` fits). */\nexport type EngineRecipe = Pick<\n RecipeDefinition,\n 'id' | 'label' | 'models' | 'nav' | 'requires'\n> &\n Partial<\n Pick<RecipeDefinition, 'requiresAny' | 'group' | 'section' | 'options'>\n >;\n\n/** One field of a catalog model, for `set_policy` and settings defaults. */\nexport interface EngineField {\n name: string;\n /** System fields (id, timestamps) cannot be given a policy. */\n system?: boolean;\n /** The field's own starting value, so a setting equal to it writes no row. */\n default?: unknown;\n}\n\n/** One catalog model. Same shape as smrt-svelte's `CookbookModelInfo`, plus fields. */\nexport interface EngineModel {\n /** Qualified name, `@scope/pkg:Class`. */\n id: string;\n name: string;\n /** Package id used in item ids: `commerce` for `@happyvertical/smrt-commerce`. */\n packageId: string;\n description?: string;\n /** Has an API, MCP or CLI surface; only exposed models can be added as features. */\n exposed?: boolean;\n /** When given, `set_policy` rejects a field not listed. */\n fields?: readonly EngineField[];\n}\n\n/** A ready-made cookbook the catalog offers (a library entry). */\nexport interface EngineLibraryCookbook {\n id: string;\n name: string;\n document: Cookbook;\n}\n\n/** Which model fields each app setting is the default of. */\nexport type SettingTargets = Record<\n 'currency' | 'taxRate' | 'paymentTerms',\n readonly { model: string; field: string }[]\n>;\n\n/**\n * Everything the engine resolves ids against, as data: recipes and models from\n * published manifests, optionally a library of cookbooks. Nothing here is\n * bundled with the engine.\n */\nexport interface EngineCatalog {\n recipes: readonly EngineRecipe[];\n models: readonly EngineModel[];\n cookbooks?: readonly EngineLibraryCookbook[];\n /** Movable shell item ids resolved by the host (`dock:`, `slot:`, recipe, or host ids). */\n placementItems?: readonly string[];\n /** Theme preset names; default {@link DEFAULT_THEME_PRESETS}. */\n themePresets?: readonly string[];\n /** ISO 4217 codes; default any three capital letters. */\n currencies?: readonly string[];\n /** Default {@link COMMERCE_SETTING_TARGETS}. */\n settingTargets?: SettingTargets;\n}\n\n/** Input of each command, keyed by the command's (tool) name. */\nexport interface CommandInputs {\n /** Switch recipes on, with everything they `require`. */\n add_recipes: { ids: string[] };\n /** Switch recipes off; ones another recipe needs are kept (and reported). */\n remove_recipes: { ids: string[] };\n /** Add models no recipe covers, by qualified name. */\n add_features: { ids: string[] };\n remove_features: { ids: string[] };\n /** Add the recipes and features of a library cookbook to what is there. */\n add_cookbook: { id: string };\n /** Remove what a library cookbook added (recipes others need are kept). */\n remove_cookbook: { id: string };\n /** Select a library cookbook, replacing the app (needs `replace` if non-empty). */\n apply_cookbook: { id: string; replace?: boolean };\n /** Import a cookbook document, replacing the app (needs `replace` if non-empty). */\n import_cookbook: { document: unknown; replace?: boolean };\n /** App settings; tax rate in percent, 0 to 100. */\n set_settings: { currency?: string; taxRate?: number; paymentTerms?: string };\n /** Set (or clear with null) one field's policy row. App scope. */\n set_policy: {\n model: string;\n field: string;\n defaultValue?: unknown;\n visibility?: 'basic' | 'advanced' | 'hidden' | null;\n label?: string | null;\n help?: string | null;\n };\n set_theme: {\n preset?: string;\n primary?: string;\n colorScheme?: 'light' | 'dark' | 'system';\n fontFamily?: string | null;\n };\n reset_theme: Record<string, never>;\n /** Name (and describe) the app; null clears. */\n set_name: { name?: string | null; description?: string | null };\n rename_section: { id: string; label: string };\n /** `null` restores the entry's own name. */\n rename_item: { id: string; label: string | null };\n hide: { id: string };\n show: { id: string };\n /** Check the current document and report typed diagnostics. Changes nothing. */\n validate: Record<string, never>;\n /** The cookbook as `<name>.cookbook.json` text. Changes nothing. */\n export_cookbook: { name?: string };\n /** Undo an earlier change by the `undoId` its receipt carried. */\n undo: { undoId: string; force?: boolean };\n}\n\nexport type CommandName = keyof CommandInputs;\n\n/** A command as a client sends it. */\nexport type CookbookCommand = {\n [N in CommandName]: {\n name: N;\n input: CommandInputs[N];\n /** Retry key: a repeated id returns the first receipt and applies nothing. */\n id?: string;\n /** Refuse with `conflict` unless the plan is still at this revision. */\n expectedRevision?: number;\n };\n}[CommandName];\n\n/** Stable strings a client can switch on. */\nexport type CommandErrorCode =\n | 'unknown_command'\n | 'invalid_input'\n | 'not_found'\n /** A destructive command needs `replace: true` (or `force: true`). */\n | 'confirmation_required'\n /** The plan moved on since the caller looked (`expectedRevision`, undo). */\n | 'conflict'\n /** A command id was reused with a different command. */\n | 'id_reuse'\n /** An atomic batch failed; `details.index` names the command, nothing applied. */\n | 'batch_failed'\n | 'failed';\n\nexport interface CommandError {\n code: CommandErrorCode;\n message: string;\n /** Where the problem is, as a path into the input (`input.ids[1]`). */\n path?: string;\n details?: Record<string, unknown>;\n}\n\n/** What a recipe/feature command did, after `requires` ran. */\nexport interface RecipeChanges {\n added: string[];\n removed: string[];\n /** Asked to remove, but another recipe still needs them. */\n kept: string[];\n}\n\nexport interface PlanSnapshot {\n version: typeof COOKBOOK_COMMANDS_VERSION;\n /** Counts applied changes; equal histories give equal revisions. */\n revision: number;\n app: { name: string; description: string | null; cookbook: string | null };\n recipes: { id: string; label: string }[];\n features: string[];\n /** Recipe and feature ids this catalog lacks; kept on export. */\n unavailable: string[];\n settings: { currency: string; taxRate: number; paymentTerms: string };\n theme: {\n text: string;\n preset: string | null;\n primary: string | null;\n colorScheme: string;\n };\n sections: {\n id: string;\n label: string;\n hidden: boolean;\n items: { id: string; label: string; hidden: boolean }[];\n }[];\n policies: number;\n /** Undo ids still available, newest last. */\n undo: string[];\n}\n\nexport interface CommandReceipt {\n name: CommandName;\n /** The command id, when one was sent. */\n id?: string;\n summary: string;\n changed: boolean;\n revisionBefore: number;\n revisionAfter: number;\n undoId?: string;\n changes?: RecipeChanges;\n /** Non-fatal notes (an import's legacy `$schema` migration). */\n warnings?: string[];\n}\n\nexport type CommandResult =\n | {\n ok: true;\n id?: string;\n /** True when this is the stored result of an earlier run of the same id. */\n replayed?: boolean;\n snapshot: PlanSnapshot;\n receipt: CommandReceipt;\n data?: unknown;\n }\n | { ok: false; id?: string; replayed?: boolean; error: CommandError };\n\nexport interface RunOptions {\n expectedRevision?: number;\n}\n\n/** Several commands applied all-or-nothing. */\nexport interface CommandBatch {\n id?: string;\n commands: readonly unknown[];\n expectedRevision?: number;\n}\n\nexport type BatchResult =\n | {\n ok: true;\n id?: string;\n replayed?: boolean;\n snapshot: PlanSnapshot;\n receipt: {\n id?: string;\n count: number;\n changed: boolean;\n revisionBefore: number;\n revisionAfter: number;\n undoId?: string;\n };\n /** One result per command, in order. */\n results: Extract<CommandResult, { ok: true }>[];\n }\n | {\n ok: false;\n id?: string;\n replayed?: boolean;\n error: CommandError;\n /** The state is exactly as before the batch. */\n snapshot: PlanSnapshot;\n };\n\n/** One diagnostic from `validate`. */\nexport interface Diagnostic {\n severity: 'error' | 'warning';\n code: string;\n path: string;\n message: string;\n}\n\nexport interface ValidateData {\n ok: boolean;\n diagnostics: Diagnostic[];\n}\n\nexport interface ExportData {\n fileName: string;\n text: string;\n cookbook: Cookbook;\n}\n\n/** A journal row: enough to answer a retry of `id` after a restart. */\nexport interface JournalEntry {\n id: string;\n fingerprint: string;\n result: CommandResult | BatchResult;\n}\n\n/** The engine's whole state as plain JSON, to persist and resume from. */\nexport interface EngineState {\n version: typeof COOKBOOK_COMMANDS_VERSION;\n revision: number;\n /** The library cookbook the plan was last set up from, if any. */\n applied: string | null;\n cookbook: Cookbook;\n journal: JournalEntry[];\n}\n\nexport interface JsonSchema {\n [key: string]: unknown;\n}\n\n/** One command as an LLM or MCP tool definition. */\nexport interface CommandTool {\n name: CommandName;\n description: string;\n inputSchema: JsonSchema;\n}\n"],"mappings":";;;;;AAcA,IAAa,4BAA4B"}
|
|
@@ -116,6 +116,8 @@
|
|
|
116
116
|
"properties": {
|
|
117
117
|
"preset": { "type": "string" },
|
|
118
118
|
"colorScheme": { "enum": ["light", "dark", "system"] },
|
|
119
|
+
"fontFamily": { "type": "string", "minLength": 1, "description": "Host font key, independent of the preset." },
|
|
120
|
+
"customCss": { "type": "string", "maxLength": 65536, "description": "Stored CSS source text; consumers do not execute it automatically." },
|
|
119
121
|
"custom": {
|
|
120
122
|
"type": "object",
|
|
121
123
|
"required": ["primary"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cookbook/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAGR,wBAAwB,EAKzB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE1D,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,iDACgB,CAAC;AACjD,oFAAoF;AACpF,eAAO,MAAM,0BAA0B,kDACU,CAAC;AAClD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,oDAAoD;IACpD,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,8DAA8D;IAC9D,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzB,mFAAmF;QACnF,cAAc,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;KACnC,CAAC;IACF,2DAA2D;IAC3D,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CACjB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,wBAAwB,KAC/B,0BAA0B,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,0BAA0B,GAClC;IACE,EAAE,EAAE,IAAI,CAAC;IACT,0EAA0E;IAC1E,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC7C,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAC1B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cookbook/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAGR,wBAAwB,EAKzB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE1D,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,iDACgB,CAAC;AACjD,oFAAoF;AACpF,eAAO,MAAM,0BAA0B,kDACU,CAAC;AAClD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,oDAAoD;IACpD,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,8DAA8D;IAC9D,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzB,mFAAmF;QACnF,cAAc,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;KACnC,CAAC;IACF,2DAA2D;IAC3D,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CACjB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,wBAAwB,KAC/B,0BAA0B,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,0BAA0B,GAClC;IACE,EAAE,EAAE,IAAI,CAAC;IACT,0EAA0E;IAC1E,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC7C,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAC1B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAAC;AAicvE;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,uBAA4B,GACpC,kBAAkB,CA4UpB;AAED,yDAAyD;AACzD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,uBAA4B,GACpC,kBAAkB,CAmBpB"}
|
|
@@ -88,6 +88,11 @@ function checkLayout(value, diagnostics) {
|
|
|
88
88
|
function checkTheme(value, diagnostics) {
|
|
89
89
|
if (!isObject(value)) return error(diagnostics, "invalid_type", "theme", "theme must be an object");
|
|
90
90
|
if (value.preset !== void 0 && typeof value.preset !== "string") error(diagnostics, "invalid_type", "theme.preset", "theme.preset must be a string");
|
|
91
|
+
if (value.fontFamily !== void 0 && (typeof value.fontFamily !== "string" || !value.fontFamily)) error(diagnostics, "invalid_value", "theme.fontFamily", "theme.fontFamily must be a non-empty string");
|
|
92
|
+
if (value.customCss !== void 0) {
|
|
93
|
+
if (typeof value.customCss !== "string") error(diagnostics, "invalid_type", "theme.customCss", "theme.customCss must be a string");
|
|
94
|
+
else if (Array.from(value.customCss).length > 65536) error(diagnostics, "invalid_value", "theme.customCss", "theme.customCss must be at most 65,536 characters");
|
|
95
|
+
}
|
|
91
96
|
if (value.colorScheme !== void 0 && !(typeof value.colorScheme === "string" && SCHEMES.has(value.colorScheme))) error(diagnostics, "invalid_value", "theme.colorScheme", "theme.colorScheme must be light, dark or system");
|
|
92
97
|
const custom = value.custom;
|
|
93
98
|
if (custom !== void 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","names":[],"sources":["../../src/cookbook/validate.ts"],"sourcesContent":["import type {\n Cookbook,\n CookbookExposureSurface,\n CookbookLayout,\n CookbookOverviewOverride,\n CookbookOverviewWidget,\n CookbookPolicyRow,\n CookbookTheme,\n RecipeShellSlot,\n} from '@happyvertical/smrt-types';\n\nexport type { Cookbook } from '@happyvertical/smrt-types';\n\n/** What a v1 document carries in `$schema`. */\nexport const COOKBOOK_SCHEMA_URL =\n 'https://s-m-r-t.dev/schemas/cookbook/v1.json';\n/** Accepted on read (the document used to be called a blueprint); never written. */\nexport const COOKBOOK_LEGACY_SCHEMA_URL =\n 'https://s-m-r-t.dev/schemas/blueprint/v1.json';\nexport const COOKBOOK_VERSION = 1;\n\nexport interface CookbookValidateOptions {\n /**\n * The recipe ids that exist (from published manifests). When given, an id\n * outside it is an error. When absent no existence check runs: that is the\n * seam `smrt cookbook validate` fills.\n */\n recipes?: Iterable<string>;\n /** Built-in or host-registered theme preset ids. */\n themePresets?: Iterable<string>;\n /** Manifest-derived ids the cookbook layout may reference. */\n layout?: {\n sections?: Iterable<string>;\n items?: Iterable<string>;\n /** Movable shell item ids resolved by the host (`dock:`, `slot:`, or host ids). */\n placementItems?: Iterable<string>;\n };\n /** Overview page ids defined by the resolved app shell. */\n overviewIds?: Iterable<string>;\n /**\n * Validate and canonicalize one structurally valid override against its page\n * definition and widget registry. The callback is trusted host code; the\n * cookbook value remains plain data and is never executed.\n */\n validateOverview?: (\n id: string,\n override: CookbookOverviewOverride,\n ) => CookbookOverviewValidation;\n}\n\nexport interface CookbookDiagnostic {\n severity: 'error' | 'warning';\n /** Stable machine-readable category. Host validators may add their own. */\n code: string;\n /** Path into the cookbook, using the existing dotted/indexed notation. */\n path: string;\n message: string;\n}\n\nexport type CookbookOverviewValidation =\n | {\n ok: true;\n /** `null` means the canonical override is empty and should be omitted. */\n override: CookbookOverviewOverride | null;\n diagnostics?: readonly CookbookDiagnostic[];\n }\n | { ok: false; diagnostics: readonly CookbookDiagnostic[] };\n\nexport type CookbookValidation =\n | {\n ok: true;\n cookbook: Cookbook;\n warnings: string[];\n diagnostics: CookbookDiagnostic[];\n }\n | { ok: false; errors: string[]; diagnostics: CookbookDiagnostic[] };\n\nconst VISIBILITIES = new Set(['basic', 'advanced', 'hidden']);\nconst SURFACES = new Set(['api', 'mcp', 'cli']);\nconst SCHEMES = new Set(['light', 'dark', 'system']);\nconst SHELL_SLOT_VALUES = [\n 'header.start',\n 'header.center',\n 'header.end',\n 'footer.start',\n 'footer.center',\n 'footer.end',\n 'leftSidebar.header',\n 'leftSidebar.footer',\n 'rightSidebar.header',\n 'rightSidebar.footer',\n] as const satisfies readonly RecipeShellSlot[];\nconst SHELL_SLOTS = new Set<string>(SHELL_SLOT_VALUES);\n\nconst isObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\nconst isStrings = (v: unknown): v is string[] =>\n Array.isArray(v) && v.every((x) => typeof x === 'string');\nconst isStringRecord = (v: unknown, valid: (x: unknown) => boolean) =>\n isObject(v) &&\n !Object.hasOwn(v, '__proto__') &&\n Object.values(v).every(valid);\n\nconst add = (\n diagnostics: CookbookDiagnostic[],\n severity: CookbookDiagnostic['severity'],\n code: string,\n path: string,\n message: string,\n): void => {\n diagnostics.push({ severity, code, path, message });\n};\n\nconst error = (\n diagnostics: CookbookDiagnostic[],\n code: string,\n path: string,\n message: string,\n): void => add(diagnostics, 'error', code, path, message);\n\nconst warning = (\n diagnostics: CookbookDiagnostic[],\n code: string,\n path: string,\n message: string,\n): void => add(diagnostics, 'warning', code, path, message);\n\nfunction checkPolicy(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n) {\n if (!isObject(value))\n return error(diagnostics, 'invalid_type', at, `${at} must be an object`);\n for (const key of ['objectRef', 'fieldName'] as const) {\n if (typeof value[key] !== 'string' || !value[key]) {\n error(\n diagnostics,\n 'invalid_value',\n `${at}.${key}`,\n `${at}.${key} must be a non-empty string`,\n );\n }\n }\n if (value.scopeType !== 'app')\n error(\n diagnostics,\n 'invalid_value',\n `${at}.scopeType`,\n `${at}.scopeType must be \"app\"`,\n );\n for (const key of ['defaultValue', 'help', 'label'] as const) {\n const v = value[key];\n if (v !== undefined && v !== null && typeof v !== 'string') {\n error(\n diagnostics,\n 'invalid_type',\n `${at}.${key}`,\n `${at}.${key} must be a string or null`,\n );\n }\n }\n const vis = value.visibility;\n if (vis != null && !(typeof vis === 'string' && VISIBILITIES.has(vis))) {\n error(\n diagnostics,\n 'invalid_value',\n `${at}.visibility`,\n `${at}.visibility must be basic, advanced or hidden`,\n );\n }\n const order = value.displayOrder;\n if (order != null && !(typeof order === 'number' && Number.isFinite(order))) {\n error(\n diagnostics,\n 'invalid_value',\n `${at}.displayOrder`,\n `${at}.displayOrder must be a finite number`,\n );\n }\n if (value.locked != null && typeof value.locked !== 'boolean') {\n error(\n diagnostics,\n 'invalid_type',\n `${at}.locked`,\n `${at}.locked must be a boolean`,\n );\n }\n}\n\nfunction checkLayout(value: unknown, diagnostics: CookbookDiagnostic[]) {\n if (!isObject(value) || value.version !== 1) {\n return error(\n diagnostics,\n 'invalid_value',\n 'layout',\n 'layout must be an object with version 1',\n );\n }\n for (const key of ['sectionOrder', 'hidden'] as const) {\n if (value[key] !== undefined && !isStrings(value[key])) {\n error(\n diagnostics,\n 'invalid_type',\n `layout.${key}`,\n `layout.${key} must be a list of strings`,\n );\n }\n }\n for (const key of ['itemOrder', 'moved', 'placements'] as const) {\n const v = value[key];\n if (v === undefined) continue;\n const ok =\n key === 'itemOrder'\n ? isStringRecord(v, isStrings)\n : isStringRecord(v, (x) => typeof x === 'string');\n if (!ok) {\n error(\n diagnostics,\n 'invalid_type',\n `layout.${key}`,\n key === 'itemOrder'\n ? 'layout.itemOrder must map ids to lists of strings'\n : `layout.${key} must map ids to strings`,\n );\n }\n }\n if (isObject(value.placements)) {\n for (const [id, slot] of Object.entries(value.placements)) {\n if (typeof slot === 'string' && !SHELL_SLOTS.has(slot)) {\n error(\n diagnostics,\n 'unknown_layout_slot',\n `layout.placements.${id}`,\n `Unknown shell slot \"${slot}\".`,\n );\n }\n }\n }\n for (const key of ['sections', 'items', 'panels'] as const) {\n if (\n value[key] !== undefined &&\n !isStringRecord(value[key], (x) => isObject(x))\n ) {\n error(\n diagnostics,\n 'invalid_type',\n `layout.${key}`,\n `layout.${key} must map ids to objects`,\n );\n }\n }\n const custom = value.customSections;\n if (\n custom !== undefined &&\n !(\n Array.isArray(custom) &&\n custom.every(\n (c) =>\n isObject(c) &&\n typeof c.id === 'string' &&\n typeof c.label === 'string',\n )\n )\n ) {\n error(\n diagnostics,\n 'invalid_type',\n 'layout.customSections',\n 'layout.customSections must be a list of { id, label }',\n );\n }\n}\n\nfunction checkTheme(value: unknown, diagnostics: CookbookDiagnostic[]) {\n if (!isObject(value))\n return error(\n diagnostics,\n 'invalid_type',\n 'theme',\n 'theme must be an object',\n );\n if (value.preset !== undefined && typeof value.preset !== 'string') {\n error(\n diagnostics,\n 'invalid_type',\n 'theme.preset',\n 'theme.preset must be a string',\n );\n }\n if (\n value.colorScheme !== undefined &&\n !(typeof value.colorScheme === 'string' && SCHEMES.has(value.colorScheme))\n ) {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.colorScheme',\n 'theme.colorScheme must be light, dark or system',\n );\n }\n const custom = value.custom;\n if (custom !== undefined) {\n if (!isObject(custom) || typeof custom.primary !== 'string') {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.custom',\n 'theme.custom must be an object with a string primary',\n );\n } else if (!/^#[0-9a-fA-F]{6}$/.test(custom.primary)) {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.custom.primary',\n 'theme.custom.primary must be a hex colour like #c2410c',\n );\n } else if (\n custom.fontFamily !== undefined &&\n typeof custom.fontFamily !== 'string'\n ) {\n error(\n diagnostics,\n 'invalid_type',\n 'theme.custom.fontFamily',\n 'theme.custom.fontFamily must be a string',\n );\n }\n }\n}\n\nconst OVERVIEW_ID = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,95}$/;\nconst WIDGET_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;\nconst WIDGET_TYPE = /^[a-z][a-z0-9-]{0,31}$/;\n\nconst isOptionValue = (value: unknown) =>\n value === null ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n typeof value === 'boolean';\n\nfunction normalizeOverviewWidget(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n): CookbookOverviewWidget | null {\n if (!isObject(value)) {\n warning(diagnostics, 'malformed_overview_widget', at, `${at} was dropped`);\n return null;\n }\n const valid = [\n typeof value.id === 'string' && WIDGET_ID.test(value.id),\n typeof value.type === 'string' && WIDGET_TYPE.test(value.type),\n typeof value.span === 'number' &&\n Number.isInteger(value.span) &&\n value.span >= 1 &&\n value.span <= 4,\n isObject(value.options) &&\n Object.values(value.options).every(isOptionValue),\n value.version === undefined ||\n (typeof value.version === 'number' &&\n Number.isInteger(value.version) &&\n value.version >= 1),\n ];\n if (valid.every(Boolean)) return value as unknown as CookbookOverviewWidget;\n warning(\n diagnostics,\n 'malformed_overview_widget',\n at,\n `${at} was dropped because it is not a valid overview widget`,\n );\n return null;\n}\n\nfunction normalizeOverviewPatch(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n): NonNullable<CookbookOverviewOverride['changed']>[string] | null {\n if (!isObject(value)) {\n warning(diagnostics, 'malformed_overview_change', at, `${at} was dropped`);\n return null;\n }\n const valid =\n (value.span === undefined ||\n (typeof value.span === 'number' &&\n Number.isInteger(value.span) &&\n value.span >= 1 &&\n value.span <= 4)) &&\n (value.options === undefined ||\n (isObject(value.options) &&\n Object.values(value.options).every(isOptionValue))) &&\n (value.version === undefined ||\n (typeof value.version === 'number' &&\n Number.isInteger(value.version) &&\n value.version >= 1));\n if (valid) {\n return value as NonNullable<CookbookOverviewOverride['changed']>[string];\n }\n warning(\n diagnostics,\n 'malformed_overview_change',\n at,\n `${at} was dropped because it is not a valid widget change`,\n );\n return null;\n}\n\nfunction normalizeOverview(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n): CookbookOverviewOverride | null {\n if (!isObject(value) || value.version !== 1) {\n warning(\n diagnostics,\n 'malformed_overview',\n at,\n `${at} was dropped because it must be an object with version 1`,\n );\n return null;\n }\n const override = {\n ...value,\n version: 1,\n } as unknown as CookbookOverviewOverride;\n for (const key of ['order', 'removed'] as const) {\n if (value[key] !== undefined && !isStrings(value[key])) {\n warning(\n diagnostics,\n 'malformed_overview_list',\n `${at}.${key}`,\n `${at}.${key} was dropped because it must be a list of strings`,\n );\n delete override[key];\n } else if (value[key] !== undefined) {\n override[key] = [...new Set(value[key])];\n }\n }\n if (value.added !== undefined) {\n if (!Array.isArray(value.added)) {\n warning(\n diagnostics,\n 'malformed_overview_widgets',\n `${at}.added`,\n `${at}.added was dropped because it must be a list of widgets`,\n );\n delete override.added;\n } else {\n const added = value.added.flatMap((widget, index) => {\n const normalized = normalizeOverviewWidget(\n widget,\n `${at}.added[${index}]`,\n diagnostics,\n );\n return normalized ? [normalized] : [];\n });\n if (added.length) override.added = added;\n else delete override.added;\n }\n }\n if (value.changed !== undefined) {\n if (!isObject(value.changed) || Object.hasOwn(value.changed, '__proto__')) {\n warning(\n diagnostics,\n 'malformed_overview_changes',\n `${at}.changed`,\n `${at}.changed was dropped because it must map widget ids to objects`,\n );\n delete override.changed;\n } else {\n const changed: NonNullable<CookbookOverviewOverride['changed']> = {};\n for (const [id, patch] of Object.entries(value.changed)) {\n if (!WIDGET_ID.test(id)) {\n warning(\n diagnostics,\n 'malformed_overview_change',\n `${at}.changed.${id}`,\n `${at}.changed.${id} was dropped because its widget id is invalid`,\n );\n continue;\n }\n const normalized = normalizeOverviewPatch(\n patch,\n `${at}.changed.${id}`,\n diagnostics,\n );\n if (normalized) changed[id] = normalized;\n }\n if (Object.keys(changed).length) override.changed = changed;\n else delete override.changed;\n }\n }\n return override;\n}\n\n/**\n * Structural validation of an unknown value (parsed JSON). Returns the\n * normalised document (legacy `$schema` rewritten, `features` defaulted,\n * recipes sorted and unique) or every problem found, each a readable line\n * naming the path. Unknown top-level keys are kept (additive fields in v1).\n *\n * Deep checks use optional host-supplied catalogs. A structurally valid\n * cookbook remains portable when those catalogs are absent. Invalid overview\n * entries are dropped and reported, following the v1 overview contract.\n */\nexport function validateCookbook(\n input: unknown,\n options: CookbookValidateOptions = {},\n): CookbookValidation {\n const diagnostics: CookbookDiagnostic[] = [];\n if (!isObject(input)) {\n error(diagnostics, 'invalid_type', '', 'expected a JSON object');\n return { ok: false, errors: ['expected a JSON object'], diagnostics };\n }\n\n const { $schema, version } = input;\n if (\n $schema !== undefined &&\n $schema !== COOKBOOK_SCHEMA_URL &&\n $schema !== COOKBOOK_LEGACY_SCHEMA_URL\n ) {\n error(\n diagnostics,\n 'invalid_schema',\n '$schema',\n `$schema must be ${COOKBOOK_SCHEMA_URL}`,\n );\n }\n if (version === undefined) {\n error(diagnostics, 'missing_field', 'version', 'version is required');\n } else if (typeof version !== 'number' || !Number.isInteger(version)) {\n error(\n diagnostics,\n 'invalid_type',\n 'version',\n 'version must be a whole number',\n );\n } else if (version > COOKBOOK_VERSION) {\n error(\n diagnostics,\n 'unsupported_version',\n 'version',\n `version ${version} is newer than this reader understands (${COOKBOOK_VERSION})`,\n );\n } else if (version !== COOKBOOK_VERSION) {\n error(\n diagnostics,\n 'unsupported_version',\n 'version',\n `version ${version} is not supported`,\n );\n }\n if ($schema === COOKBOOK_LEGACY_SCHEMA_URL) {\n warning(\n diagnostics,\n 'migrated_schema',\n '$schema',\n '$schema uses the legacy blueprint URL; migrated',\n );\n }\n\n for (const key of ['name', 'description'] as const) {\n if (input[key] !== undefined && typeof input[key] !== 'string') {\n error(diagnostics, 'invalid_type', key, `${key} must be a string`);\n }\n }\n\n let recipes: string[] = [];\n if (!isStrings(input.recipes)) {\n error(\n diagnostics,\n 'invalid_type',\n 'recipes',\n 'recipes must be a list of recipe ids',\n );\n } else {\n recipes = [...new Set(input.recipes)].sort();\n if (options.recipes) {\n const known = new Set(options.recipes);\n for (const id of recipes) {\n if (!known.has(id))\n error(\n diagnostics,\n 'unknown_recipe',\n `recipes[${input.recipes.indexOf(id)}]`,\n `recipes: unknown recipe \"${id}\"`,\n );\n }\n }\n }\n\n let features: string[] = [];\n if (input.features !== undefined) {\n if (!isStrings(input.features)) {\n error(\n diagnostics,\n 'invalid_type',\n 'features',\n 'features must be a list of model names',\n );\n } else {\n features = [...new Set(input.features)].sort();\n if (features.length !== input.features.length) {\n warning(\n diagnostics,\n 'duplicate_value',\n 'features',\n 'features listed a model more than once; deduplicated',\n );\n }\n }\n }\n\n const policies: CookbookPolicyRow[] = [];\n if (!Array.isArray(input.policies)) {\n error(diagnostics, 'invalid_type', 'policies', 'policies must be a list');\n } else {\n for (const [i, row] of input.policies.entries()) {\n checkPolicy(row, `policies[${i}]`, diagnostics);\n policies.push(row as CookbookPolicyRow);\n }\n }\n\n if (input.exposure !== undefined) {\n const ok = isStringRecord(\n input.exposure,\n (s) => isStrings(s) && s.every((x) => SURFACES.has(x)),\n );\n if (!ok)\n error(\n diagnostics,\n 'invalid_value',\n 'exposure',\n 'exposure must map model names to lists of api, mcp or cli',\n );\n }\n if (input.layout !== undefined) checkLayout(input.layout, diagnostics);\n if (input.theme !== undefined) checkTheme(input.theme, diagnostics);\n\n if (isObject(input.theme) && typeof input.theme.preset === 'string') {\n const presets = options.themePresets\n ? new Set(options.themePresets)\n : undefined;\n if (presets && !presets.has(input.theme.preset)) {\n error(\n diagnostics,\n 'unknown_theme_preset',\n 'theme.preset',\n `Unknown theme preset \"${input.theme.preset}\".`,\n );\n }\n }\n\n if (isObject(input.layout) && input.layout.version === 1 && options.layout) {\n const sections = options.layout.sections\n ? new Set(options.layout.sections)\n : undefined;\n const items = options.layout.items\n ? new Set(options.layout.items)\n : undefined;\n const placementItems = options.layout.placementItems\n ? new Set(options.layout.placementItems)\n : undefined;\n if (sections && Array.isArray(input.layout.customSections)) {\n for (const section of input.layout.customSections) {\n if (isObject(section) && typeof section.id === 'string') {\n sections.add(section.id);\n }\n }\n }\n const known = (id: string, kind: 'section' | 'item' | 'either') =>\n kind === 'section'\n ? sections?.has(id) !== false\n : kind === 'item'\n ? items?.has(id) !== false\n : sections?.has(id) === true ||\n items?.has(id) === true ||\n (!sections && !items);\n const check = (\n id: unknown,\n path: string,\n kind: 'section' | 'item' | 'either',\n ) => {\n if (typeof id === 'string' && !known(id, kind)) {\n error(\n diagnostics,\n 'unknown_layout_id',\n path,\n `Unknown layout ${kind === 'either' ? 'section or item' : kind} \"${id}\".`,\n );\n }\n };\n if (isStrings(input.layout.sectionOrder)) {\n input.layout.sectionOrder.forEach((id, index) => {\n check(id, `layout.sectionOrder[${index}]`, 'section');\n });\n }\n if (isStrings(input.layout.hidden)) {\n input.layout.hidden.forEach((id, index) => {\n check(id, `layout.hidden[${index}]`, 'either');\n });\n }\n if (isObject(input.layout.itemOrder)) {\n for (const [section, order] of Object.entries(input.layout.itemOrder)) {\n check(section, `layout.itemOrder.${section}`, 'section');\n if (isStrings(order)) {\n order.forEach((id, index) => {\n check(id, `layout.itemOrder.${section}[${index}]`, 'item');\n });\n }\n }\n }\n if (isObject(input.layout.moved)) {\n for (const [id, section] of Object.entries(input.layout.moved)) {\n check(id, `layout.moved.${id}`, 'item');\n check(section, `layout.moved.${id}`, 'section');\n }\n }\n for (const key of ['sections', 'items'] as const) {\n if (!isObject(input.layout[key])) continue;\n for (const id of Object.keys(input.layout[key])) {\n check(\n id,\n `layout.${key}.${id}`,\n key === 'sections' ? 'section' : 'item',\n );\n }\n }\n if (isObject(input.layout.placements)) {\n for (const id of Object.keys(input.layout.placements)) {\n if (placementItems && !placementItems.has(id)) {\n error(\n diagnostics,\n 'unknown_layout_id',\n `layout.placements.${id}`,\n `Unknown layout placement item \"${id}\".`,\n );\n }\n }\n }\n }\n\n const overviews: Record<string, CookbookOverviewOverride> = {};\n if (input.overviews !== undefined) {\n if (\n !isObject(input.overviews) ||\n Object.hasOwn(input.overviews, '__proto__')\n ) {\n error(\n diagnostics,\n 'invalid_type',\n 'overviews',\n 'overviews must map overview ids to overrides',\n );\n } else {\n const overviewIds = options.overviewIds\n ? new Set(options.overviewIds)\n : undefined;\n for (const [id, value] of Object.entries(input.overviews)) {\n const at = `overviews.${id}`;\n if (!OVERVIEW_ID.test(id)) {\n warning(\n diagnostics,\n 'invalid_overview_id',\n at,\n `${at} was dropped because its overview id is invalid`,\n );\n continue;\n }\n if (overviewIds && !overviewIds.has(id)) {\n warning(\n diagnostics,\n 'unknown_overview',\n at,\n `Unknown overview \"${id}\" was dropped.`,\n );\n continue;\n }\n const override = normalizeOverview(value, at, diagnostics);\n if (!override) continue;\n if (!options.validateOverview) {\n overviews[id] = override;\n continue;\n }\n let checked: CookbookOverviewValidation;\n try {\n checked = options.validateOverview(id, override);\n } catch {\n warning(\n diagnostics,\n 'overview_validation_failed',\n at,\n `Overview \"${id}\" was dropped because its host validator failed.`,\n );\n continue;\n }\n for (const issue of checked.diagnostics ?? []) {\n warning(diagnostics, issue.code, issue.path || at, issue.message);\n }\n if (!checked.ok && checked.diagnostics.length === 0) {\n warning(\n diagnostics,\n 'overview_rejected',\n at,\n `Overview \"${id}\" was rejected by its host validator and dropped.`,\n );\n }\n if (checked.ok && checked.override) overviews[id] = checked.override;\n }\n }\n }\n\n const errors = diagnostics\n .filter((diagnostic) => diagnostic.severity === 'error')\n .map((diagnostic) => diagnostic.message);\n if (errors.length) return { ok: false, errors, diagnostics };\n\n const cookbook = {\n ...input,\n $schema: COOKBOOK_SCHEMA_URL,\n version: COOKBOOK_VERSION,\n recipes,\n features,\n policies,\n } as unknown as Cookbook;\n if (input.exposure !== undefined) {\n cookbook.exposure = input.exposure as Record<\n string,\n CookbookExposureSurface[]\n >;\n }\n if (input.layout !== undefined)\n cookbook.layout = input.layout as CookbookLayout;\n if (input.theme !== undefined) cookbook.theme = input.theme as CookbookTheme;\n delete cookbook.overviews;\n if (Object.keys(overviews).length) cookbook.overviews = overviews;\n const warnings = diagnostics\n .filter((diagnostic) => diagnostic.severity === 'warning')\n .map((diagnostic) => diagnostic.message);\n return { ok: true, cookbook, warnings, diagnostics };\n}\n\n/** Parse JSON text, then {@link validateCookbook} it. */\nexport function parseCookbookText(\n text: string,\n options: CookbookValidateOptions = {},\n): CookbookValidation {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return {\n ok: false,\n errors: ['not valid JSON'],\n diagnostics: [\n {\n severity: 'error',\n code: 'invalid_json',\n path: '',\n message: 'not valid JSON',\n },\n ],\n };\n }\n return validateCookbook(value, options);\n}\n"],"mappings":";;AAcA,IAAa,sBACX;;AAEF,IAAa,6BACX;AACF,IAAa,mBAAmB;AA0DhC,IAAM,+BAAe,IAAI,IAAI;CAAC;CAAS;CAAY;AAAQ,CAAC;AAC5D,IAAM,2BAAW,IAAI,IAAI;CAAC;CAAO;CAAO;AAAK,CAAC;AAC9C,IAAM,0BAAU,IAAI,IAAI;CAAC;CAAS;CAAQ;AAAQ,CAAC;AAanD,IAAM,8BAAc,IAAI,IAAY;CAXlC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAEkC,CAAiB;AAErD,IAAM,YAAY,MAChB,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AACzD,IAAM,aAAa,MACjB,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,MAAM,OAAO,MAAM,QAAQ;AAC1D,IAAM,kBAAkB,GAAY,UAClC,SAAS,CAAC,KACV,CAAC,OAAO,OAAO,GAAG,WAAW,KAC7B,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,KAAK;AAE9B,IAAM,OACJ,aACA,UACA,MACA,MACA,YACS;CACT,YAAY,KAAK;EAAE;EAAU;EAAM;EAAM;CAAQ,CAAC;AACpD;AAEA,IAAM,SACJ,aACA,MACA,MACA,YACS,IAAI,aAAa,SAAS,MAAM,MAAM,OAAO;AAExD,IAAM,WACJ,aACA,MACA,MACA,YACS,IAAI,aAAa,WAAW,MAAM,MAAM,OAAO;AAE1D,SAAS,YACP,OACA,IACA,aACA;CACA,IAAI,CAAC,SAAS,KAAK,GACjB,OAAO,MAAM,aAAa,gBAAgB,IAAI,GAAG,GAAG,mBAAmB;CACzE,KAAK,MAAM,OAAO,CAAC,aAAa,WAAW,GACzC,IAAI,OAAO,MAAM,SAAS,YAAY,CAAC,MAAM,MAC3C,MACE,aACA,iBACA,GAAG,GAAG,GAAG,OACT,GAAG,GAAG,GAAG,IAAI,4BACf;CAGJ,IAAI,MAAM,cAAc,OACtB,MACE,aACA,iBACA,GAAG,GAAG,aACN,GAAG,GAAG,yBACR;CACF,KAAK,MAAM,OAAO;EAAC;EAAgB;EAAQ;CAAO,GAAY;EAC5D,MAAM,IAAI,MAAM;EAChB,IAAI,MAAM,KAAA,KAAa,MAAM,QAAQ,OAAO,MAAM,UAChD,MACE,aACA,gBACA,GAAG,GAAG,GAAG,OACT,GAAG,GAAG,GAAG,IAAI,0BACf;CAEJ;CACA,MAAM,MAAM,MAAM;CAClB,IAAI,OAAO,QAAQ,EAAE,OAAO,QAAQ,YAAY,aAAa,IAAI,GAAG,IAClE,MACE,aACA,iBACA,GAAG,GAAG,cACN,GAAG,GAAG,8CACR;CAEF,MAAM,QAAQ,MAAM;CACpB,IAAI,SAAS,QAAQ,EAAE,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IACvE,MACE,aACA,iBACA,GAAG,GAAG,gBACN,GAAG,GAAG,sCACR;CAEF,IAAI,MAAM,UAAU,QAAQ,OAAO,MAAM,WAAW,WAClD,MACE,aACA,gBACA,GAAG,GAAG,UACN,GAAG,GAAG,0BACR;AAEJ;AAEA,SAAS,YAAY,OAAgB,aAAmC;CACtE,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,YAAY,GACxC,OAAO,MACL,aACA,iBACA,UACA,yCACF;CAEF,KAAK,MAAM,OAAO,CAAC,gBAAgB,QAAQ,GACzC,IAAI,MAAM,SAAS,KAAA,KAAa,CAAC,UAAU,MAAM,IAAI,GACnD,MACE,aACA,gBACA,UAAU,OACV,UAAU,IAAI,2BAChB;CAGJ,KAAK,MAAM,OAAO;EAAC;EAAa;EAAS;CAAY,GAAY;EAC/D,MAAM,IAAI,MAAM;EAChB,IAAI,MAAM,KAAA,GAAW;EAKrB,IAAI,EAHF,QAAQ,cACJ,eAAe,GAAG,SAAS,IAC3B,eAAe,IAAI,MAAM,OAAO,MAAM,QAAQ,IAElD,MACE,aACA,gBACA,UAAU,OACV,QAAQ,cACJ,sDACA,UAAU,IAAI,yBACpB;CAEJ;CACA,IAAI,SAAS,MAAM,UAAU;OACtB,MAAM,CAAC,IAAI,SAAS,OAAO,QAAQ,MAAM,UAAU,GACtD,IAAI,OAAO,SAAS,YAAY,CAAC,YAAY,IAAI,IAAI,GACnD,MACE,aACA,uBACA,qBAAqB,MACrB,uBAAuB,KAAK,GAC9B;CAAA;CAIN,KAAK,MAAM,OAAO;EAAC;EAAY;EAAS;CAAQ,GAC9C,IACE,MAAM,SAAS,KAAA,KACf,CAAC,eAAe,MAAM,OAAO,MAAM,SAAS,CAAC,CAAC,GAE9C,MACE,aACA,gBACA,UAAU,OACV,UAAU,IAAI,yBAChB;CAGJ,MAAM,SAAS,MAAM;CACrB,IACE,WAAW,KAAA,KACX,EACE,MAAM,QAAQ,MAAM,KACpB,OAAO,OACJ,MACC,SAAS,CAAC,KACV,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,UAAU,QACvB,IAGF,MACE,aACA,gBACA,yBACA,uDACF;AAEJ;AAEA,SAAS,WAAW,OAAgB,aAAmC;CACrE,IAAI,CAAC,SAAS,KAAK,GACjB,OAAO,MACL,aACA,gBACA,SACA,yBACF;CACF,IAAI,MAAM,WAAW,KAAA,KAAa,OAAO,MAAM,WAAW,UACxD,MACE,aACA,gBACA,gBACA,+BACF;CAEF,IACE,MAAM,gBAAgB,KAAA,KACtB,EAAE,OAAO,MAAM,gBAAgB,YAAY,QAAQ,IAAI,MAAM,WAAW,IAExE,MACE,aACA,iBACA,qBACA,iDACF;CAEF,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,KAAA;MACT,CAAC,SAAS,MAAM,KAAK,OAAO,OAAO,YAAY,UACjD,MACE,aACA,iBACA,gBACA,sDACF;OACK,IAAI,CAAC,oBAAoB,KAAK,OAAO,OAAO,GACjD,MACE,aACA,iBACA,wBACA,wDACF;OACK,IACL,OAAO,eAAe,KAAA,KACtB,OAAO,OAAO,eAAe,UAE7B,MACE,aACA,gBACA,2BACA,0CACF;CAAA;AAGN;AAEA,IAAM,cAAc;AACpB,IAAM,YAAY;AAClB,IAAM,cAAc;AAEpB,IAAM,iBAAiB,UACrB,UAAU,QACV,OAAO,UAAU,YAChB,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KACnD,OAAO,UAAU;AAEnB,SAAS,wBACP,OACA,IACA,aAC+B;CAC/B,IAAI,CAAC,SAAS,KAAK,GAAG;EACpB,QAAQ,aAAa,6BAA6B,IAAI,GAAG,GAAG,aAAa;EACzE,OAAO;CACT;CAeA,IAAI;EAbF,OAAO,MAAM,OAAO,YAAY,UAAU,KAAK,MAAM,EAAE;EACvD,OAAO,MAAM,SAAS,YAAY,YAAY,KAAK,MAAM,IAAI;EAC7D,OAAO,MAAM,SAAS,YACpB,OAAO,UAAU,MAAM,IAAI,KAC3B,MAAM,QAAQ,KACd,MAAM,QAAQ;EAChB,SAAS,MAAM,OAAO,KACpB,OAAO,OAAO,MAAM,OAAO,CAAC,CAAC,MAAM,aAAa;EAClD,MAAM,YAAY,KAAA,KACf,OAAO,MAAM,YAAY,YACxB,OAAO,UAAU,MAAM,OAAO,KAC9B,MAAM,WAAW;CAEnB,CAAA,CAAM,MAAM,OAAO,GAAG,OAAO;CACjC,QACE,aACA,6BACA,IACA,GAAG,GAAG,uDACR;CACA,OAAO;AACT;AAEA,SAAS,uBACP,OACA,IACA,aACiE;CACjE,IAAI,CAAC,SAAS,KAAK,GAAG;EACpB,QAAQ,aAAa,6BAA6B,IAAI,GAAG,GAAG,aAAa;EACzE,OAAO;CACT;CAcA,KAZG,MAAM,SAAS,KAAA,KACb,OAAO,MAAM,SAAS,YACrB,OAAO,UAAU,MAAM,IAAI,KAC3B,MAAM,QAAQ,KACd,MAAM,QAAQ,OACjB,MAAM,YAAY,KAAA,KAChB,SAAS,MAAM,OAAO,KACrB,OAAO,OAAO,MAAM,OAAO,CAAC,CAAC,MAAM,aAAa,OACnD,MAAM,YAAY,KAAA,KAChB,OAAO,MAAM,YAAY,YACxB,OAAO,UAAU,MAAM,OAAO,KAC9B,MAAM,WAAW,IAErB,OAAO;CAET,QACE,aACA,6BACA,IACA,GAAG,GAAG,qDACR;CACA,OAAO;AACT;AAEA,SAAS,kBACP,OACA,IACA,aACiC;CACjC,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,YAAY,GAAG;EAC3C,QACE,aACA,sBACA,IACA,GAAG,GAAG,yDACR;EACA,OAAO;CACT;CACA,MAAM,WAAW;EACf,GAAG;EACH,SAAS;CACX;CACA,KAAK,MAAM,OAAO,CAAC,SAAS,SAAS,GACnC,IAAI,MAAM,SAAS,KAAA,KAAa,CAAC,UAAU,MAAM,IAAI,GAAG;EACtD,QACE,aACA,2BACA,GAAG,GAAG,GAAG,OACT,GAAG,GAAG,GAAG,IAAI,kDACf;EACA,OAAO,SAAS;CAClB,OAAO,IAAI,MAAM,SAAS,KAAA,GACxB,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC;CAG3C,IAAI,MAAM,UAAU,KAAA,GAClB,IAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG;EAC/B,QACE,aACA,8BACA,GAAG,GAAG,SACN,GAAG,GAAG,wDACR;EACA,OAAO,SAAS;CAClB,OAAO;EACL,MAAM,QAAQ,MAAM,MAAM,SAAS,QAAQ,UAAU;GACnD,MAAM,aAAa,wBACjB,QACA,GAAG,GAAG,SAAS,MAAM,IACrB,WACF;GACA,OAAO,aAAa,CAAC,UAAU,IAAI,CAAC;EACtC,CAAC;EACD,IAAI,MAAM,QAAQ,SAAS,QAAQ;OAC9B,OAAO,SAAS;CACvB;CAEF,IAAI,MAAM,YAAY,KAAA,GACpB,IAAI,CAAC,SAAS,MAAM,OAAO,KAAK,OAAO,OAAO,MAAM,SAAS,WAAW,GAAG;EACzE,QACE,aACA,8BACA,GAAG,GAAG,WACN,GAAG,GAAG,+DACR;EACA,OAAO,SAAS;CAClB,OAAO;EACL,MAAM,UAA4D,CAAC;EACnE,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,MAAM,OAAO,GAAG;GACvD,IAAI,CAAC,UAAU,KAAK,EAAE,GAAG;IACvB,QACE,aACA,6BACA,GAAG,GAAG,WAAW,MACjB,GAAG,GAAG,WAAW,GAAG,8CACtB;IACA;GACF;GACA,MAAM,aAAa,uBACjB,OACA,GAAG,GAAG,WAAW,MACjB,WACF;GACA,IAAI,YAAY,QAAQ,MAAM;EAChC;EACA,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,QAAQ,SAAS,UAAU;OAC/C,OAAO,SAAS;CACvB;CAEF,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,iBACd,OACA,UAAmC,CAAC,GAChB;CACpB,MAAM,cAAoC,CAAC;CAC3C,IAAI,CAAC,SAAS,KAAK,GAAG;EACpB,MAAM,aAAa,gBAAgB,IAAI,wBAAwB;EAC/D,OAAO;GAAE,IAAI;GAAO,QAAQ,CAAC,wBAAwB;GAAG;EAAY;CACtE;CAEA,MAAM,EAAE,SAAS,YAAY;CAC7B,IACE,YAAY,KAAA,KACZ,YAAA,kDACA,YAAA,iDAEA,MACE,aACA,kBACA,WACA,mBAAmB,qBACrB;CAEF,IAAI,YAAY,KAAA,GACd,MAAM,aAAa,iBAAiB,WAAW,qBAAqB;MAC/D,IAAI,OAAO,YAAY,YAAY,CAAC,OAAO,UAAU,OAAO,GACjE,MACE,aACA,gBACA,WACA,gCACF;MACK,IAAI,UAAA,GACT,MACE,aACA,uBACA,WACA,WAAW,QAAQ,2CACrB;MACK,IAAI,YAAA,GACT,MACE,aACA,uBACA,WACA,WAAW,QAAQ,kBACrB;CAEF,IAAI,YAAA,iDACF,QACE,aACA,mBACA,WACA,iDACF;CAGF,KAAK,MAAM,OAAO,CAAC,QAAQ,aAAa,GACtC,IAAI,MAAM,SAAS,KAAA,KAAa,OAAO,MAAM,SAAS,UACpD,MAAM,aAAa,gBAAgB,KAAK,GAAG,IAAI,kBAAkB;CAIrE,IAAI,UAAoB,CAAC;CACzB,IAAI,CAAC,UAAU,MAAM,OAAO,GAC1B,MACE,aACA,gBACA,WACA,sCACF;MACK;EACL,UAAU,CAAC,GAAG,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK;EAC3C,IAAI,QAAQ,SAAS;GACnB,MAAM,QAAQ,IAAI,IAAI,QAAQ,OAAO;GACrC,KAAK,MAAM,MAAM,SACf,IAAI,CAAC,MAAM,IAAI,EAAE,GACf,MACE,aACA,kBACA,WAAW,MAAM,QAAQ,QAAQ,EAAE,EAAE,IACrC,4BAA4B,GAAG,EACjC;EAEN;CACF;CAEA,IAAI,WAAqB,CAAC;CAC1B,IAAI,MAAM,aAAa,KAAA,GACrB,IAAI,CAAC,UAAU,MAAM,QAAQ,GAC3B,MACE,aACA,gBACA,YACA,wCACF;MACK;EACL,WAAW,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK;EAC7C,IAAI,SAAS,WAAW,MAAM,SAAS,QACrC,QACE,aACA,mBACA,YACA,sDACF;CAEJ;CAGF,MAAM,WAAgC,CAAC;CACvC,IAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,GAC/B,MAAM,aAAa,gBAAgB,YAAY,yBAAyB;MAExE,KAAK,MAAM,CAAC,GAAG,QAAQ,MAAM,SAAS,QAAQ,GAAG;EAC/C,YAAY,KAAK,YAAY,EAAE,IAAI,WAAW;EAC9C,SAAS,KAAK,GAAwB;CACxC;CAGF,IAAI,MAAM,aAAa,KAAA;MAKjB,CAJO,eACT,MAAM,WACL,MAAM,UAAU,CAAC,KAAK,EAAE,OAAO,MAAM,SAAS,IAAI,CAAC,CAAC,CAElD,GACH,MACE,aACA,iBACA,YACA,2DACF;CAAA;CAEJ,IAAI,MAAM,WAAW,KAAA,GAAW,YAAY,MAAM,QAAQ,WAAW;CACrE,IAAI,MAAM,UAAU,KAAA,GAAW,WAAW,MAAM,OAAO,WAAW;CAElE,IAAI,SAAS,MAAM,KAAK,KAAK,OAAO,MAAM,MAAM,WAAW,UAAU;EACnE,MAAM,UAAU,QAAQ,eACpB,IAAI,IAAI,QAAQ,YAAY,IAC5B,KAAA;EACJ,IAAI,WAAW,CAAC,QAAQ,IAAI,MAAM,MAAM,MAAM,GAC5C,MACE,aACA,wBACA,gBACA,yBAAyB,MAAM,MAAM,OAAO,GAC9C;CAEJ;CAEA,IAAI,SAAS,MAAM,MAAM,KAAK,MAAM,OAAO,YAAY,KAAK,QAAQ,QAAQ;EAC1E,MAAM,WAAW,QAAQ,OAAO,WAC5B,IAAI,IAAI,QAAQ,OAAO,QAAQ,IAC/B,KAAA;EACJ,MAAM,QAAQ,QAAQ,OAAO,QACzB,IAAI,IAAI,QAAQ,OAAO,KAAK,IAC5B,KAAA;EACJ,MAAM,iBAAiB,QAAQ,OAAO,iBAClC,IAAI,IAAI,QAAQ,OAAO,cAAc,IACrC,KAAA;EACJ,IAAI,YAAY,MAAM,QAAQ,MAAM,OAAO,cAAc;QAClD,MAAM,WAAW,MAAM,OAAO,gBACjC,IAAI,SAAS,OAAO,KAAK,OAAO,QAAQ,OAAO,UAC7C,SAAS,IAAI,QAAQ,EAAE;EAAA;EAI7B,MAAM,SAAS,IAAY,SACzB,SAAS,YACL,UAAU,IAAI,EAAE,MAAM,QACtB,SAAS,SACP,OAAO,IAAI,EAAE,MAAM,QACnB,UAAU,IAAI,EAAE,MAAM,QACtB,OAAO,IAAI,EAAE,MAAM,QAClB,CAAC,YAAY,CAAC;EACvB,MAAM,SACJ,IACA,MACA,SACG;GACH,IAAI,OAAO,OAAO,YAAY,CAAC,MAAM,IAAI,IAAI,GAC3C,MACE,aACA,qBACA,MACA,kBAAkB,SAAS,WAAW,oBAAoB,KAAK,IAAI,GAAG,GACxE;EAEJ;EACA,IAAI,UAAU,MAAM,OAAO,YAAY,GACrC,MAAM,OAAO,aAAa,SAAS,IAAI,UAAU;GAC/C,MAAM,IAAI,uBAAuB,MAAM,IAAI,SAAS;EACtD,CAAC;EAEH,IAAI,UAAU,MAAM,OAAO,MAAM,GAC/B,MAAM,OAAO,OAAO,SAAS,IAAI,UAAU;GACzC,MAAM,IAAI,iBAAiB,MAAM,IAAI,QAAQ;EAC/C,CAAC;EAEH,IAAI,SAAS,MAAM,OAAO,SAAS,GACjC,KAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,MAAM,OAAO,SAAS,GAAG;GACrE,MAAM,SAAS,oBAAoB,WAAW,SAAS;GACvD,IAAI,UAAU,KAAK,GACjB,MAAM,SAAS,IAAI,UAAU;IAC3B,MAAM,IAAI,oBAAoB,QAAQ,GAAG,MAAM,IAAI,MAAM;GAC3D,CAAC;EAEL;EAEF,IAAI,SAAS,MAAM,OAAO,KAAK,GAC7B,KAAK,MAAM,CAAC,IAAI,YAAY,OAAO,QAAQ,MAAM,OAAO,KAAK,GAAG;GAC9D,MAAM,IAAI,gBAAgB,MAAM,MAAM;GACtC,MAAM,SAAS,gBAAgB,MAAM,SAAS;EAChD;EAEF,KAAK,MAAM,OAAO,CAAC,YAAY,OAAO,GAAY;GAChD,IAAI,CAAC,SAAS,MAAM,OAAO,IAAI,GAAG;GAClC,KAAK,MAAM,MAAM,OAAO,KAAK,MAAM,OAAO,IAAI,GAC5C,MACE,IACA,UAAU,IAAI,GAAG,MACjB,QAAQ,aAAa,YAAY,MACnC;EAEJ;EACA,IAAI,SAAS,MAAM,OAAO,UAAU;QAC7B,MAAM,MAAM,OAAO,KAAK,MAAM,OAAO,UAAU,GAClD,IAAI,kBAAkB,CAAC,eAAe,IAAI,EAAE,GAC1C,MACE,aACA,qBACA,qBAAqB,MACrB,kCAAkC,GAAG,GACvC;EAAA;CAIR;CAEA,MAAM,YAAsD,CAAC;CAC7D,IAAI,MAAM,cAAc,KAAA,GACtB,IACE,CAAC,SAAS,MAAM,SAAS,KACzB,OAAO,OAAO,MAAM,WAAW,WAAW,GAE1C,MACE,aACA,gBACA,aACA,8CACF;MACK;EACL,MAAM,cAAc,QAAQ,cACxB,IAAI,IAAI,QAAQ,WAAW,IAC3B,KAAA;EACJ,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,MAAM,SAAS,GAAG;GACzD,MAAM,KAAK,aAAa;GACxB,IAAI,CAAC,YAAY,KAAK,EAAE,GAAG;IACzB,QACE,aACA,uBACA,IACA,GAAG,GAAG,gDACR;IACA;GACF;GACA,IAAI,eAAe,CAAC,YAAY,IAAI,EAAE,GAAG;IACvC,QACE,aACA,oBACA,IACA,qBAAqB,GAAG,eAC1B;IACA;GACF;GACA,MAAM,WAAW,kBAAkB,OAAO,IAAI,WAAW;GACzD,IAAI,CAAC,UAAU;GACf,IAAI,CAAC,QAAQ,kBAAkB;IAC7B,UAAU,MAAM;IAChB;GACF;GACA,IAAI;GACJ,IAAI;IACF,UAAU,QAAQ,iBAAiB,IAAI,QAAQ;GACjD,QAAQ;IACN,QACE,aACA,8BACA,IACA,aAAa,GAAG,iDAClB;IACA;GACF;GACA,KAAK,MAAM,SAAS,QAAQ,eAAe,CAAC,GAC1C,QAAQ,aAAa,MAAM,MAAM,MAAM,QAAQ,IAAI,MAAM,OAAO;GAElE,IAAI,CAAC,QAAQ,MAAM,QAAQ,YAAY,WAAW,GAChD,QACE,aACA,qBACA,IACA,aAAa,GAAG,kDAClB;GAEF,IAAI,QAAQ,MAAM,QAAQ,UAAU,UAAU,MAAM,QAAQ;EAC9D;CACF;CAGF,MAAM,SAAS,YACZ,QAAQ,eAAe,WAAW,aAAa,OAAO,CAAC,CACvD,KAAK,eAAe,WAAW,OAAO;CACzC,IAAI,OAAO,QAAQ,OAAO;EAAE,IAAI;EAAO;EAAQ;CAAY;CAE3D,MAAM,WAAW;EACf,GAAG;EACH,SAAS;EACT,SAAA;EACA;EACA;EACA;CACF;CACA,IAAI,MAAM,aAAa,KAAA,GACrB,SAAS,WAAW,MAAM;CAK5B,IAAI,MAAM,WAAW,KAAA,GACnB,SAAS,SAAS,MAAM;CAC1B,IAAI,MAAM,UAAU,KAAA,GAAW,SAAS,QAAQ,MAAM;CACtD,OAAO,SAAS;CAChB,IAAI,OAAO,KAAK,SAAS,CAAC,CAAC,QAAQ,SAAS,YAAY;CAIxD,OAAO;EAAE,IAAI;EAAM;EAAU,UAHZ,YACd,QAAQ,eAAe,WAAW,aAAa,SAAS,CAAC,CACzD,KAAK,eAAe,WAAW,OACL;EAAU;CAAY;AACrD;;AAGA,SAAgB,kBACd,MACA,UAAmC,CAAC,GAChB;CACpB,IAAI;CACJ,IAAI;EACF,QAAQ,KAAK,MAAM,IAAI;CACzB,QAAQ;EACN,OAAO;GACL,IAAI;GACJ,QAAQ,CAAC,gBAAgB;GACzB,aAAa,CACX;IACE,UAAU;IACV,MAAM;IACN,MAAM;IACN,SAAS;GACX,CACF;EACF;CACF;CACA,OAAO,iBAAiB,OAAO,OAAO;AACxC"}
|
|
1
|
+
{"version":3,"file":"validate.js","names":[],"sources":["../../src/cookbook/validate.ts"],"sourcesContent":["import type {\n Cookbook,\n CookbookExposureSurface,\n CookbookLayout,\n CookbookOverviewOverride,\n CookbookOverviewWidget,\n CookbookPolicyRow,\n CookbookTheme,\n RecipeShellSlot,\n} from '@happyvertical/smrt-types';\n\nexport type { Cookbook } from '@happyvertical/smrt-types';\n\n/** What a v1 document carries in `$schema`. */\nexport const COOKBOOK_SCHEMA_URL =\n 'https://s-m-r-t.dev/schemas/cookbook/v1.json';\n/** Accepted on read (the document used to be called a blueprint); never written. */\nexport const COOKBOOK_LEGACY_SCHEMA_URL =\n 'https://s-m-r-t.dev/schemas/blueprint/v1.json';\nexport const COOKBOOK_VERSION = 1;\n\nexport interface CookbookValidateOptions {\n /**\n * The recipe ids that exist (from published manifests). When given, an id\n * outside it is an error. When absent no existence check runs: that is the\n * seam `smrt cookbook validate` fills.\n */\n recipes?: Iterable<string>;\n /** Built-in or host-registered theme preset ids. */\n themePresets?: Iterable<string>;\n /** Manifest-derived ids the cookbook layout may reference. */\n layout?: {\n sections?: Iterable<string>;\n items?: Iterable<string>;\n /** Movable shell item ids resolved by the host (`dock:`, `slot:`, or host ids). */\n placementItems?: Iterable<string>;\n };\n /** Overview page ids defined by the resolved app shell. */\n overviewIds?: Iterable<string>;\n /**\n * Validate and canonicalize one structurally valid override against its page\n * definition and widget registry. The callback is trusted host code; the\n * cookbook value remains plain data and is never executed.\n */\n validateOverview?: (\n id: string,\n override: CookbookOverviewOverride,\n ) => CookbookOverviewValidation;\n}\n\nexport interface CookbookDiagnostic {\n severity: 'error' | 'warning';\n /** Stable machine-readable category. Host validators may add their own. */\n code: string;\n /** Path into the cookbook, using the existing dotted/indexed notation. */\n path: string;\n message: string;\n}\n\nexport type CookbookOverviewValidation =\n | {\n ok: true;\n /** `null` means the canonical override is empty and should be omitted. */\n override: CookbookOverviewOverride | null;\n diagnostics?: readonly CookbookDiagnostic[];\n }\n | { ok: false; diagnostics: readonly CookbookDiagnostic[] };\n\nexport type CookbookValidation =\n | {\n ok: true;\n cookbook: Cookbook;\n warnings: string[];\n diagnostics: CookbookDiagnostic[];\n }\n | { ok: false; errors: string[]; diagnostics: CookbookDiagnostic[] };\n\nconst VISIBILITIES = new Set(['basic', 'advanced', 'hidden']);\nconst SURFACES = new Set(['api', 'mcp', 'cli']);\nconst SCHEMES = new Set(['light', 'dark', 'system']);\nconst SHELL_SLOT_VALUES = [\n 'header.start',\n 'header.center',\n 'header.end',\n 'footer.start',\n 'footer.center',\n 'footer.end',\n 'leftSidebar.header',\n 'leftSidebar.footer',\n 'rightSidebar.header',\n 'rightSidebar.footer',\n] as const satisfies readonly RecipeShellSlot[];\nconst SHELL_SLOTS = new Set<string>(SHELL_SLOT_VALUES);\n\nconst isObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\nconst isStrings = (v: unknown): v is string[] =>\n Array.isArray(v) && v.every((x) => typeof x === 'string');\nconst isStringRecord = (v: unknown, valid: (x: unknown) => boolean) =>\n isObject(v) &&\n !Object.hasOwn(v, '__proto__') &&\n Object.values(v).every(valid);\n\nconst add = (\n diagnostics: CookbookDiagnostic[],\n severity: CookbookDiagnostic['severity'],\n code: string,\n path: string,\n message: string,\n): void => {\n diagnostics.push({ severity, code, path, message });\n};\n\nconst error = (\n diagnostics: CookbookDiagnostic[],\n code: string,\n path: string,\n message: string,\n): void => add(diagnostics, 'error', code, path, message);\n\nconst warning = (\n diagnostics: CookbookDiagnostic[],\n code: string,\n path: string,\n message: string,\n): void => add(diagnostics, 'warning', code, path, message);\n\nfunction checkPolicy(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n) {\n if (!isObject(value))\n return error(diagnostics, 'invalid_type', at, `${at} must be an object`);\n for (const key of ['objectRef', 'fieldName'] as const) {\n if (typeof value[key] !== 'string' || !value[key]) {\n error(\n diagnostics,\n 'invalid_value',\n `${at}.${key}`,\n `${at}.${key} must be a non-empty string`,\n );\n }\n }\n if (value.scopeType !== 'app')\n error(\n diagnostics,\n 'invalid_value',\n `${at}.scopeType`,\n `${at}.scopeType must be \"app\"`,\n );\n for (const key of ['defaultValue', 'help', 'label'] as const) {\n const v = value[key];\n if (v !== undefined && v !== null && typeof v !== 'string') {\n error(\n diagnostics,\n 'invalid_type',\n `${at}.${key}`,\n `${at}.${key} must be a string or null`,\n );\n }\n }\n const vis = value.visibility;\n if (vis != null && !(typeof vis === 'string' && VISIBILITIES.has(vis))) {\n error(\n diagnostics,\n 'invalid_value',\n `${at}.visibility`,\n `${at}.visibility must be basic, advanced or hidden`,\n );\n }\n const order = value.displayOrder;\n if (order != null && !(typeof order === 'number' && Number.isFinite(order))) {\n error(\n diagnostics,\n 'invalid_value',\n `${at}.displayOrder`,\n `${at}.displayOrder must be a finite number`,\n );\n }\n if (value.locked != null && typeof value.locked !== 'boolean') {\n error(\n diagnostics,\n 'invalid_type',\n `${at}.locked`,\n `${at}.locked must be a boolean`,\n );\n }\n}\n\nfunction checkLayout(value: unknown, diagnostics: CookbookDiagnostic[]) {\n if (!isObject(value) || value.version !== 1) {\n return error(\n diagnostics,\n 'invalid_value',\n 'layout',\n 'layout must be an object with version 1',\n );\n }\n for (const key of ['sectionOrder', 'hidden'] as const) {\n if (value[key] !== undefined && !isStrings(value[key])) {\n error(\n diagnostics,\n 'invalid_type',\n `layout.${key}`,\n `layout.${key} must be a list of strings`,\n );\n }\n }\n for (const key of ['itemOrder', 'moved', 'placements'] as const) {\n const v = value[key];\n if (v === undefined) continue;\n const ok =\n key === 'itemOrder'\n ? isStringRecord(v, isStrings)\n : isStringRecord(v, (x) => typeof x === 'string');\n if (!ok) {\n error(\n diagnostics,\n 'invalid_type',\n `layout.${key}`,\n key === 'itemOrder'\n ? 'layout.itemOrder must map ids to lists of strings'\n : `layout.${key} must map ids to strings`,\n );\n }\n }\n if (isObject(value.placements)) {\n for (const [id, slot] of Object.entries(value.placements)) {\n if (typeof slot === 'string' && !SHELL_SLOTS.has(slot)) {\n error(\n diagnostics,\n 'unknown_layout_slot',\n `layout.placements.${id}`,\n `Unknown shell slot \"${slot}\".`,\n );\n }\n }\n }\n for (const key of ['sections', 'items', 'panels'] as const) {\n if (\n value[key] !== undefined &&\n !isStringRecord(value[key], (x) => isObject(x))\n ) {\n error(\n diagnostics,\n 'invalid_type',\n `layout.${key}`,\n `layout.${key} must map ids to objects`,\n );\n }\n }\n const custom = value.customSections;\n if (\n custom !== undefined &&\n !(\n Array.isArray(custom) &&\n custom.every(\n (c) =>\n isObject(c) &&\n typeof c.id === 'string' &&\n typeof c.label === 'string',\n )\n )\n ) {\n error(\n diagnostics,\n 'invalid_type',\n 'layout.customSections',\n 'layout.customSections must be a list of { id, label }',\n );\n }\n}\n\nfunction checkTheme(value: unknown, diagnostics: CookbookDiagnostic[]) {\n if (!isObject(value))\n return error(\n diagnostics,\n 'invalid_type',\n 'theme',\n 'theme must be an object',\n );\n if (value.preset !== undefined && typeof value.preset !== 'string') {\n error(\n diagnostics,\n 'invalid_type',\n 'theme.preset',\n 'theme.preset must be a string',\n );\n }\n if (\n value.fontFamily !== undefined &&\n (typeof value.fontFamily !== 'string' || !value.fontFamily)\n ) {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.fontFamily',\n 'theme.fontFamily must be a non-empty string',\n );\n }\n if (value.customCss !== undefined) {\n if (typeof value.customCss !== 'string') {\n error(\n diagnostics,\n 'invalid_type',\n 'theme.customCss',\n 'theme.customCss must be a string',\n );\n } else if (Array.from(value.customCss).length > 65536) {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.customCss',\n 'theme.customCss must be at most 65,536 characters',\n );\n }\n }\n if (\n value.colorScheme !== undefined &&\n !(typeof value.colorScheme === 'string' && SCHEMES.has(value.colorScheme))\n ) {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.colorScheme',\n 'theme.colorScheme must be light, dark or system',\n );\n }\n const custom = value.custom;\n if (custom !== undefined) {\n if (!isObject(custom) || typeof custom.primary !== 'string') {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.custom',\n 'theme.custom must be an object with a string primary',\n );\n } else if (!/^#[0-9a-fA-F]{6}$/.test(custom.primary)) {\n error(\n diagnostics,\n 'invalid_value',\n 'theme.custom.primary',\n 'theme.custom.primary must be a hex colour like #c2410c',\n );\n } else if (\n custom.fontFamily !== undefined &&\n typeof custom.fontFamily !== 'string'\n ) {\n error(\n diagnostics,\n 'invalid_type',\n 'theme.custom.fontFamily',\n 'theme.custom.fontFamily must be a string',\n );\n }\n }\n}\n\nconst OVERVIEW_ID = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,95}$/;\nconst WIDGET_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;\nconst WIDGET_TYPE = /^[a-z][a-z0-9-]{0,31}$/;\n\nconst isOptionValue = (value: unknown) =>\n value === null ||\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n typeof value === 'boolean';\n\nfunction normalizeOverviewWidget(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n): CookbookOverviewWidget | null {\n if (!isObject(value)) {\n warning(diagnostics, 'malformed_overview_widget', at, `${at} was dropped`);\n return null;\n }\n const valid = [\n typeof value.id === 'string' && WIDGET_ID.test(value.id),\n typeof value.type === 'string' && WIDGET_TYPE.test(value.type),\n typeof value.span === 'number' &&\n Number.isInteger(value.span) &&\n value.span >= 1 &&\n value.span <= 4,\n isObject(value.options) &&\n Object.values(value.options).every(isOptionValue),\n value.version === undefined ||\n (typeof value.version === 'number' &&\n Number.isInteger(value.version) &&\n value.version >= 1),\n ];\n if (valid.every(Boolean)) return value as unknown as CookbookOverviewWidget;\n warning(\n diagnostics,\n 'malformed_overview_widget',\n at,\n `${at} was dropped because it is not a valid overview widget`,\n );\n return null;\n}\n\nfunction normalizeOverviewPatch(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n): NonNullable<CookbookOverviewOverride['changed']>[string] | null {\n if (!isObject(value)) {\n warning(diagnostics, 'malformed_overview_change', at, `${at} was dropped`);\n return null;\n }\n const valid =\n (value.span === undefined ||\n (typeof value.span === 'number' &&\n Number.isInteger(value.span) &&\n value.span >= 1 &&\n value.span <= 4)) &&\n (value.options === undefined ||\n (isObject(value.options) &&\n Object.values(value.options).every(isOptionValue))) &&\n (value.version === undefined ||\n (typeof value.version === 'number' &&\n Number.isInteger(value.version) &&\n value.version >= 1));\n if (valid) {\n return value as NonNullable<CookbookOverviewOverride['changed']>[string];\n }\n warning(\n diagnostics,\n 'malformed_overview_change',\n at,\n `${at} was dropped because it is not a valid widget change`,\n );\n return null;\n}\n\nfunction normalizeOverview(\n value: unknown,\n at: string,\n diagnostics: CookbookDiagnostic[],\n): CookbookOverviewOverride | null {\n if (!isObject(value) || value.version !== 1) {\n warning(\n diagnostics,\n 'malformed_overview',\n at,\n `${at} was dropped because it must be an object with version 1`,\n );\n return null;\n }\n const override = {\n ...value,\n version: 1,\n } as unknown as CookbookOverviewOverride;\n for (const key of ['order', 'removed'] as const) {\n if (value[key] !== undefined && !isStrings(value[key])) {\n warning(\n diagnostics,\n 'malformed_overview_list',\n `${at}.${key}`,\n `${at}.${key} was dropped because it must be a list of strings`,\n );\n delete override[key];\n } else if (value[key] !== undefined) {\n override[key] = [...new Set(value[key])];\n }\n }\n if (value.added !== undefined) {\n if (!Array.isArray(value.added)) {\n warning(\n diagnostics,\n 'malformed_overview_widgets',\n `${at}.added`,\n `${at}.added was dropped because it must be a list of widgets`,\n );\n delete override.added;\n } else {\n const added = value.added.flatMap((widget, index) => {\n const normalized = normalizeOverviewWidget(\n widget,\n `${at}.added[${index}]`,\n diagnostics,\n );\n return normalized ? [normalized] : [];\n });\n if (added.length) override.added = added;\n else delete override.added;\n }\n }\n if (value.changed !== undefined) {\n if (!isObject(value.changed) || Object.hasOwn(value.changed, '__proto__')) {\n warning(\n diagnostics,\n 'malformed_overview_changes',\n `${at}.changed`,\n `${at}.changed was dropped because it must map widget ids to objects`,\n );\n delete override.changed;\n } else {\n const changed: NonNullable<CookbookOverviewOverride['changed']> = {};\n for (const [id, patch] of Object.entries(value.changed)) {\n if (!WIDGET_ID.test(id)) {\n warning(\n diagnostics,\n 'malformed_overview_change',\n `${at}.changed.${id}`,\n `${at}.changed.${id} was dropped because its widget id is invalid`,\n );\n continue;\n }\n const normalized = normalizeOverviewPatch(\n patch,\n `${at}.changed.${id}`,\n diagnostics,\n );\n if (normalized) changed[id] = normalized;\n }\n if (Object.keys(changed).length) override.changed = changed;\n else delete override.changed;\n }\n }\n return override;\n}\n\n/**\n * Structural validation of an unknown value (parsed JSON). Returns the\n * normalised document (legacy `$schema` rewritten, `features` defaulted,\n * recipes sorted and unique) or every problem found, each a readable line\n * naming the path. Unknown top-level keys are kept (additive fields in v1).\n *\n * Deep checks use optional host-supplied catalogs. A structurally valid\n * cookbook remains portable when those catalogs are absent. Invalid overview\n * entries are dropped and reported, following the v1 overview contract.\n */\nexport function validateCookbook(\n input: unknown,\n options: CookbookValidateOptions = {},\n): CookbookValidation {\n const diagnostics: CookbookDiagnostic[] = [];\n if (!isObject(input)) {\n error(diagnostics, 'invalid_type', '', 'expected a JSON object');\n return { ok: false, errors: ['expected a JSON object'], diagnostics };\n }\n\n const { $schema, version } = input;\n if (\n $schema !== undefined &&\n $schema !== COOKBOOK_SCHEMA_URL &&\n $schema !== COOKBOOK_LEGACY_SCHEMA_URL\n ) {\n error(\n diagnostics,\n 'invalid_schema',\n '$schema',\n `$schema must be ${COOKBOOK_SCHEMA_URL}`,\n );\n }\n if (version === undefined) {\n error(diagnostics, 'missing_field', 'version', 'version is required');\n } else if (typeof version !== 'number' || !Number.isInteger(version)) {\n error(\n diagnostics,\n 'invalid_type',\n 'version',\n 'version must be a whole number',\n );\n } else if (version > COOKBOOK_VERSION) {\n error(\n diagnostics,\n 'unsupported_version',\n 'version',\n `version ${version} is newer than this reader understands (${COOKBOOK_VERSION})`,\n );\n } else if (version !== COOKBOOK_VERSION) {\n error(\n diagnostics,\n 'unsupported_version',\n 'version',\n `version ${version} is not supported`,\n );\n }\n if ($schema === COOKBOOK_LEGACY_SCHEMA_URL) {\n warning(\n diagnostics,\n 'migrated_schema',\n '$schema',\n '$schema uses the legacy blueprint URL; migrated',\n );\n }\n\n for (const key of ['name', 'description'] as const) {\n if (input[key] !== undefined && typeof input[key] !== 'string') {\n error(diagnostics, 'invalid_type', key, `${key} must be a string`);\n }\n }\n\n let recipes: string[] = [];\n if (!isStrings(input.recipes)) {\n error(\n diagnostics,\n 'invalid_type',\n 'recipes',\n 'recipes must be a list of recipe ids',\n );\n } else {\n recipes = [...new Set(input.recipes)].sort();\n if (options.recipes) {\n const known = new Set(options.recipes);\n for (const id of recipes) {\n if (!known.has(id))\n error(\n diagnostics,\n 'unknown_recipe',\n `recipes[${input.recipes.indexOf(id)}]`,\n `recipes: unknown recipe \"${id}\"`,\n );\n }\n }\n }\n\n let features: string[] = [];\n if (input.features !== undefined) {\n if (!isStrings(input.features)) {\n error(\n diagnostics,\n 'invalid_type',\n 'features',\n 'features must be a list of model names',\n );\n } else {\n features = [...new Set(input.features)].sort();\n if (features.length !== input.features.length) {\n warning(\n diagnostics,\n 'duplicate_value',\n 'features',\n 'features listed a model more than once; deduplicated',\n );\n }\n }\n }\n\n const policies: CookbookPolicyRow[] = [];\n if (!Array.isArray(input.policies)) {\n error(diagnostics, 'invalid_type', 'policies', 'policies must be a list');\n } else {\n for (const [i, row] of input.policies.entries()) {\n checkPolicy(row, `policies[${i}]`, diagnostics);\n policies.push(row as CookbookPolicyRow);\n }\n }\n\n if (input.exposure !== undefined) {\n const ok = isStringRecord(\n input.exposure,\n (s) => isStrings(s) && s.every((x) => SURFACES.has(x)),\n );\n if (!ok)\n error(\n diagnostics,\n 'invalid_value',\n 'exposure',\n 'exposure must map model names to lists of api, mcp or cli',\n );\n }\n if (input.layout !== undefined) checkLayout(input.layout, diagnostics);\n if (input.theme !== undefined) checkTheme(input.theme, diagnostics);\n\n if (isObject(input.theme) && typeof input.theme.preset === 'string') {\n const presets = options.themePresets\n ? new Set(options.themePresets)\n : undefined;\n if (presets && !presets.has(input.theme.preset)) {\n error(\n diagnostics,\n 'unknown_theme_preset',\n 'theme.preset',\n `Unknown theme preset \"${input.theme.preset}\".`,\n );\n }\n }\n\n if (isObject(input.layout) && input.layout.version === 1 && options.layout) {\n const sections = options.layout.sections\n ? new Set(options.layout.sections)\n : undefined;\n const items = options.layout.items\n ? new Set(options.layout.items)\n : undefined;\n const placementItems = options.layout.placementItems\n ? new Set(options.layout.placementItems)\n : undefined;\n if (sections && Array.isArray(input.layout.customSections)) {\n for (const section of input.layout.customSections) {\n if (isObject(section) && typeof section.id === 'string') {\n sections.add(section.id);\n }\n }\n }\n const known = (id: string, kind: 'section' | 'item' | 'either') =>\n kind === 'section'\n ? sections?.has(id) !== false\n : kind === 'item'\n ? items?.has(id) !== false\n : sections?.has(id) === true ||\n items?.has(id) === true ||\n (!sections && !items);\n const check = (\n id: unknown,\n path: string,\n kind: 'section' | 'item' | 'either',\n ) => {\n if (typeof id === 'string' && !known(id, kind)) {\n error(\n diagnostics,\n 'unknown_layout_id',\n path,\n `Unknown layout ${kind === 'either' ? 'section or item' : kind} \"${id}\".`,\n );\n }\n };\n if (isStrings(input.layout.sectionOrder)) {\n input.layout.sectionOrder.forEach((id, index) => {\n check(id, `layout.sectionOrder[${index}]`, 'section');\n });\n }\n if (isStrings(input.layout.hidden)) {\n input.layout.hidden.forEach((id, index) => {\n check(id, `layout.hidden[${index}]`, 'either');\n });\n }\n if (isObject(input.layout.itemOrder)) {\n for (const [section, order] of Object.entries(input.layout.itemOrder)) {\n check(section, `layout.itemOrder.${section}`, 'section');\n if (isStrings(order)) {\n order.forEach((id, index) => {\n check(id, `layout.itemOrder.${section}[${index}]`, 'item');\n });\n }\n }\n }\n if (isObject(input.layout.moved)) {\n for (const [id, section] of Object.entries(input.layout.moved)) {\n check(id, `layout.moved.${id}`, 'item');\n check(section, `layout.moved.${id}`, 'section');\n }\n }\n for (const key of ['sections', 'items'] as const) {\n if (!isObject(input.layout[key])) continue;\n for (const id of Object.keys(input.layout[key])) {\n check(\n id,\n `layout.${key}.${id}`,\n key === 'sections' ? 'section' : 'item',\n );\n }\n }\n if (isObject(input.layout.placements)) {\n for (const id of Object.keys(input.layout.placements)) {\n if (placementItems && !placementItems.has(id)) {\n error(\n diagnostics,\n 'unknown_layout_id',\n `layout.placements.${id}`,\n `Unknown layout placement item \"${id}\".`,\n );\n }\n }\n }\n }\n\n const overviews: Record<string, CookbookOverviewOverride> = {};\n if (input.overviews !== undefined) {\n if (\n !isObject(input.overviews) ||\n Object.hasOwn(input.overviews, '__proto__')\n ) {\n error(\n diagnostics,\n 'invalid_type',\n 'overviews',\n 'overviews must map overview ids to overrides',\n );\n } else {\n const overviewIds = options.overviewIds\n ? new Set(options.overviewIds)\n : undefined;\n for (const [id, value] of Object.entries(input.overviews)) {\n const at = `overviews.${id}`;\n if (!OVERVIEW_ID.test(id)) {\n warning(\n diagnostics,\n 'invalid_overview_id',\n at,\n `${at} was dropped because its overview id is invalid`,\n );\n continue;\n }\n if (overviewIds && !overviewIds.has(id)) {\n warning(\n diagnostics,\n 'unknown_overview',\n at,\n `Unknown overview \"${id}\" was dropped.`,\n );\n continue;\n }\n const override = normalizeOverview(value, at, diagnostics);\n if (!override) continue;\n if (!options.validateOverview) {\n overviews[id] = override;\n continue;\n }\n let checked: CookbookOverviewValidation;\n try {\n checked = options.validateOverview(id, override);\n } catch {\n warning(\n diagnostics,\n 'overview_validation_failed',\n at,\n `Overview \"${id}\" was dropped because its host validator failed.`,\n );\n continue;\n }\n for (const issue of checked.diagnostics ?? []) {\n warning(diagnostics, issue.code, issue.path || at, issue.message);\n }\n if (!checked.ok && checked.diagnostics.length === 0) {\n warning(\n diagnostics,\n 'overview_rejected',\n at,\n `Overview \"${id}\" was rejected by its host validator and dropped.`,\n );\n }\n if (checked.ok && checked.override) overviews[id] = checked.override;\n }\n }\n }\n\n const errors = diagnostics\n .filter((diagnostic) => diagnostic.severity === 'error')\n .map((diagnostic) => diagnostic.message);\n if (errors.length) return { ok: false, errors, diagnostics };\n\n const cookbook = {\n ...input,\n $schema: COOKBOOK_SCHEMA_URL,\n version: COOKBOOK_VERSION,\n recipes,\n features,\n policies,\n } as unknown as Cookbook;\n if (input.exposure !== undefined) {\n cookbook.exposure = input.exposure as Record<\n string,\n CookbookExposureSurface[]\n >;\n }\n if (input.layout !== undefined)\n cookbook.layout = input.layout as CookbookLayout;\n if (input.theme !== undefined) cookbook.theme = input.theme as CookbookTheme;\n delete cookbook.overviews;\n if (Object.keys(overviews).length) cookbook.overviews = overviews;\n const warnings = diagnostics\n .filter((diagnostic) => diagnostic.severity === 'warning')\n .map((diagnostic) => diagnostic.message);\n return { ok: true, cookbook, warnings, diagnostics };\n}\n\n/** Parse JSON text, then {@link validateCookbook} it. */\nexport function parseCookbookText(\n text: string,\n options: CookbookValidateOptions = {},\n): CookbookValidation {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return {\n ok: false,\n errors: ['not valid JSON'],\n diagnostics: [\n {\n severity: 'error',\n code: 'invalid_json',\n path: '',\n message: 'not valid JSON',\n },\n ],\n };\n }\n return validateCookbook(value, options);\n}\n"],"mappings":"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"}
|