// Pure metadata validation. Diagnostics never include input values or unknown keys. const ID = /^[a-z0-9][a-z0-9._-]{0,63}$/; const REF = /^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,63}$/; export const validId = value => typeof value === 'string' && ID.test(value); const object = v => v !== null && typeof v === 'object' && !Array.isArray(v) && [Object.prototype, null].includes(Object.getPrototypeOf(v)); const text = v => typeof v === 'string' && v.length > 0; const list = predicate => v => Array.isArray(v) && v.length > 0 && v.every(predicate) && new Set(v).size === v.length; const types = { version: v => v === 1, id: validId, string: text, ids: list(validId), strings: list(text), refs: list(v => typeof v === 'string' && REF.test(v)), object, boolean: v => typeof v === 'boolean', timestamp: v => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(v) && Number.isFinite(Date.parse(v)), }; const codes = { version: 'unsupported-version', id: 'invalid-id', string: 'invalid-string', ids: 'invalid-id-list', strings: 'invalid-model-list', refs: 'invalid-account-ref-list', object: 'not-an-object', boolean: 'invalid-boolean', timestamp: 'invalid-timestamp' }; export class ValidationError extends Error { constructor(path, code) { super(`${path}: ${code}`); this.name = 'ValidationError'; this.path = path; this.code = code; this.detail = ''; } } const fail = (errors, path, code) => errors.push(new ValidationError(path, code)); function shape(errors, path, value, fields, required) { if (!object(value)) { fail(errors, path, 'not-an-object'); return false; } if (Object.keys(value).some(k => !Object.hasOwn(fields, k))) fail(errors, path, 'unknown-field'); for (const [key, type] of Object.entries(fields)) { if (!Object.hasOwn(value, key)) { if (required.includes(key)) fail(errors, `${path}.${key}`, 'missing-field'); } else if (!types[type](value[key])) fail(errors, `${path}.${key}`, codes[type]); } return errors.length === 0; } export function validateProvider(record) { const errors = []; if (!shape(errors, 'provider', record, { providerVersion: 'version', id: 'id', kind: 'string', harnesses: 'object', credentialTypes: 'ids', allowInsecureTransport: 'boolean' }, ['providerVersion', 'id', 'kind', 'harnesses', 'credentialTypes'])) return errors; if (!['native', 'custom-endpoint'].includes(record.kind)) fail(errors, 'provider.kind', 'unsupported-kind'); if (record.credentialTypes.some(t => !['oauth', 'api_key', 'none'].includes(t))) fail(errors, 'provider.credentialTypes', 'unsupported-credential-type'); if (!Object.keys(record.harnesses).length) fail(errors, 'provider.harnesses', 'empty-harnesses'); if (record.kind === 'native' && record.allowInsecureTransport === true) fail(errors, 'provider.allowInsecureTransport', 'insecure-transport-unsupported-for-native'); for (const [id, cfg] of Object.entries(record.harnesses)) { const p = 'provider.harnesses.entry'; if (!validId(id)) fail(errors, p, 'invalid-id'); const local = []; if (record.kind === 'native') { shape(local, p, cfg, { providerId: 'id' }, ['providerId']); } else if (record.kind === 'custom-endpoint') { if (shape(local, p, cfg, { api: 'string', baseUrl: 'string', models: 'strings' }, ['api', 'baseUrl', 'models'])) { checkUrl(local, p, cfg.baseUrl, record.allowInsecureTransport); } } errors.push(...local); } return errors; } function checkUrl(errors, path, value, allowHttp) { try { const u = new URL(value); if (!['http:', 'https:'].includes(u.protocol) || !u.hostname || u.hash || /[\s\\]/.test(value)) fail(errors, path, 'invalid-url'); if (u.username || u.password) fail(errors, path, 'url-credentials-forbidden'); if (u.protocol === 'http:' && allowHttp !== true) fail(errors, path, 'insecure-transport-not-allowed'); } catch { fail(errors, path, 'invalid-url'); } } export function validateAccount(record, providerId) { const errors = []; if (!shape(errors, 'account', record, { accountVersion: 'version', id: 'id', name: 'string', provider: 'id', type: 'string', createdAt: 'timestamp' }, ['accountVersion', 'id', 'name', 'provider', 'type', 'createdAt'])) return errors; if (providerId !== undefined && record.provider !== providerId) fail(errors, 'account.provider', 'provider-path-mismatch'); if (!['oauth', 'api_key', 'none'].includes(record.type)) fail(errors, 'account.type', 'unsupported-credential-type'); return errors; } function accountMap(errors, path, map) { for (const [provider, ref] of Object.entries(map ?? {})) { if (!validId(provider) || typeof ref !== 'string' || !REF.test(ref)) fail(errors, path, 'invalid-account-ref'); else if (ref.split('/')[0] !== provider) fail(errors, path, 'account-provider-mismatch'); } } export function validateSettingsProfile(record) { const errors = []; if (!shape(errors, 'profile', record, { settingsVersion: 'version', id: 'id', allowedAccounts: 'refs', providers: 'ids', defaultAccounts: 'object', models: 'object' }, ['settingsVersion', 'id', 'allowedAccounts'])) return errors; accountMap(errors, 'profile.defaultAccounts', record.defaultAccounts); for (const ref of Object.values(record.defaultAccounts ?? {})) if (!record.allowedAccounts.includes(ref)) fail(errors, 'profile.defaultAccounts', 'default-account-not-enrolled'); for (const [id, models] of Object.entries(record.models ?? {})) if (!validId(id) || !list(text)(models)) fail(errors, 'profile.models', 'invalid-model-list'); return errors; } export function validateSeatSelection(record) { const errors = []; if (shape(errors, 'selection', record, { selectionVersion: 'version', profile: 'id', accounts: 'object', updatedAt: 'timestamp', pinnedFromSession: 'string' }, ['selectionVersion', 'profile'])) accountMap(errors, 'selection.accounts', record.accounts); return errors; } export function validateHarnessManifest(record) { const errors = []; if (!shape(errors, 'harness', record, { harnessVersion: 'version', id: 'id', executable: 'id', adapter: 'id', compatibleRange: 'string', executionMode: 'string', materializers: 'ids' }, ['harnessVersion', 'id', 'executable', 'adapter', 'compatibleRange', 'executionMode', 'materializers'])) return errors; if (record.id !== record.executable) fail(errors, 'harness.executable', 'id-executable-mismatch'); if (!['container', 'host'].includes(record.executionMode)) fail(errors, 'harness.executionMode', 'unsupported-mode'); return errors; }