import type { MicrosoftSecurityBaselineSetting } from '../domain/microsoft-security-baseline-analysis'; import type { EnvironmentProfile } from '../domain/gpo-snapshot-aggregation'; type TemplateContext = NonNullable['context']; /** Deliberately narrow Microsoft template naming grammar, not a fuzzy role guess. */ export function baselineTemplateContext(names: string[]): TemplateContext { const unique = [...new Set(names)]; if (unique.length !== 1) return undefined; const name = unique[0].trim(); const server = /^(?:MSFT )?Windows Server (2008 R2|2012 R2|2016|2019|2022|2025) - (Member Server|Domain Controller)$/i.exec(name); if (server) return { productFamily: 'Windows Server', productVersion: server[1].toUpperCase(), role: server[2].toLowerCase() === 'member server' ? 'Member Server' : 'Domain Controller' }; const client = /^(?:MSFT )?Windows (10|11) (RS1|1607|1703|1709|1803|1809|1903|1909|2004|20H2|21H1|21H2|22H2|23H2|24H2|25H2) - (?:Computer|User)$/i.exec(name); if (client) return { productFamily: `Windows ${client[1]}`, productVersion: client[2].toUpperCase() === 'RS1' ? '1607' : client[2].toUpperCase(), role: 'Client' }; return undefined; } export function selectBaselineTemplates(settings: MicrosoftSecurityBaselineSetting[], profile: EnvironmentProfile) { const scope = settings[0].observation.scope; const contexts = profile.contexts.filter(item => item.scope === 'mixed' || item.scope === scope); // Inventory-derived contexts and scope-only aggregation do not prove GPO applicability. if (contexts.length !== 1 || contexts[0].sourceObservationIds !== undefined) { return { settings, contextIds: [] as string[], status: 'unresolved' as const }; } const context = contexts[0]; const normalize = (value: string) => value.trim().toLowerCase(); if (!['client', 'member server', 'domain controller'].includes(normalize(context.role))) { return { settings, contextIds: [] as string[], status: 'unresolved' as const }; } // An unknown template could still apply: never drop it to manufacture a match. if (settings.some(item => !item.template?.context)) { return { settings, contextIds: [] as string[], status: 'unresolved' as const }; } const selected = settings.filter(item => { const target = item.template!.context!; return normalize(target.productFamily) === normalize(context.productFamily) && normalize(target.productVersion) === normalize(context.productVersion) && normalize(target.role) === normalize(context.role); }); return selected.length ? { settings: selected, contextIds: [context.id], status: 'selected' as const } : { settings, contextIds: [context.id], status: 'no-match' as const }; }