/** * Meta validation: check the caller-provided {@link WorkflowMeta} DATA against the shape * contract and reject everything else loud, every violation named. Meta arrives as schema-checked * JSON data, never evaluated script text; evaluating it on the host could run getters outside the * worker timeout that exists to isolate model-written code. * @module @deepseek-ai/dsh-workflow-workerthread/meta */ import { WorkflowError } from '@deepseek-ai/dsh-workflow' import type { WorkflowMeta, WorkflowPhase } from '@deepseek-ai/dsh-workflow' /** Collect shape violations for a meta value (plain JSON data by the seam contract). */ function validateMetaShape(meta: unknown): { meta?: WorkflowMeta; violations: string[] } { const violations: string[] = [] if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) { return { violations: ['meta must be an object'] } } const record = meta as Record const known = new Set(['name', 'description', 'whenToUse', 'phases']) for (const key of Object.keys(record)) { if (!known.has(key)) violations.push(`meta.${key} is not a recognized field (name/description/whenToUse/phases)`) } if (typeof record.name !== 'string' || record.name.length === 0) violations.push('meta.name must be a non-empty string') if (typeof record.description !== 'string' || record.description.length === 0) violations.push('meta.description must be a non-empty string') if (record.whenToUse !== undefined && typeof record.whenToUse !== 'string') violations.push('meta.whenToUse must be a string') const phases: WorkflowPhase[] = [] if (record.phases !== undefined) { if (!Array.isArray(record.phases)) { violations.push('meta.phases must be an array') } else { record.phases.forEach((phase, index) => { if (typeof phase !== 'object' || phase === null || Array.isArray(phase)) { violations.push(`meta.phases[${index}] must be an object`) return } const entry = phase as Record for (const key of Object.keys(entry)) { if (!['title', 'detail', 'provider', 'model'].includes(key)) violations.push(`meta.phases[${index}].${key} is not a recognized field`) } if (typeof entry.title !== 'string' || entry.title.length === 0) violations.push(`meta.phases[${index}].title must be a non-empty string`) if (entry.detail !== undefined && typeof entry.detail !== 'string') violations.push(`meta.phases[${index}].detail must be a string`) if (entry.provider !== undefined && typeof entry.provider !== 'string') violations.push(`meta.phases[${index}].provider must be a string`) if (entry.model !== undefined && typeof entry.model !== 'string') violations.push(`meta.phases[${index}].model must be a string`) if (violations.length === 0) { phases.push({ title: entry.title as string, ...entry.detail !== undefined ? { detail: entry.detail as string } : {}, ...entry.provider !== undefined ? { provider: entry.provider as string } : {}, ...entry.model !== undefined ? { model: entry.model as string } : {}, }) } }) } } if (violations.length > 0) return { violations } return { violations, meta: { name: record.name as string, description: record.description as string, ...record.whenToUse !== undefined ? { whenToUse: record.whenToUse as string } : {}, ...record.phases !== undefined ? { phases } : {}, }, } } /** * Validate a caller-provided meta value against the {@link WorkflowMeta} * contract. Throws `META_INVALID` naming every violation (unknown fields, * missing/mistyped `name`/`description`, malformed `phases`); the returned * meta is a NORMALIZED copy built from the validated fields, so the engine * never aliases the caller's object. * @param value - the meta data from the start request (plain JSON by the seam contract). * @returns the validated, normalized meta block. */ export function validateMeta(value: unknown): WorkflowMeta { const { meta, violations } = validateMetaShape(value) if (meta === undefined) { throw new WorkflowError(`invalid meta: ${violations.join('; ')}`, 'META_INVALID') } return meta }