import { isSemaEvent, isSemaEventLabel, isSemaIntent, isSemaIntentBinding } from './event' import { SemaInvariantError } from './errors' import { SEMA_FAMILY_POLICY, type SemaActionEvent, type SemaIntentBinding } from './types' export { SemaInvariantError } from './errors' export function validateSemaIntentBinding(binding: SemaIntentBinding, ctx = 'sema.intent'): void { if (!isSemaIntentBinding(binding)) { throw new SemaInvariantError(`${ctx} is not a valid SemaIntentBinding`) } const supported = binding.supported ?? [] if (supported.length === 0) return for (const intent of supported) { if (!isSemaIntent(intent)) { throw new SemaInvariantError(`${ctx}: intent "${String(intent)}" is not a valid Intent`) } } if (!supported.includes(binding.default)) { throw new SemaInvariantError( `${ctx}: default intent "${binding.default}" must be included in supported intents` ) } } export function validateSemaEvent(event: SemaActionEvent, ctx = 'sema.event'): void { if (isSemaEventLabel(event)) return if (!isSemaEvent(event)) { throw new SemaInvariantError(`${ctx} is not a valid canonical semantic event`) } const intent = 'intent' in event ? event.intent : undefined // Policy enforcement: families marked `intentPolicy: 'expected'` MUST // declare intent. if ( intent === undefined && SEMA_FAMILY_POLICY[event.family].intentPolicy === 'expected' ) { throw new SemaInvariantError( `${ctx}: family "${event.family}" requires intent (intentPolicy: 'expected')` ) } // Bindings need structural validation; literals + absence are fine. if (intent === undefined || typeof intent === 'string') return validateSemaIntentBinding(intent, `${ctx}.intent`) }