/** * cli:scaffold-coded-entity — validate.ts * * Zod parse + cross-entity coherence: the code keys form a registry (one * descriptor per key at boot), so duplicates fail closed. Plus the FORMAT gate: * `defaultFormat` is checked against the socle's closed grammar * (`lib/code-pattern-grammar.ts`) — an invented token or a reset with no * matching date token makes the engine throw at EVERY insert, so it is an * error here, never a warning. Same fail-closed posture on * `collisionStrategy: 'Suffix'` without a probe: since v3.67 the engine * REFUSES the allocation on a probe-less key instead of suffixing blindly. */ import { hasSequence, referencedFields, validateFormat } from '../../../../../lib/code-pattern-grammar.js'; import { CodedEntitySpecSchema, type ValidationResult } from './types.js'; export function validate(raw: unknown): ValidationResult { const parsed = CodedEntitySpecSchema.safeParse(raw); if (!parsed.success) { return { valid: false, errors: parsed.error.issues.map(i => `[${i.path.join('.')}] ${i.message}`), warnings: [], }; } const spec = parsed.data; const errors: string[] = []; const warnings: string[] = []; const seenEntities = new Set(); const seenKeys = new Set(); for (const e of spec.entities) { if (seenEntities.has(e.entityName)) { errors.push(`duplicate entity "${e.entityName}" — one descriptor per entity`); } seenEntities.add(e.entityName); if (seenKeys.has(e.codeKey)) { errors.push(`duplicate codeKey "${e.codeKey}" — keys form a platform-wide registry (one descriptor per key)`); } seenKeys.add(e.codeKey); for (const problem of validateFormat(e.defaultFormat, e.reset)) { errors.push(`entity "${e.entityName}": ${problem}`); } // ─── Collision strategy ↔ probe coherence (mirror of the v3.67 runtime) ─── if (e.collisionStrategy === 'Suffix' && !e.probeType) { errors.push( `entity "${e.entityName}": collisionStrategy "Suffix" requires a probeType — since v3.67 the engine ` + `consults the key's ICodeUniquenessProbe before suffixing and REFUSES the allocation on a probe-less key ` + `(runtime failure at the first collision). Write the probe class (its scope is a business decision — ` + `no scaffolder emits it) and pass its name; the DI becomes AddSmartStackCodeKey().` ); } if ( e.collisionStrategy === 'Fail' && !e.probeType && !hasSequence(e.defaultFormat) && referencedFields(e.defaultFormat).length > 0 ) { warnings.push( `entity "${e.entityName}": sequence-less derived format "${e.defaultFormat}" with collisionStrategy "Fail" ` + `and no probe — a collision surfaces as a RAW SQL unique-index violation instead of a domain error. ` + `Consider a probeType (+ Suffix), or add {SEQ:n} to the format.` ); } } return { valid: errors.length === 0, errors, warnings, data: spec }; }