import type { EnvironmentObservation, EnvironmentSnapshot, ScannerReportSection, ScannerReportTable } from '../../domain/environment-context'; export type EnvironmentEvidenceGroup = 'all' | 'directory' | 'resilience' | 'inventory' | 'password-policy' | 'certificates' | 'source-metrics' | 'operating-systems'; export interface EnvironmentEvidence { sections: ScannerReportSection[]; observations: EnvironmentObservation[]; } interface SubsectionLabels { explicit: Set; rowCount: Set; } const subsectionCache = new WeakMap>(); const subsectionKey = (runId: string, fileId: string, sectionKey: string, tableIndex: number) => `${runId}\u0000${fileId}\u0000${sectionKey}\u0000${tableIndex}`; function indexedSubsections(snapshot: EnvironmentSnapshot): Map { const cached = subsectionCache.get(snapshot); if (cached) return cached; const index = new Map(); for (const observation of snapshot.observations) { const tableIndex = observation.attributes.sourceTableIndex; if (typeof tableIndex !== 'number' || !observation.sectionKey) continue; const key = subsectionKey(observation.scanRunId, observation.importFileId, observation.sectionKey, tableIndex); let labels = index.get(key); if (!labels) { labels = { explicit: new Set(), rowCount: new Set() }; index.set(key, labels); } const explicit = observation.attributes.subsectionLabel; if (typeof explicit === 'string' && explicit.trim()) labels.explicit.add(explicit.trim()); if (/\.row_count$/.test(observation.property) && observation.subject.label?.trim()) labels.rowCount.add(observation.subject.label.trim()); } subsectionCache.set(snapshot, index); return index; } /** Retained native subsection headings are keyed by source table within one exact report section. */ export function environmentTableSubsection( snapshot: EnvironmentSnapshot, section: ScannerReportSection, table: ScannerReportTable ): string | undefined { if (table.sourceTableIndex === undefined) return undefined; const labels = indexedSubsections(snapshot).get(subsectionKey( section.scanRunId, section.importFileId, section.sectionKey, table.sourceTableIndex)); if (!labels) return undefined; if (labels.explicit.size) return labels.explicit.size === 1 ? [...labels.explicit][0] : undefined; return labels.rowCount.size === 1 ? [...labels.rowCount][0] : undefined; } /** A section labelled with its first subsection must not appear to own all later siblings. */ export function environmentSectionTitle(snapshot: EnvironmentSnapshot, section: ScannerReportSection): string { const subsectionLabels = new Set(section.tables .map(table => environmentTableSubsection(snapshot, section, table)).filter((label): label is string => Boolean(label))); if (subsectionLabels.size > 1 && subsectionLabels.has(section.label)) { const fromKey = section.sectionKey.replace(/^section/i, '').replace(/([a-z0-9])([A-Z])/g, '$1 $2').trim(); if (fromKey && fromKey.toLocaleLowerCase('en-US') !== section.label.toLocaleLowerCase('en-US')) return fromKey; } return section.label || section.sectionKey; } const osTable = (table: ScannerReportTable) => /operating\s+systems?/i.test(table.name); const resilienceProperty = /^(?:backup\.|laps\.|kerberos\.krbtgt\.|logging\.wef\.|domain\.recycle_bin_enabled$)/i; const inventorySection = /^section(?:UserInformation|ComputerInformation|AdminGroups|Trustsdetails|ControlPathsAnalysis|neverexpiresuser|enabledtrusteddelegationcomputer)$/i; function observationInGroup(observation: EnvironmentObservation, group: EnvironmentEvidenceGroup): boolean { const property = observation.property; const section = observation.sectionKey ?? ''; if (group === 'operating-systems') return /^operating_system\./i.test(property) || /operating\s+systems?/i.test(String(observation.attributes.tableName ?? '')); if (group === 'resilience') return resilienceProperty.test(property); if (group === 'password-policy') return /^password_policy\./i.test(property) || /^sectionPasswordPolicies$/i.test(section); if (group === 'certificates') return /^pki\./i.test(property) || /^sectionPKI$/i.test(section); if (group === 'directory') return /^(?:domain\.|forest\.|entra\.)/i.test(property) || /^section(?:DomainInformation|Infrastructure)$/i.test(section); if (group === 'inventory') return /^(?:host\.|trust\.|user\.|computer\.|domain\.controller_count$)/i.test(property) || inventorySection.test(section); if (group === 'source-metrics') return /^(?:report\.|risk\.|purple_knight\.|benchmark\.|maester\.|entra\.tenant_(?:id|name)$)/i.test(property) || /^(?:report-overview|purple-knight-assessment-summary)$/i.test(section); return true; } function sectionForGroup(section: ScannerReportSection, group: EnvironmentEvidenceGroup): ScannerReportSection | undefined { if (group === 'all') return section; const key = section.sectionKey; if (group === 'directory' && /^section(?:DomainInformation|Infrastructure)$/i.test(key)) return section; if (group === 'inventory' && inventorySection.test(key)) return section; if (group === 'password-policy' && /^sectionPasswordPolicies$/i.test(key)) return section; if (group === 'certificates' && /^sectionPKI$/i.test(key)) return section; if (group === 'source-metrics' && /^(?:report-overview|purple-knight-assessment-summary)$/i.test(key)) return section; if (group === 'operating-systems' && /^sectionComputerInformation$/i.test(key)) { const tables = section.tables.filter(osTable); return tables.length ? { ...section, tables, prose: undefined } : undefined; } if (group === 'resilience' && /^sectionAnomalies$/i.test(key)) { const tables = section.tables.filter(table => /backup|laps|krbtgt|event\s+forwarding|\bwef\b|recycle/i.test(table.name)); return tables.length ? { ...section, tables, prose: undefined } : undefined; } return undefined; } /** Read-only, source-backed projection. Every retained row remains available in `all`. */ export function environmentEvidenceForGroup( snapshot: EnvironmentSnapshot | undefined, group: EnvironmentEvidenceGroup ): EnvironmentEvidence { if (!snapshot) return { sections: [], observations: [] }; return { sections: snapshot.sections.flatMap(section => { const selected = sectionForGroup(section, group); return selected ? [selected] : []; }), observations: group === 'all' ? snapshot.observations : snapshot.observations.filter(observation => observationInGroup(observation, group)) }; }