// Fixture-only execution resolution. Not a membership/registration authority. import { validId, validateProvider, validateAccount, validateSettingsProfile, validateHarnessManifest } from './records.mjs'; export class FixtureError extends Error { constructor(code) { super(code); this.name = 'FixtureError'; this.code = code; } } export const refuse = code => { throw new FixtureError(code); }; const obj = v => v !== null && typeof v === 'object' && !Array.isArray(v); export function exact(v, required, optional = []) { if (!obj(v) || required.some(k => !Object.hasOwn(v, k)) || Object.keys(v).some(k => ![...required, ...optional].includes(k))) refuse('invalid-fixture-input'); } function checkRegistry(result) { if (!obj(result) || !Array.isArray(result.errors) || result.errors.length || !obj(result.entries)) refuse('invalid-registry'); const entries = result.entries; for (const [name, validate] of [['providers', validateProvider], ['accounts', validateAccount], ['profiles', validateSettingsProfile], ['harnesses', validateHarnessManifest]]) { if (!obj(entries[name])) refuse('invalid-registry'); for (const [key, record] of Object.entries(entries[name])) { if (validate(record).length) refuse('invalid-registry'); if (key !== (name === 'accounts' ? `${record.provider}/${record.id}` : record.id)) refuse('invalid-registry'); } } return entries; } export function resolveFixtureExecution(result, request) { // Clone immediately: later caller mutation cannot change this resolution. let r, registry; try { r = structuredClone(request); registry = structuredClone(result); } catch { refuse('invalid-fixture-input'); } const entries = checkRegistry(registry); exact(r, ['fixtureOnly', 'agentId', 'projectId', 'workspaceId', 'sessionId', 'executionId', 'profile'], ['accounts', 'fork']); if (r.fixtureOnly !== true) refuse('fixture-only'); for (const key of ['agentId', 'projectId', 'workspaceId', 'sessionId', 'executionId', 'profile']) if (!validId(r[key])) refuse('invalid-scope'); if (!Object.hasOwn(entries.profiles, r.profile)) refuse('missing-profile'); const profile = entries.profiles[r.profile]; const overrides = r.accounts ?? {}; if (!obj(overrides)) refuse('invalid-selection'); const providers = new Set([...(profile.providers ?? []), ...profile.allowedAccounts.map(ref => ref.split('/')[0])]); for (const key of Object.keys(overrides)) if (!providers.has(key)) refuse('provider-not-enrolled'); if (Object.hasOwn(r, 'fork')) { exact(r.fork, ['sourceSessionId', 'accounts']); if (!validId(r.fork.sourceSessionId) || r.fork.sourceSessionId === r.sessionId || !obj(r.fork.accounts)) refuse('invalid-fork-pin'); for (const id of Object.keys(r.fork.accounts)) if (!providers.has(id)) refuse('revoked-fork-pin'); } const accounts = Object.create(null), models = { providers: Object.create(null) }, types = Object.create(null); for (const id of [...providers].sort()) { if (!Object.hasOwn(entries.providers, id)) refuse('missing-provider'); const provider = entries.providers[id], cfg = provider.harnesses.pi; if (!cfg || !entries.harnesses.pi) refuse('unsupported-harness'); let ref; if (r.fork) { ref = r.fork.accounts[id]; if (Object.hasOwn(overrides, id) && overrides[id] !== ref) refuse('fork-account-change'); } else ref = Object.hasOwn(overrides, id) ? overrides[id] : profile.defaultAccounts?.[id]; if (ref === undefined) { if (!provider.credentialTypes.includes('none')) refuse(r.fork ? 'missing-fork-pin' : 'missing-account-selection'); } else { if (typeof ref !== 'string' || !profile.allowedAccounts.includes(ref) || ref.split('/')[0] !== id) refuse('account-not-enrolled'); if (!Object.hasOwn(entries.accounts, ref)) refuse('missing-account'); const account = entries.accounts[ref]; if (account.provider !== id || !provider.credentialTypes.includes(account.type)) refuse('credential-type-not-supported'); accounts[id] = ref; types[id] = account.type; } if (provider.kind === 'native') { // No aliasing to another provider's credential slot. if (cfg.providerId !== id) refuse('provider-alias-unsupported'); if (profile.models?.[id]) refuse('native-model-enforcement-unavailable'); } else { if (!['openai-completions', 'openai-responses', 'anthropic-messages', 'google-generative-ai'].includes(cfg.api)) refuse('unsupported-model-api'); const selected = profile.models?.[id] ?? cfg.models; if (!selected.length || selected.some(m => !cfg.models.includes(m))) refuse('model-not-enrolled'); models.providers[id] = { api: cfg.api, baseUrl: cfg.baseUrl, models: selected.map(model => ({ id: model })) }; if (!ref || types[id] === 'none') models.providers[id].apiKey = 'FIXTURE_NONE'; } } const scope = Object.fromEntries(['agentId', 'projectId', 'workspaceId', 'sessionId', 'executionId'].map(k => [k, r[k]])); return { fixtureOnly: true, scope, profile: r.profile, accounts, types, models, fork: r.fork ? { sourceSessionId: r.fork.sourceSessionId, accounts: { ...accounts } } : null }; } // Real keys/tokens are deliberately outside this slice's accepted input language. const marker = v => typeof v === 'string' && /^FIXTURE_[A-Z0-9_-]{1,128}$/.test(v); export function validateFixtureCredential(value, type) { if (type === 'none') { if (value !== null) refuse('invalid-fixture-credential'); return null; } if (type === 'api_key') { exact(value, ['type', 'key']); if (value.type !== type || !marker(value.key)) refuse('invalid-fixture-credential'); } else if (type === 'oauth') { exact(value, ['type', 'access', 'refresh', 'expires']); if (value.type !== type || !marker(value.access) || !marker(value.refresh) || !Number.isSafeInteger(value.expires) || value.expires < 0) refuse('invalid-fixture-credential'); } else refuse('unsupported-fixture-credential'); return structuredClone(value); }