import type { Assessment, CanonicalAssessmentModel } from '../../domain/canonical-model'; import type { ImportedDataset } from '../../domain/dataset'; import type { StoredPolicyEvaluation } from '../../domain/policy-evaluation'; import { selectCurrentPolicyEvaluations, type UnifiedResultQuery, type UnifiedResultQueryResult } from '../../domain/unified-result'; import type { PolicyAnalysisOverview } from '../../domain/policy-analysis-overview'; import { queryAllUnifiedResults } from '../../exports/unified-result-jsonl'; import { canonicalModelsToDataset } from './canonical-model-to-dataset'; import { appendPolicyObservationsToDataset } from './policy-observations-to-dataset'; import type { StoredMicrosoftSecurityBaselineAnalysis } from '../../domain/microsoft-security-baseline-analysis'; import { buildPolicyGuidance } from '../../profiles/group-policy/guidance'; import { canonicalSecurityScores } from '../../scoring/canonical-security-score'; import { defaultSecurityScoringPolicy, type SecurityScanScore } from '../../domain/security-scoring'; export interface CanonicalProjectReader { listProjectAssessments(projectId: string): Promise; loadAssessmentModel(assessmentId: string): Promise; queryUnifiedResults?(query?: UnifiedResultQuery): Promise; listPolicyEvaluations?(projectId: string): Promise<{ records: StoredPolicyEvaluation[] }>; listCurrentPolicyEvaluationsForMetrics?(projectId: string): Promise<{ records: StoredPolicyEvaluation[] }>; getPolicyAnalysisOverview?(projectId: string): Promise>; listMicrosoftSecurityBaselineAnalyses?(baselineId: string): Promise<{ records: StoredMicrosoftSecurityBaselineAnalysis[] }>; } export interface CanonicalProjectWorkspace { models: CanonicalAssessmentModel[]; dataset: ImportedDataset | null; assessmentIds: string[]; } export interface CanonicalWorkspaceLoadOptions { includeHistoricalScores?: boolean; includeBaselineGuidance?: boolean; summaryOnly?: boolean; } export function canonicalModelForScanRuns( model: CanonicalAssessmentModel, scanRunIds: string[] ): CanonicalAssessmentModel | null { const selectedIds = new Set(scanRunIds); const scanRuns = model.scanRuns.filter(run => selectedIds.has(run.id)); if (!scanRuns.length) return null; const importFiles = model.importFiles.filter(file => selectedIds.has(file.scanRunId)); const fileIds = new Set(importFiles.map(file => file.id)); const sourceTables = model.sourceTables.filter(table => fileIds.has(table.importFileId)); const tableIds = new Set(sourceTables.map(table => table.id)); const findingOccurrences = model.findingOccurrences.filter(occurrence => selectedIds.has(occurrence.scanRunId)); const occurrenceIds = new Set(findingOccurrences.map(occurrence => occurrence.id)); const definitionIds = new Set(findingOccurrences.map(occurrence => occurrence.definitionId)); return { ...model, referenceRuns: model.referenceRuns?.filter(run => selectedIds.has(run.scanRunId)), scanRuns, importFiles, sourceTables, findingDefinitions: model.findingDefinitions.filter(definition => definitionIds.has(definition.id)), findingOccurrences, evidence: model.evidence.filter(record => occurrenceIds.has(record.occurrenceId)), reviews: model.reviews.filter(review => review.target.kind === 'definition' ? definitionIds.has(review.target.definitionId) : occurrenceIds.has(review.target.occurrenceId)), diagnostics: model.diagnostics.filter(diagnostic => fileIds.has(diagnostic.importFileId) && (diagnostic.sourceTableId === undefined || tableIds.has(diagnostic.sourceTableId))), environmentObservations: model.environmentObservations?.filter(item => selectedIds.has(item.scanRunId) && fileIds.has(item.importFileId)), reportSections: model.reportSections?.filter(item => selectedIds.has(item.scanRunId) && fileIds.has(item.importFileId)) }; } export function canonicalModelForScanRun(model: CanonicalAssessmentModel, scanRunId: string): CanonicalAssessmentModel | null { return canonicalModelForScanRuns(model, [scanRunId]); } export function currentPolicyEvaluationsForWork( records: StoredPolicyEvaluation[], observationIds: Set, currentEvaluationIds?: ReadonlySet ): StoredPolicyEvaluation[] { // The backend owns profile/identity/snapshot supersession. Result coverage // (in particular the referenced backup subset) is not a source identity. const allowedAggregations = currentEvaluationIds === undefined ? undefined : new Set(records .filter(record => currentEvaluationIds.has(record.run.id)).map(record => record.run.aggregationRunId)); return selectCurrentPolicyEvaluations(records.filter(record => (allowedAggregations === undefined || allowedAggregations.has(record.run.aggregationRunId)) && record.results.some(result => result.observationIds.some(id => observationIds.has(id))) && record.results.every(result => result.observationIds.every(id => observationIds.has(id))) ), { allowPreviousVersionForDisplay: true }); } export async function loadCanonicalProjectWorkspace( reader: CanonicalProjectReader, projectId: string, scanRunIds?: string[], selectedAssessmentIds?: string[], options: CanonicalWorkspaceLoadOptions = {} ): Promise { const assessments = await reader.listProjectAssessments(projectId); const selectedAssessments = selectedAssessmentIds ? assessments.filter(assessment => selectedAssessmentIds.includes(assessment.id)) : assessments; // In a selected timeslot, policy observation lookup needs only the assessment // IDs. Start it alongside model loading; both can be expensive for large scans. const selectedPolicyResults = selectedAssessmentIds && reader.queryUnifiedResults && selectedAssessments.length ? queryAllUnifiedResults( query => reader.queryUnifiedResults!(query), { audience: 'internal-analysis', projectId, assessmentIds: selectedAssessments.map(item => item.id), sourceKind: ['policy-observation'] } ) : undefined; const loadedModels = await Promise.all(selectedAssessments.map(assessment => reader.loadAssessmentModel(assessment.id))); const models = scanRunIds?.length ? loadedModels.map(model => canonicalModelForScanRuns(model, scanRunIds)) .filter((model): model is CanonicalAssessmentModel => model !== null) : loadedModels; // A dated timeslot can contain scanner runs and GPO results stored in separate // assessments. Scanner scoping deliberately removes models without one of the // selected runs, but it must not also erase the explicit assessment scope used // by policy observations. Otherwise the same GPO rows disappear from Work and // Client Info as soon as that mixed timeslot is opened. const assessmentIds = [...new Set((selectedAssessmentIds ? selectedAssessments : models).map(item => 'assessment' in item ? item.assessment.id : item.id))]; const policyObservations = selectedPolicyResults ? await selectedPolicyResults : reader.queryUnifiedResults && assessmentIds.length ? await queryAllUnifiedResults( query => reader.queryUnifiedResults!(query), { audience: 'internal-analysis', projectId, assessmentIds, sourceKind: ['policy-observation'] } ) : []; const scannerDataset = canonicalModelsToDataset(models, loadedModels, { summaryOnly: options.summaryOnly }); if (scannerDataset?.securityScoring && options.includeHistoricalScores !== false) { const currentIds = new Set(assessmentIds); const currentDates = models.map(model => Date.parse(model.assessment.effectiveAt ?? model.assessment.createdAt)).filter(Number.isFinite); const cutoff = currentDates.length ? Math.min(...currentDates) : undefined; const historical = cutoff === undefined ? [] : assessments.filter(item => !currentIds.has(item.id) && Date.parse(item.effectiveAt ?? item.createdAt) < cutoff); const loadedById = new Map(loadedModels.map(model => [model.assessment.id, model])); const lastKnown = new Map(); // Workspace-owned, read-only projection. Missing later scans retain their last // known open obligations; historical scores are never relabelled as current. for (const assessment of historical.sort((a, b) => Date.parse(a.effectiveAt ?? a.createdAt) - Date.parse(b.effectiveAt ?? b.createdAt))) { try { const historicModel = loadedById.get(assessment.id) ?? await reader.loadAssessmentModel(assessment.id); for (const scan of canonicalSecurityScores([historicModel])) { lastKnown.set(scan.comparisonKey ?? `unverified:${scan.toolId}:${scan.contentSha256 ?? scan.id}`, scan); } } catch { scannerDataset.securityScoring.historyUnavailable = true; } } if (historical.length) scannerDataset.securityScoring.baseline = [...lastKnown.values()]; } const currentMetricEvaluations = options.summaryOnly && reader.listCurrentPolicyEvaluationsForMetrics; const [evaluationList, policyOverview] = policyObservations.length && reader.listPolicyEvaluations ? await Promise.all([ (currentMetricEvaluations ? currentMetricEvaluations(projectId) : reader.listPolicyEvaluations(projectId)), currentMetricEvaluations ? undefined : reader.getPolicyAnalysisOverview?.(projectId) ]) : []; const evaluations = currentPolicyEvaluationsForWork( evaluationList?.records ?? [], new Set(policyObservations.map(result => result.sourceResultId)), policyOverview ? new Set(policyOverview.current.evaluationRunIds) : undefined ); const projectName = models[0]?.project.name ?? assessments.find(item => assessmentIds.includes(item.id))?.name ?? projectId; const guidance = buildPolicyGuidance(evaluations); // One request per referenced baseline, sequential to bound catalog memory. Optional // documentation must not prevent opening otherwise usable assessment results. if (options.includeBaselineGuidance !== false && reader.listMicrosoftSecurityBaselineAnalyses) { const byBaseline = new Map(); for (const evaluation of evaluations) for (const baselineId of new Set(evaluation.results.map(result => result.baselineId))) { byBaseline.set(baselineId, [...(byBaseline.get(baselineId) ?? []), { ...evaluation, results: evaluation.results.filter(result => result.baselineId === baselineId) }]); } for (const [baselineId, records] of byBaseline) { try { const response = await reader.listMicrosoftSecurityBaselineAnalyses(baselineId); for (const [id, value] of buildPolicyGuidance(records, response.records)) guidance.set(id, value); } catch { // A failed documentation request is distinct from absent source metadata. for (const record of records) for (const result of record.results) { const retained = guidance.get(result.id); if (retained) guidance.set(result.id, { ...retained, descriptionLoadFailed: true }); } } } } const dataset = appendPolicyObservationsToDataset(scannerDataset, projectName, policyObservations, evaluations, guidance); if (dataset && !dataset.securityScoring) dataset.securityScoring = { policy: models[0]?.assessment.scoringPolicy ?? defaultSecurityScoringPolicy(), scans: [] }; return { models, assessmentIds, dataset }; }