// registry-migrate.mjs — One-shot ai-foundry -> foundry URL remap + reserved // schema migration. Pure functions (no I/O) so they are unit-testable; the // migrate-ai-foundry.mjs script wires backup + atomic write around them. // // Microsoft rebranded "Azure AI Foundry" -> "Microsoft Foundry"; doc URLs moved // /azure/ai-foundry/ -> /azure/foundry/. Two pages probed in Sesjon 1 are DEAD // on a naive prefix swap and need explicit targets. const AI_FOUNDRY_PREFIX = 'https://learn.microsoft.com/azure/ai-foundry/'; const FOUNDRY_PREFIX = 'https://learn.microsoft.com/azure/foundry/'; // Probed DEAD pages (Sesjon 1): naive prefix swap 404s, these are the live targets. const DEAD_REMAPS = { 'https://learn.microsoft.com/azure/ai-foundry/concepts/evaluation-evaluators': 'https://learn.microsoft.com/azure/foundry/concepts/built-in-evaluators', 'https://learn.microsoft.com/azure/ai-foundry/agent-service': 'https://learn.microsoft.com/azure/foundry/agents/overview', }; /** * Remap a single registry URL key. DEAD overrides win; otherwise prefix-swap * ai-foundry -> foundry; non-ai-foundry URLs are returned unchanged. * @param {string} url * @returns {string} */ export function remapKey(url) { if (DEAD_REMAPS[url]) return DEAD_REMAPS[url]; if (url.startsWith(AI_FOUNDRY_PREFIX)) { return FOUNDRY_PREFIX + url.slice(AI_FOUNDRY_PREFIX.length); } return url; } /** * Migrate a registry: remap ai-foundry keys (resetting their tracking state so * the next poll re-evaluates them), merge any target collisions, and ensure the * reserved schema fields (authority_source, course) exist on every entry. * Pure — does not mutate the input. * @param {object} registry * @returns {{registry: object, stats: {remapped: number, deadRemapped: number, collisions: number}}} */ export function migrateRegistry(registry) { const out = { ...registry, urls: {} }; const stats = { remapped: 0, deadRemapped: 0, collisions: 0 }; for (const [url, entry] of Object.entries(registry.urls)) { const target = remapKey(url); const isRemap = target !== url; // Normalize every entry to the full schema (reserved fields included). const base = { sitemap_lastmod: entry.sitemap_lastmod ?? null, reference_files: entry.reference_files ?? [], status: entry.status ?? 'unpolled', authority_source: entry.authority_source ?? null, course: entry.course ?? null, }; let next = base; if (isRemap) { stats.remapped++; if (DEAD_REMAPS[url]) stats.deadRemapped++; // The URL changed — old poll state is invalid; let the next poll decide. next = { ...base, sitemap_lastmod: null, status: 'unpolled' }; } if (out.urls[target]) { // Two source keys collapsed onto one target — union the reference files. stats.collisions++; const merged = [ ...new Set([...out.urls[target].reference_files, ...next.reference_files]), ].sort(); out.urls[target] = { ...out.urls[target], ...next, reference_files: merged }; } else { out.urls[target] = next; } } return { registry: out, stats }; }