const ALLOWED_OPERATIONS = ['resolve', 'write', 'compact']; const ALLOWED_PLUGIN_CATEGORIES = new Set([ 'semantic', 'extraction', 'lifecycle', 'behavior', 'policy', ]); function check(name, ok, message = null, details = {}) { return { name, ok: Boolean(ok), ...(message ? { message } : {}), ...details }; } function isValidName(value) { return /^[a-z][a-z0-9-]*$/.test(String(value ?? '')); } function supports(backend, operation) { return typeof backend?.[operation] === 'function'; } function safeAvailable(backend) { try { return backend?.isAvailable?.() !== false; } catch { return false; } } export function inspectMemoryV2Backend(backend) { const name = String(backend?.name ?? '').trim(); const available = safeAvailable(backend); const support = Object.fromEntries( ALLOWED_OPERATIONS.map((operation) => [operation, supports(backend, operation)]), ); const checks = [ check('name_present', Boolean(name), 'Backend name is required'), check('name_format', isValidName(name), 'Backend name must be lowercase kebab-case', { backendName: name, }), check( 'has_operation', Object.values(support).some(Boolean), 'Backend must implement at least one Memory V2 operation', { supports: support }, ), ]; if (!available) { const reason = typeof backend?.getUnavailableReason === 'function' ? backend.getUnavailableReason() : backend?.unavailableReason; checks.push(check( 'unavailable_reason', Boolean(reason), 'Unavailable backend must expose a reason', { reason: reason ?? null }, )); } if (backend?.category != null) { checks.push(check( 'category_known', ALLOWED_PLUGIN_CATEGORIES.has(String(backend.category)), 'Plugin backend category is unknown', { category: String(backend.category) }, )); } if (backend?.flag != null) { checks.push(check( 'flag_format', /^[A-Z0-9_]+$/.test(String(backend.flag)), 'Backend feature flag must be SCREAMING_SNAKE_CASE', { flag: String(backend.flag) }, )); } return { ok: checks.every((item) => item.ok), name, available, supports: support, category: backend?.category ?? null, role: backend?.role ?? null, flag: backend?.flag ?? null, checks, }; } export function validateMemoryV2BackendSet(backends = []) { const inspected = backends.map((backend) => inspectMemoryV2Backend(backend)); const names = inspected.map((item) => item.name).filter(Boolean); const duplicateNames = names.filter((name, index) => names.indexOf(name) !== index); const checks = [ check('backend_list_present', Array.isArray(backends), 'Backends must be an array'), check('backend_list_non_empty', inspected.length > 0, 'At least one backend is required'), check('backend_names_unique', duplicateNames.length === 0, 'Backend names must be unique', { duplicateNames: [...new Set(duplicateNames)], }), check( 'legacy_backend_present', names.includes('legacy-conversation-memory'), 'Legacy conversation memory backend must remain registered', ), ]; return { ok: checks.every((item) => item.ok) && inspected.every((item) => item.ok), checkedAt: new Date().toISOString(), summary: { backendCount: inspected.length, availableBackends: inspected.filter((item) => item.available).map((item) => item.name), operationSupport: Object.fromEntries( ALLOWED_OPERATIONS.map((operation) => [ operation, inspected.filter((item) => item.supports[operation]).map((item) => item.name), ]), ), }, checks, backends: inspected, }; }