import type { Assessment, CanonicalAssessmentModel } from '../src/domain/canonical-model'; import type { StoredGpoBackupInstance } from '../src/domain/gpo-analysis-persistence'; import type { StoredPolicyEvaluation } from '../src/domain/policy-evaluation'; import type { PolicyAnalysisOverview } from '../src/domain/policy-analysis-overview'; import type { UnifiedResultQuery, UnifiedResultQueryResult } from '../src/domain/unified-result'; import { queryAllUnifiedResults } from '../src/exports/unified-result-jsonl'; import { buildPolicyWorkResults, type PolicyWorkResult } from '../src/evaluation/policy-work-results'; import { canonicalModelForScanRuns, currentPolicyEvaluationsForWork } from '../src/features/project-import/load-canonical-project-workspace'; import { RepositoryNotFoundError } from '../src/persistence/repository-errors'; /** One timeslot as Data Hub groups it: its assessments and the scanner runs selected inside them. */ export interface TimeslotSelection { assessmentIds: string[]; scanRunIds: string[]; } export interface TimeslotInputReader { listAssessments(projectId: string): Promise; loadAssessmentModel(assessmentId: string): Promise; queryUnifiedResults(query?: UnifiedResultQuery): Promise; listPolicyEvaluations(projectId: string): Promise; listGpoBackupInstances(projectId: string): Promise; getPolicyAnalysisOverview?(projectId: string): Promise>; } export interface TimeslotComparisonInput { assessments: Assessment[]; models: CanonicalAssessmentModel[]; scanRunIds: string[]; gpoResults: PolicyWorkResult[]; /** Latest GPO backup creation time; undefined unless every contributing backup states one. */ gpoObservedAt?: string; } /** * Core-owned adapter: builds one timeslot exactly as Work reads it (scanner models scoped to the * selected runs, GPO settings through the same current-evaluation selection and Work verdict * builder). Only the resulting records reach the Comparison Engine. Import times are never used as * observation times. */ export async function loadTimeslotComparisonInput( reader: TimeslotInputReader, projectId: string, selection: TimeslotSelection ): Promise { const projectAssessments = await reader.listAssessments(projectId); const assessments = selection.assessmentIds.map(id => { const assessment = projectAssessments.find(item => item.id === id); if (!assessment) throw new RepositoryNotFoundError(`Assessment ${id} is not in this project.`); return assessment; }); const loaded = await Promise.all(assessments.map(async assessment => { const model = await reader.loadAssessmentModel(assessment.id); if (!model || model.project.id !== projectId) throw new RepositoryNotFoundError(`Assessment ${assessment.id} is not in this project.`); return model; })); const models = loaded.map(model => canonicalModelForScanRuns(model, selection.scanRunIds)) .filter((model): model is CanonicalAssessmentModel => model !== null); const availableRuns = new Set(models.flatMap(model => model.scanRuns.map(run => run.id))); const missingRun = selection.scanRunIds.find(id => !availableRuns.has(id)); if (missingRun) throw new RepositoryNotFoundError(`Scan run ${missingRun} is not in the selected assessments.`); const observations = await queryAllUnifiedResults(query => reader.queryUnifiedResults(query), { audience: 'internal-analysis', projectId, assessmentIds: selection.assessmentIds, sourceKind: ['policy-observation'] }); let gpoResults: PolicyWorkResult[] = []; let gpoObservedAt: string | undefined; if (observations.length) { const [evaluations, overview, backups] = await Promise.all([ reader.listPolicyEvaluations(projectId), reader.getPolicyAnalysisOverview?.(projectId), reader.listGpoBackupInstances(projectId) ]); gpoResults = buildPolicyWorkResults(observations, currentPolicyEvaluationsForWork(evaluations, new Set(observations.map(item => item.sourceResultId)), overview ? new Set(overview.current.evaluationRunIds) : undefined)); const backupIds = new Set(observations.flatMap(item => item.policyBackupInstanceId ?? [])); const times = backups.filter(item => backupIds.has(item.instance.id)).map(item => item.instance.backupCreatedAt); if (backupIds.size && times.length === backupIds.size && times.every(time => time && Number.isFinite(Date.parse(time)))) { gpoObservedAt = new Date(Math.max(...times.map(time => Date.parse(time!)))).toISOString(); } } return { assessments, models, scanRunIds: [...selection.scanRunIds], gpoResults, ...(gpoObservedAt ? { gpoObservedAt } : {}) }; }