import type { SystemDefinition, ValidationIssue, ValidationResult } from './types.ts'; import { SYSTEM_FORMAT, FORMAT_VERSION } from './types.ts'; function err(path: string, message: string, code: string): ValidationIssue { return { path, message, code }; } export function validateSystemDefinition(system: unknown): ValidationResult { const errors: ValidationIssue[] = []; if (!system || typeof system !== 'object') { return { ok: false, errors: [err('$', 'System must be an object', 'type')] }; } const s = system as Partial; if (s.format !== SYSTEM_FORMAT) errors.push(err('format', `Expected ${SYSTEM_FORMAT}`, 'format')); if (s.formatVersion !== FORMAT_VERSION) { errors.push(err('formatVersion', `Expected ${FORMAT_VERSION}`, 'formatVersion')); } if (!s.id || typeof s.id !== 'string') errors.push(err('id', 'id required', 'required')); if (!s.name || typeof s.name !== 'string') errors.push(err('name', 'name required', 'required')); if (typeof s.schemaVersion !== 'number') errors.push(err('schemaVersion', 'number required', 'type')); if (typeof s.revision !== 'number') errors.push(err('revision', 'number required', 'type')); if (!s.bricks || typeof s.bricks !== 'object') errors.push(err('bricks', 'bricks required', 'required')); if (!Array.isArray(s.creationRules)) errors.push(err('creationRules', 'array required', 'type')); if (!s.sheetLayout || !Array.isArray(s.sheetLayout.pages)) { errors.push(err('sheetLayout.pages', 'pages required', 'required')); } const derived = s.bricks?.derived ?? []; const keys = new Set(derived.map((d) => d.key)); for (const d of derived) { if (!d.formula) errors.push(err(`derived.${d.key}`, 'formula required', 'required')); for (const dep of d.dependsOn ?? []) { if (dep.startsWith('derived.') && !keys.has(dep.slice('derived.'.length))) { // allow forward refs; cycle checked elsewhere } } } return { ok: errors.length === 0, errors }; }