import type { Assessment, AssessmentProject, CanonicalExposureState, CanonicalOutcome, CanonicalFindingStatus, CanonicalResultType, CanonicalSeverity, CanonicalTechnicalExecutionState, FindingDefinition, FindingOccurrence, FindingReview, ImportFileRecord, JsonValue, ReviewPriority, ScanRun, SourcePosition } from './canonical-model'; import type { PolicyFindingOccurrence } from './policy-finding'; import type { EnvironmentCollectionState, EnvironmentObservation, EnvironmentValueType } from './environment-context'; import type { ImportArtifact, ImportProcessingRun } from './ingestion-persistence'; import type { StoredGpoBackupInstance, StoredGpoPolicyAnalysis, StoredGpoPolicyIdentityResolution } from './gpo-analysis-persistence'; import type { GpoPolicyObservation, GpoPreferenceContext } from './gpo-policy'; import { POLICY_COMPARATOR_CATALOG, POLICY_EVALUATION_ENGINE, type PolicyEvaluationResult, type PolicyEvaluationRun, type StoredPolicyEvaluation } from './policy-evaluation'; import type { StoredPolicySnapshotAggregation } from './gpo-snapshot-aggregation'; import { deriveExposureState, deriveIsIoe, deriveResultType } from './result-contract-mapping'; import { isDeviation } from './deviation-predicate'; import { POLICY_CLASSIFICATION_LABELS } from './policy-status-labels'; import { policyComparisonDisposition } from './policy-comparison-disposition'; import { severityRank } from './severity-order'; import { processingReadiness, type ProcessingReadinessStatus } from './processing-compatibility'; import { discloseSupplementalObservation, type ResultDbSupplementalDisclosureClass } from './result-db-supplemental-disclosure'; import type { ResultDbDisclosureTrace } from './result-db-trace'; export type UnifiedResultSourceKind = 'scanner-finding' | 'policy-evaluation' | 'policy-observation' | 'environment-observation'; export type UnifiedResultAudience = 'result-db' | 'internal-analysis'; export type UnifiedOutcome = CanonicalOutcome; export type PolicyResultClassification = | 'misconfigured' | 'not-configured' | 'match' | 'conflict' | 'duplicate' | 'review-required'; export type TechnicalExecutionState = CanonicalTechnicalExecutionState; export type UnifiedReviewStatus = 'unreviewed' | 'open' | 'in-progress' | 'resolved'; export type UnifiedReviewDisposition = 'accepted' | 'deferred' | 'not-relevant'; export type UnifiedLifecycleState = 'new' | 'persistent' | 'resolved' | 'reoccurred' | 'unknown'; export type UnifiedEffectiveState = | 'confirmed_configured_deviation' | 'configured_match' | 'not_applicable' | 'unsupported' | 'unresolved_identity' | 'incomplete_observation' | 'missing_baseline_recommendation' | 'not_evaluated' | 'unknown_effective_state'; export interface UnifiedResultCategory { namespace: string; categoryCode: string; categoryLabel: string; categoryType: 'source' | 'canonical'; isPrimary: boolean; sourceTool?: string; sourceProfileId?: string; sourceProfileVersion?: string; } export interface UnifiedResultFrameworkReference { namespace: string; value: string; sourceFieldKey?: string; sourceLabel?: string; } export interface UnifiedResultProjection { resultProjectionId: string; sourceKind: UnifiedResultSourceKind; sourceResultId: string; projectId: string; assessmentId?: string; collectionRunId: string; scanRunId?: string; gpoSnapshotId?: string; evaluationRunId?: string; sourceTool?: string; /** Controlled scanner scope used to distinguish products sharing one canonical profile. */ sourceToolScope?: 'active-directory' | 'entra-id' | 'combined'; sourceToolVersion?: string; sourceProfileId?: string; sourceProfileVersion?: string; mappingRuleVersion?: string; normalizationRuleVersion?: string; presentationVersion?: string; processingCompatibilityKey?: string; processingStatus?: ProcessingReadinessStatus; reprocessingAvailable?: boolean; sourceRuleId?: string; canonicalDefinitionId?: string; importArtifactId?: string; sourceViewId?: string; sourceLocator?: SourcePosition; environmentProperty?: string; environmentSubjectType?: string; environmentSubjectIdentity?: string; environmentValueType?: EnvironmentValueType; environmentState?: EnvironmentCollectionState; environmentOriginalValue?: JsonValue; environmentNormalizedValue?: JsonValue; environmentSectionKey?: string; environmentAttributes?: Record; supplementalDisclosurePolicyVersion?: string; supplementalDisclosureClass?: ResultDbSupplementalDisclosureClass; policyScope?: GpoPolicyObservation['scope']; policyDeliveryMechanism?: GpoPolicyObservation['deliveryMechanism']; policyState?: GpoPolicyObservation['state']; policyIdentityStatus?: GpoPolicyIdentityResolutionStatus; policyIdentityKey?: string; policyValues?: JsonValue[]; policyDiagnosticCodes?: string[]; policyBackupInstanceId?: string; /** Link status of the contributing GPO backup from its own GPReport.xml; absent when not retained. */ policyBackupLinkStatus?: 'linked' | 'not-linked'; /** * Retained Group Policy Preference context. A preference is an instruction with its own * action, item-level targeting and apply-once semantics, so it is never reduced to a * comparable policy setting; these fields only make that retained context readable. */ policyPreferenceCse?: GpoPreferenceContext['cse']; policyPreferenceAction?: GpoPreferenceContext['operation']; policyPreferenceTargeting?: GpoPreferenceContext['targeting']['status']; /** Retained native identities, distinct from ASSESSOR's backup-instance ID. */ policyGpoId?: string; policyNativeBackupId?: string; /** Retained instruction order within its source, never cross-GPO precedence. */ policySourceSequence?: number; policyBaselineId?: string; policyBaselineLabel?: string; policyBaselineValue?: JsonValue; policyBaselineVariantAssessment?: PolicyEvaluationResult['baselineVariantAssessment']; policyActualValue?: JsonValue; policyComparisonRelation?: string; policyCustomerState?: PolicyEvaluationResult['customerState']; policyClassification?: PolicyResultClassification; policySourceCount?: number; title: string; description?: string; recommendation?: string; affectedAsset?: string; score?: number; severityOriginal: JsonValue; severityNormalized: CanonicalSeverity; outcomeOriginal: JsonValue; outcomeNormalized: UnifiedOutcome; technicalExecutionState: TechnicalExecutionState; /** * RESULT_CONTRACT-Facetten (docs/RESULT_CONTRACT.md), additiv realisiert * über src/domain/result-contract-mapping.ts. exposureState existiert nur * bei resultType = 'exposure_finding'. */ resultType: CanonicalResultType; isIoe: boolean; /** * Abgeleitet ueber src/domain/deviation-predicate.ts, nicht persistiert. * Bewusst breiter als `isIoe`, damit Funde ohne festgestellte Exposition * (Outcome `warning`) nicht aus den Abweichungsansichten fallen. */ isDeviation: boolean; exposureState?: CanonicalExposureState; effectiveState?: UnifiedEffectiveState; lifecycleState: UnifiedLifecycleState; mappingConfidence: number; applicabilityState?: string; completenessState?: string; scopeState?: 'known' | 'unknown'; effectivenessVerification?: 'verified' | 'not-verified'; scopeConfidence?: number; /** Client-owned review state; internal analysis only, withheld by the Result DB disclosure policy. */ reviewStatus?: UnifiedReviewStatus; reviewDisposition?: UnifiedReviewDisposition; reviewPriority?: ReviewPriority; reviewOwner?: string; reviewDueDate?: string; sourceObservedAt?: string; importedAt: string; evaluatedAt?: string; createdAt: string; categories: UnifiedResultCategory[]; frameworkReferences: UnifiedResultFrameworkReference[]; /** Result DB only: content-free redaction trace stored with the materialized row. */ disclosureTrace?: ResultDbDisclosureTrace; } export interface UnifiedResultQuery { audience?: UnifiedResultAudience; projectId?: string; assessmentId?: string; assessmentIds?: string[]; scanRunId?: string; scanRunIds?: string[]; evaluationRunId?: string; sourceKind?: UnifiedResultSourceKind[]; sourceTool?: string[]; severity?: CanonicalSeverity[]; outcome?: UnifiedOutcome[]; technicalExecutionState?: TechnicalExecutionState[]; resultType?: CanonicalResultType[]; policyClassification?: PolicyResultClassification[]; isIoe?: boolean; deviation?: boolean; mappingRuleVersion?: string[]; sourceProfileVersion?: string[]; processingStatus?: ProcessingReadinessStatus[]; reviewDisposition?: UnifiedReviewDisposition[]; lifecycleState?: UnifiedLifecycleState[]; categoryNamespace?: string[]; categoryValue?: string[]; affectedAsset?: string; environmentState?: EnvironmentCollectionState[]; environmentProperty?: string[]; environmentSubjectType?: string[]; search?: string; observedFrom?: string; observedTo?: string; offset?: number; limit?: number; } export interface UnifiedResultQueryResult { total: number; items: UnifiedResultProjection[]; } export interface UnifiedResultFacetValue { value: string; label: string; count: number; namespace?: string; } export interface UnifiedResultFacet { id: 'sourceKind' | 'sourceTool' | 'severity' | 'outcome' | 'technicalExecutionState' | 'reviewStatus' | 'reviewDisposition' | 'lifecycleState' | 'effectiveState' | 'category' | 'resultType' | 'isIoe' | 'deviation' | 'mappingRuleVersion' | 'sourceProfileVersion' | 'processingStatus' | 'environmentState' | 'environmentProperty' | 'environmentSubjectType' | 'policyClassification'; label: string; values: UnifiedResultFacetValue[]; } export interface UnifiedResultFacetResult { total: number; facets: UnifiedResultFacet[]; } export interface ScannerUnifiedResultSource { project: AssessmentProject; assessment: Assessment; scanRun: ScanRun; importFile: ImportFileRecord; definition: FindingDefinition; occurrence: FindingOccurrence; reviews: FindingReview[]; } function scannerSourceToolScope(source: ScannerUnifiedResultSource): UnifiedResultProjection['sourceToolScope'] { if (source.occurrence.sourceIdentity.tool.id !== 'purple-knight') return undefined; const name = source.scanRun.tool.name.trim().toLocaleLowerCase('en-US'); const activeDirectory = name.includes('active directory'); const entra = name.includes('entra'); if (activeDirectory && entra) return 'combined'; if (entra) return 'entra-id'; if (activeDirectory) return 'active-directory'; return undefined; } export function resultSourceToolLabel(value?: string): string { const labels: Record = { 'purple-knight-ad': 'Purple Knight for Active Directory', 'purple-knight-entra': 'Purple Knight for Entra ID', 'purple-knight-combined': 'Purple Knight for Active Directory and Entra ID' }; return value ? labels[value] ?? value : '–'; } export interface PolicyUnifiedResultSource { comparison?: PolicyEvaluationResult; definition: FindingDefinition; occurrence: PolicyFindingOccurrence; } export interface EnvironmentUnifiedResultSource { project: AssessmentProject; assessment: Assessment; scanRun: ScanRun; importFile: ImportFileRecord; observation: EnvironmentObservation; } type GpoPolicyIdentityResolutionStatus = StoredGpoPolicyIdentityResolution['resolution']['status']; export interface PolicyObservationUnifiedResultSource { artifact: ImportArtifact; backup: StoredGpoBackupInstance; observation: GpoPolicyObservation; resolution?: StoredGpoPolicyIdentityResolution; } export function outcomeFromLegacyStatus(status: CanonicalFindingStatus): UnifiedOutcome { if (status === 'failed') return 'fail'; if (status === 'passed') return 'pass'; if (status === 'error') return 'execution_error'; if (status === 'not-run') return 'not_run'; if (status === 'not-applicable') return 'not_applicable'; if (status === 'manual') return 'informational'; if (status === 'investigate') return 'warning'; return 'unknown'; } export function technicalStateFromLegacyStatus(status: CanonicalFindingStatus): TechnicalExecutionState { if (status === 'error') return 'failed'; if (status === 'not-run') return 'not-run'; if (status === 'unknown') return 'unknown'; return 'completed'; } function matchingReview(source: ScannerUnifiedResultSource): FindingReview | undefined { return source.reviews .filter(review => review.target.kind === 'occurrence' ? review.target.occurrenceId === source.occurrence.id : review.target.definitionId === source.definition.id) .sort((left, right) => { const leftOccurrence = left.target.kind === 'occurrence' ? 1 : 0; const rightOccurrence = right.target.kind === 'occurrence' ? 1 : 0; return rightOccurrence - leftOccurrence || right.revision - left.revision || right.id.localeCompare(left.id); })[0]; } function reviewDimensions(review?: FindingReview): { status: UnifiedReviewStatus; disposition?: UnifiedReviewDisposition; } { if (!review || review.workflowStatus === 'unreviewed') return { status: 'unreviewed' }; if (review.workflowStatus === 'open') return { status: 'open' }; if (review.workflowStatus === 'in-progress') return { status: 'in-progress' }; if (review.workflowStatus === 'accepted') return { status: 'resolved', disposition: 'accepted' }; if (review.workflowStatus === 'deferred') return { status: 'resolved', disposition: 'deferred' }; if (review.workflowStatus === 'not-relevant') return { status: 'resolved', disposition: 'not-relevant' }; return { status: 'resolved' }; } function categoryStrings(value: JsonValue): string[] { if (typeof value === 'string') return value.trim() ? [value.trim()] : []; if (Array.isArray(value)) return value.flatMap(categoryStrings); return []; } export function scannerResultCategories( definition: FindingDefinition, occurrence: FindingOccurrence, importFile: ImportFileRecord ): UnifiedResultCategory[] { const values = [ ...(definition.category?.trim() ? [definition.category.trim()] : []), ...occurrence.fieldMappings .filter(mapping => mapping.targetRole === 'category') .flatMap(mapping => categoryStrings(mapping.normalizedValue)) ]; const unique = [...new Set(values)]; return unique.map((value, index) => ({ namespace: occurrence.sourceIdentity.tool.id, categoryCode: value, categoryLabel: value, categoryType: 'source', isPrimary: index === 0, sourceTool: occurrence.sourceIdentity.tool.id, sourceProfileId: importFile.processedWith.profileId, sourceProfileVersion: importFile.processedWith.profileVersion })); } export function scannerResultFrameworkReferences(occurrence: FindingOccurrence): UnifiedResultFrameworkReference[] { return occurrence.fieldMappings.filter(mapping => mapping.targetRole === 'framework-reference') .flatMap(mapping => { const sourceFieldKey = mapping.sourceFieldKeys[0]; const sourceLabel = occurrence.originalFields.find(field => field.key === sourceFieldKey)?.label; const values = typeof mapping.normalizedValue === 'string' ? [mapping.normalizedValue] : Array.isArray(mapping.normalizedValue) ? mapping.normalizedValue.filter((value): value is string => typeof value === 'string') : []; return values.filter(value => value.trim() !== '').map(value => ({ namespace: occurrence.sourceIdentity.tool.id, value, sourceFieldKey, sourceLabel })); }); } function mappingConfidence(occurrence: FindingOccurrence): number { const scores = [ occurrence.severity.provenance.confidence.score, occurrence.status.provenance.confidence.score, occurrence.outcome?.provenance.confidence.score, occurrence.technicalExecutionState?.provenance.confidence.score, ...occurrence.fieldMappings.map(mapping => mapping.provenance.confidence.score) ].filter((score): score is number => score !== undefined && Number.isFinite(score)); return scores.length ? Math.min(...scores) : 0; } export function createScannerUnifiedResult(source: ScannerUnifiedResultSource): UnifiedResultProjection { const review = matchingReview(source); const dimensions = reviewDimensions(review); const outcomeNormalized = source.occurrence.outcome?.normalizedValue ?? outcomeFromLegacyStatus(source.occurrence.status.normalizedValue ?? 'unknown'); const resultType = source.occurrence.resultType ?? deriveResultType(source.occurrence.sourceIdentity.tool.id); const exposureState = source.occurrence.exposureState ?? deriveExposureState(resultType, outcomeNormalized); const isIoe = source.occurrence.isIoe ?? deriveIsIoe(resultType, exposureState).isIoe; return { resultProjectionId: `scanner-finding:${source.occurrence.id}`, sourceKind: 'scanner-finding', sourceResultId: source.occurrence.id, projectId: source.project.id, assessmentId: source.assessment.id, collectionRunId: source.scanRun.id, scanRunId: source.scanRun.id, sourceTool: source.occurrence.sourceIdentity.tool.id, sourceToolScope: scannerSourceToolScope(source), sourceToolVersion: source.occurrence.sourceIdentity.tool.version, sourceProfileId: source.importFile.processedWith.profileId, sourceProfileVersion: source.importFile.processedWith.profileVersion, mappingRuleVersion: source.occurrence.mappingRuleVersion ?? source.importFile.processedWith.mappingVersion, normalizationRuleVersion: source.importFile.processedWith.normalizationVersion, presentationVersion: source.importFile.processedWith.presentationVersion, processingCompatibilityKey: source.importFile.processedWith.compatibilityKey, processingStatus: processingReadiness(source.importFile.processedWith.profileId, source.importFile.processedWith), reprocessingAvailable: source.importFile.sourceArtifact.kind !== 'not-retained', sourceRuleId: source.occurrence.sourceIdentity.originalRuleId ?? source.occurrence.sourceIdentity.canonicalRuleId, canonicalDefinitionId: source.definition.id, importArtifactId: source.importFile.id, sourceViewId: source.occurrence.sourceTableId, sourceLocator: source.occurrence.sourcePosition, title: source.definition.title, description: source.definition.description, recommendation: source.definition.recommendation, affectedAsset: source.occurrence.affectedAsset?.normalizedValue ?? undefined, score: source.occurrence.score?.normalizedValue ?? undefined, severityOriginal: source.occurrence.severity.originalValue, severityNormalized: source.occurrence.severity.normalizedValue ?? 'unknown', outcomeOriginal: source.occurrence.outcome?.originalValue ?? source.occurrence.status.originalValue, outcomeNormalized: outcomeNormalized, technicalExecutionState: source.occurrence.technicalExecutionState?.normalizedValue ?? technicalStateFromLegacyStatus(source.occurrence.status.normalizedValue ?? 'unknown'), resultType, isIoe, isDeviation: isDeviation(isIoe, outcomeNormalized), exposureState, lifecycleState: 'unknown', mappingConfidence: mappingConfidence(source.occurrence), reviewStatus: dimensions.status, reviewDisposition: dimensions.disposition, reviewPriority: review?.priority, reviewOwner: review?.owner, reviewDueDate: review?.dueAt, sourceObservedAt: source.occurrence.observedAt, importedAt: source.importFile.importedAt, createdAt: source.importFile.importedAt, categories: scannerResultCategories(source.definition, source.occurrence, source.importFile), frameworkReferences: scannerResultFrameworkReferences(source.occurrence) }; } export function createPolicyUnifiedResult(source: PolicyUnifiedResultSource): UnifiedResultProjection { const { definition, occurrence, comparison } = source; return { resultProjectionId: `policy-evaluation:${occurrence.id}`, sourceKind: 'policy-evaluation', sourceResultId: occurrence.id, projectId: occurrence.projectId, collectionRunId: occurrence.evaluationRunId, gpoSnapshotId: occurrence.snapshotId, evaluationRunId: occurrence.evaluationRunId, sourceTool: 'GPOs', sourceProfileId: occurrence.environmentProfileId, sourceProfileVersion: String(occurrence.environmentProfileRevision), mappingRuleVersion: 'policy-finding-factory-1.0.0', normalizationRuleVersion: 'policy-finding-factory-1.0.0', presentationVersion: 'policy-finding-factory-1.0.0', processingCompatibilityKey: 'policy-finding-canonical-v1', processingStatus: 'current', reprocessingAvailable: true, sourceRuleId: occurrence.riskRuleId, canonicalDefinitionId: definition.id, sourceViewId: occurrence.aggregationStateId, title: definition.title, description: definition.description, recommendation: definition.recommendation, severityOriginal: occurrence.severity, severityNormalized: occurrence.severity, outcomeOriginal: occurrence.classification, outcomeNormalized: comparison ? policyEvaluationOutcome(comparison) : 'not_evaluated', technicalExecutionState: 'completed', resultType: 'policy_deviation', isIoe: false, isDeviation: comparison ? policyComparisonDisposition(comparison) === 'misconfigured' : false, policyCustomerState: comparison?.customerState, policyClassification: comparison ? policyEvaluationClassification(comparison) : undefined, effectiveState: 'unknown_effective_state', lifecycleState: 'unknown', mappingConfidence: 1, applicabilityState: 'explicit-recommendation', completenessState: 'complete', scopeState: occurrence.scopeState, effectivenessVerification: occurrence.effectiveState, scopeConfidence: 0, reviewStatus: 'unreviewed', importedAt: occurrence.createdAt, evaluatedAt: occurrence.createdAt, createdAt: occurrence.createdAt, frameworkReferences: [], categories: [{ namespace: 'assessor-policy', categoryCode: definition.category ?? 'group-policy-baseline', categoryLabel: definition.category ?? 'group-policy-baseline', categoryType: 'canonical', isPrimary: true, sourceTool: 'GPOs', sourceProfileId: occurrence.environmentProfileId, sourceProfileVersion: String(occurrence.environmentProfileRevision) }] }; } function policyEvaluationTitle(result: PolicyEvaluationResult): string { if (result.identity.admx.status === 'matched') return result.identity.admx.reference.policyName; const target = result.identity.target; if (target.kind === 'registry-key' || target.kind === 'registry-value') { return [target.key, target.valueName].filter(Boolean).join(' · '); } return `${'section' in target ? target.section : target.kind} · ${target.key}`; } function policyEvaluationOutcome(result: PolicyEvaluationResult): UnifiedOutcome { if (policyComparisonDisposition(result) === 'not-configured') return 'informational'; if (result.classification === 'configured_match') return 'pass'; if (policyComparisonDisposition(result) === 'misconfigured') return 'fail'; if (result.classification === 'not_applicable') return 'not_applicable'; return 'warning'; } function policyEvaluationClassification(result: PolicyEvaluationResult): PolicyResultClassification { if (policyComparisonDisposition(result) === 'misconfigured') return 'misconfigured'; if (result.customerState === 'conflicted' || result.diagnosticCodes.includes('policy-evaluation-baseline-consensus-conflict')) return 'conflict'; if (policyComparisonDisposition(result) === 'not-configured') return 'not-configured'; if (result.classification === 'configured_match') return 'match'; return 'review-required'; } function policyDeliveryMechanism(result: PolicyEvaluationResult): GpoPolicyObservation['deliveryMechanism'] { if (result.identity.target.kind === 'security-template') return 'security-template'; if (result.identity.target.kind === 'advanced-audit-policy') return 'advanced-audit-policy'; return 'registry-policy'; } export interface PolicyEvaluationUnifiedResultContext { assessmentId?: string; importArtifactId?: string; snapshotId?: string; } /** * Projects a persisted baseline comparison as a normal Result DB row. Finding * eligibility only controls risk/severity enrichment by the Finding Factory; * it does not hide a deterministic PASS/FAIL compliance result. */ export function createPolicyEvaluationUnifiedResult( run: PolicyEvaluationRun, result: PolicyEvaluationResult, context: PolicyEvaluationUnifiedResultContext = {} ): UnifiedResultProjection { const outcomeNormalized = policyEvaluationOutcome(result); return { resultProjectionId: `policy-evaluation-comparison:${result.id}`, sourceKind: 'policy-evaluation', sourceResultId: result.id, projectId: run.projectId, assessmentId: context.assessmentId, collectionRunId: run.aggregationRunId, gpoSnapshotId: context.snapshotId, evaluationRunId: run.id, sourceTool: 'GPOs', sourceProfileId: run.evaluator.id, sourceProfileVersion: run.evaluator.version, mappingRuleVersion: POLICY_COMPARATOR_CATALOG.version, normalizationRuleVersion: POLICY_COMPARATOR_CATALOG.version, presentationVersion: 'gpo-policy-evaluation-1.1.0', processingCompatibilityKey: 'gpo-policy-evaluation-v1', processingStatus: 'current', reprocessingAvailable: true, sourceRuleId: result.identity.id, importArtifactId: context.importArtifactId, sourceViewId: result.aggregationStateId, title: policyEvaluationTitle(result), severityOriginal: null, severityNormalized: 'unknown', outcomeOriginal: result.classification, outcomeNormalized, technicalExecutionState: 'completed', resultType: 'policy_deviation', isIoe: false, isDeviation: policyComparisonDisposition(result) === 'misconfigured', effectiveState: result.classification === 'review_required' ? 'incomplete_observation' : result.classification, lifecycleState: 'unknown', mappingConfidence: 1, applicabilityState: result.baselineState, completenessState: result.diagnosticCodes.includes('policy-evaluation-baseline-incomplete') ? 'partial' : 'complete', scopeState: 'known', effectivenessVerification: 'not-verified', scopeConfidence: 1, reviewStatus: result.classification === 'review_required' ? 'open' : 'unreviewed', importedAt: run.completedAt, evaluatedAt: run.completedAt, createdAt: run.completedAt, policyScope: result.identity.scope, policyDeliveryMechanism: policyDeliveryMechanism(result), policyIdentityStatus: 'resolved', policyIdentityKey: result.identity.id, policyDiagnosticCodes: result.diagnosticCodes, policyBaselineId: result.baselineId, policyBaselineLabel: result.baselineLabel, policyBaselineValue: result.comparator.baseline as JsonValue | undefined, policyBaselineVariantAssessment: result.baselineVariantAssessment, policyActualValue: result.comparator.actual as JsonValue | undefined, policyComparisonRelation: result.comparator.relation, policyCustomerState: result.customerState, policyClassification: policyEvaluationClassification(result), frameworkReferences: [], categories: [{ namespace: 'group-policy', categoryCode: 'GPOs', categoryLabel: 'GPOs', categoryType: 'canonical', isPrimary: true, sourceTool: 'GPOs', sourceProfileId: run.evaluator.id, sourceProfileVersion: run.evaluator.version }] }; } export function selectCurrentPolicyEvaluations( records: StoredPolicyEvaluation[], options: { allowPreviousVersionForDisplay?: boolean } = {} ): StoredPolicyEvaluation[] { const selected = new Map(); // Broader benchmark coverage wins, measured in the distinct baselines actually compared // rather than in analysis references: several retained analysis generations of one // baseline package are one benchmark, not extra coverage, and must not outrank a // narrower current run. Where the retained results name no baseline, nothing better is // known and the declared analysis references remain the only available measure. const coverage = new Map(); const baselineCoverage = (record: StoredPolicyEvaluation): number => { const cached = coverage.get(record.run.id); if (cached !== undefined) return cached; const compared = new Set(record.results.flatMap(result => result.baselineId ? [result.baselineId] : [])); const value = compared.size || record.run.baselineAnalysisIds.length; coverage.set(record.run.id, value); return value; }; for (const record of records) { const isCurrent = record.run.evaluator.version === POLICY_EVALUATION_ENGINE.version; // Read-only Work compatibility, never a completion/finding-generation signal. const readablePrevious = options.allowPreviousVersionForDisplay && ['1.3.3', '1.4.0'].includes(record.run.evaluator.version); if (record.run.evaluator.id !== POLICY_EVALUATION_ENGINE.id || (!isCurrent && !readablePrevious)) continue; const current = selected.get(record.run.aggregationRunId); const sameVersion = current?.run.evaluator.version === record.run.evaluator.version; const newerPrevious = current?.run.evaluator.version === '1.3.3' && record.run.evaluator.version === '1.4.0'; const replacesCurrent = !current || (isCurrent && !sameVersion) || newerPrevious || (sameVersion && (baselineCoverage(record) > baselineCoverage(current!) || (baselineCoverage(record) === baselineCoverage(current!) && `${record.run.completedAt}\u001f${record.run.id}` > `${current.run.completedAt}\u001f${current.run.id}`))); if (replacesCurrent) selected.set(record.run.aggregationRunId, record); } return [...selected.values()].sort((left, right) => left.run.completedAt.localeCompare(right.run.completedAt)); } export function createCurrentPolicyEvaluationUnifiedResults( evaluations: StoredPolicyEvaluation[], aggregations: StoredPolicySnapshotAggregation[], analyses: StoredGpoPolicyAnalysis[], artifacts: ImportArtifact[], excludedEvaluationResultIds: ReadonlySet = new Set() ): UnifiedResultProjection[] { const aggregationById = new Map(aggregations.map(record => [record.run.id, record])); const analysisById = new Map(analyses.map(record => [record.run.id, record])); const artifactById = new Map(artifacts.map(record => [record.id, record])); return selectCurrentPolicyEvaluations(evaluations).flatMap(record => { const aggregation = aggregationById.get(record.run.aggregationRunId); if (!aggregation) return []; const sourceArtifacts = aggregation.run.sourceAnalysisRunIds .map(id => analysisById.get(id)?.run.artifactId) .filter((id): id is string => Boolean(id)) .map(id => artifactById.get(id)) .filter((item): item is ImportArtifact => Boolean(item)); const assessmentIds = [...new Set(sourceArtifacts.map(item => item.assessmentId).filter(Boolean))]; const artifactIds = [...new Set(sourceArtifacts.map(item => item.id))]; const context: PolicyEvaluationUnifiedResultContext = { assessmentId: assessmentIds.length === 1 ? assessmentIds[0] : undefined, importArtifactId: artifactIds.length === 1 ? artifactIds[0] : undefined, snapshotId: aggregation.run.snapshotId }; return record.results .filter(result => !excludedEvaluationResultIds.has(result.id)) .map(result => createPolicyEvaluationUnifiedResult(record.run, result, context)); }); } function policyObservationTitle(observation: GpoPolicyObservation): string { const section = observation.identity.section.trim(); const key = observation.identity.key.trim(); return [section, key].filter(Boolean).join(' · '); } function policyObservationLocator(observation: GpoPolicyObservation): SourcePosition { const suffix = observation.sourceLines?.length ? `#lines=${observation.sourceLines.join(',')}` : observation.sourceRecords?.length ? `#records=${observation.sourceRecords.map(record => record.index).join(',')}` : ''; return { objectPath: observation.sourcePath + suffix }; } /** * Projects a parsed GPO setting as reviewable evidence. This is deliberately * not a policy deviation: only the Finding Factory may create one after a * complete baseline evaluation. The projection makes imported GPO content * visible without weakening that safety boundary. */ export function createPolicyObservationUnifiedResult(source: PolicyObservationUnifiedResultSource): UnifiedResultProjection { const { artifact, backup, observation, resolution } = source; const resolved = resolution?.resolution.status === 'resolved' ? resolution.resolution.identity : undefined; const unresolvedReason = resolution?.resolution.status === 'unresolved' ? resolution.resolution.reasonCode : 'policy-identity-resolution-missing'; const policyIdentityKey = resolved?.id ?? `${observation.identity.namespace}:${observation.identity.section}:${observation.identity.key}`; const diagnosticCodes = [...new Set([ ...observation.diagnosticCodes, ...(resolved ? [] : [unresolvedReason]) ])].sort(); const needsReview = !resolved || observation.state === 'conflicted' || diagnosticCodes.length > 0; return { resultProjectionId: `policy-observation:${observation.id}`, sourceKind: 'policy-observation', sourceResultId: observation.id, projectId: backup.projectId, assessmentId: artifact.assessmentId, collectionRunId: backup.processingRunId, sourceTool: 'group-policy', sourceProfileId: observation.parser.id, sourceProfileVersion: observation.parser.version, mappingRuleVersion: resolved?.resolver.version ?? resolution?.analysisRunId ?? 'unresolved', normalizationRuleVersion: observation.reducer.version, presentationVersion: 'gpo-policy-observation-1.0.0', processingCompatibilityKey: 'gpo-policy-observation-v1', processingStatus: 'current', reprocessingAvailable: true, sourceRuleId: resolved?.id, importArtifactId: artifact.id, sourceViewId: backup.id, sourceLocator: policyObservationLocator(observation), title: policyObservationTitle(observation), description: resolved ? 'Imported Group Policy setting with a resolved technical identity.' : `Imported Group Policy setting requiring identity review (${unresolvedReason}).`, severityOriginal: null, severityNormalized: 'unknown', outcomeOriginal: observation.state, outcomeNormalized: needsReview ? 'warning' : 'informational', technicalExecutionState: 'completed', resultType: 'informational', isIoe: false, isDeviation: false, effectiveState: resolved ? 'not_evaluated' : 'unresolved_identity', lifecycleState: 'unknown', mappingConfidence: resolved ? 1 : 0, completenessState: backup.instance.inventory.status, scopeState: 'unknown', effectivenessVerification: 'not-verified', scopeConfidence: 0, reviewStatus: needsReview ? 'open' : 'unreviewed', sourceObservedAt: backup.instance.backupCreatedAt ?? backup.createdAt, importedAt: artifact.createdAt, createdAt: backup.createdAt, policyScope: observation.scope, policyDeliveryMechanism: observation.deliveryMechanism, policyState: observation.state, policyIdentityStatus: resolved ? 'resolved' : 'unresolved', policyIdentityKey, policyValues: observation.values as unknown as JsonValue[], policyDiagnosticCodes: diagnosticCodes, policyBackupInstanceId: backup.instance.id, ...(backup.instance.links ? { policyBackupLinkStatus: backup.instance.links.status } : {}), policyPreferenceCse: observation.preference?.cse, policyPreferenceAction: observation.preference?.operation, policyPreferenceTargeting: observation.preference?.targeting.status, policyGpoId: backup.instance.identity.status === 'resolved' ? backup.instance.identity.gpoId : undefined, policyNativeBackupId: backup.instance.identity.status === 'resolved' ? backup.instance.identity.backupId : undefined, policySourceSequence: observation.registryPolicy?.sequence ?? observation.preference?.sequence, frameworkReferences: [], categories: [{ namespace: 'group-policy', categoryCode: observation.deliveryMechanism, categoryLabel: observation.deliveryMechanism, categoryType: 'source', isPrimary: true, sourceTool: 'group-policy', sourceProfileId: observation.parser.id, sourceProfileVersion: observation.parser.version }] }; } export function createCurrentPolicyObservationUnifiedResults( artifacts: ImportArtifact[], processingRuns: ImportProcessingRun[], backups: StoredGpoBackupInstance[], analyses: StoredGpoPolicyAnalysis[] ): UnifiedResultProjection[] { const artifactById = new Map(artifacts.map(artifact => [artifact.id, artifact])); const supersededProcessingIds = new Set(processingRuns.map(run => run.supersedesRunId).filter(Boolean)); const currentProcessingIds = new Set(processingRuns .filter(run => !supersededProcessingIds.has(run.id) && run.status !== 'failed') .map(run => run.id)); const supersededAnalysisIds = new Set(analyses.map(record => record.run.supersedesAnalysisRunId).filter(Boolean)); const resolutionByObservation = new Map(); for (const analysis of analyses) { if (supersededAnalysisIds.has(analysis.run.id) || !currentProcessingIds.has(analysis.run.sourceProcessingRunId)) continue; for (const resolution of analysis.resolutions) resolutionByObservation.set(resolution.observationId, resolution); } return backups .filter(backup => currentProcessingIds.has(backup.processingRunId)) .flatMap(backup => { const artifact = artifactById.get(backup.artifactId); if (!artifact) return []; return (backup.instance.policyObservations ?? []).map(observation => createPolicyObservationUnifiedResult({ artifact, backup, observation, resolution: resolutionByObservation.get(observation.id) })); }) .sort((left, right) => left.importedAt.localeCompare(right.importedAt) || left.resultProjectionId.localeCompare(right.resultProjectionId)); } function environmentOutcome(state: EnvironmentCollectionState): UnifiedOutcome { if (state === 'parse-failed') return 'execution_error'; if (['not-assessed', 'access-denied', 'license-restricted', 'unsupported', 'unknown'].includes(state)) return 'not_evaluated'; return 'informational'; } function environmentExecutionState(state: EnvironmentCollectionState): TechnicalExecutionState { if (state === 'parse-failed') return 'failed'; if (['not-assessed', 'access-denied', 'license-restricted', 'unsupported'].includes(state)) return 'not-run'; if (state === 'unknown') return 'unknown'; return 'completed'; } function environmentPropertyGroup(property: string): string { return property.split('.')[0]?.trim() || 'environment'; } export function createEnvironmentUnifiedResult(source: EnvironmentUnifiedResultSource): UnifiedResultProjection | undefined { const { observation, importFile, scanRun, assessment, project } = source; const disclosure = discloseSupplementalObservation(observation); if (!disclosure) return undefined; return { resultProjectionId: `environment-observation:${observation.id}`, sourceKind: 'environment-observation', sourceResultId: observation.id, projectId: project.id, assessmentId: assessment.id, collectionRunId: scanRun.id, scanRunId: scanRun.id, sourceTool: observation.tool.id, sourceToolVersion: observation.tool.version, sourceProfileId: importFile.processedWith.profileId, sourceProfileVersion: importFile.processedWith.profileVersion, mappingRuleVersion: observation.mappingVersion, normalizationRuleVersion: importFile.processedWith.normalizationVersion, presentationVersion: importFile.processedWith.presentationVersion, processingCompatibilityKey: importFile.processedWith.compatibilityKey, processingStatus: processingReadiness(importFile.processedWith.profileId, importFile.processedWith), reprocessingAvailable: importFile.sourceArtifact.kind !== 'not-retained', importArtifactId: importFile.id, environmentProperty: disclosure.property, environmentSubjectType: disclosure.subjectType, environmentValueType: disclosure.valueType, environmentState: disclosure.state, environmentNormalizedValue: disclosure.value, supplementalDisclosurePolicyVersion: disclosure.policyVersion, supplementalDisclosureClass: disclosure.disclosureClass, title: disclosure.title, severityOriginal: null, severityNormalized: 'info', outcomeOriginal: observation.state, outcomeNormalized: environmentOutcome(observation.state), technicalExecutionState: environmentExecutionState(observation.state), resultType: 'informational', isIoe: false, isDeviation: false, lifecycleState: 'unknown', mappingConfidence: 1, completenessState: observation.state, reviewStatus: 'unreviewed', importedAt: importFile.importedAt, createdAt: importFile.importedAt, frameworkReferences: [], categories: [{ namespace: 'environment-subject', categoryCode: disclosure.subjectType, categoryLabel: disclosure.subjectType, categoryType: 'canonical', isPrimary: true, sourceTool: observation.tool.id, sourceProfileId: importFile.processedWith.profileId, sourceProfileVersion: importFile.processedWith.profileVersion }, { namespace: 'environment-property', categoryCode: environmentPropertyGroup(disclosure.property), categoryLabel: environmentPropertyGroup(disclosure.property), categoryType: 'canonical', isPrimary: false, sourceTool: observation.tool.id, sourceProfileId: importFile.processedWith.profileId, sourceProfileVersion: importFile.processedWith.profileVersion }] }; } function includesAny(values: T[] | undefined, actual: T | undefined): boolean { return !values?.length || actual !== undefined && values.includes(actual); } function normalizedText(value: string): string { return value.normalize('NFKC').toLocaleLowerCase('de-DE'); } export function filterUnifiedResults(items: UnifiedResultProjection[], query: UnifiedResultQuery = {}): UnifiedResultProjection[] { const sourceTools = query.sourceTool?.map(normalizedText); const search = query.search?.trim() ? normalizedText(query.search.trim()) : ''; const asset = query.affectedAsset?.trim() ? normalizedText(query.affectedAsset.trim()) : ''; const from = query.observedFrom ? Date.parse(query.observedFrom) : Number.NEGATIVE_INFINITY; const to = query.observedTo ? Date.parse(query.observedTo) : Number.POSITIVE_INFINITY; return items.filter(item => { if (query.projectId && item.projectId !== query.projectId) return false; if (query.assessmentId && item.assessmentId !== query.assessmentId) return false; if (query.assessmentIds?.length && (!item.assessmentId || !query.assessmentIds.includes(item.assessmentId))) return false; if (query.scanRunId && item.scanRunId !== query.scanRunId) return false; if (query.scanRunIds?.length && item.scanRunId && !query.scanRunIds.includes(item.scanRunId)) return false; if (query.scanRunIds?.length && !item.scanRunId && !query.assessmentIds?.includes(item.assessmentId ?? '')) return false; if (query.evaluationRunId && item.evaluationRunId !== query.evaluationRunId) return false; if (!includesAny(query.sourceKind, item.sourceKind)) return false; if (sourceTools?.length && (!item.sourceTool || !sourceTools.includes(normalizedText(item.sourceTool)))) return false; if (!includesAny(query.severity, item.severityNormalized)) return false; if (!includesAny(query.outcome, item.outcomeNormalized)) return false; if (!includesAny(query.technicalExecutionState, item.technicalExecutionState)) return false; if (!includesAny(query.resultType, item.resultType)) return false; if (!includesAny(query.policyClassification, item.policyClassification)) return false; if (query.isIoe !== undefined && item.isIoe !== query.isIoe) return false; if (query.deviation !== undefined && item.sourceKind !== 'policy-observation' && item.isDeviation !== query.deviation) return false; if (!includesAny(query.mappingRuleVersion, item.mappingRuleVersion)) return false; if (!includesAny(query.sourceProfileVersion, item.sourceProfileVersion)) return false; if (!includesAny(query.processingStatus, item.processingStatus)) return false; if (!includesAny(query.reviewDisposition, item.reviewDisposition)) return false; if (!includesAny(query.lifecycleState, item.lifecycleState)) return false; if (!includesAny(query.environmentState, item.environmentState)) return false; if (!includesAny(query.environmentProperty, item.environmentProperty)) return false; if (!includesAny(query.environmentSubjectType, item.environmentSubjectType)) return false; if (query.categoryNamespace?.length && !item.categories.some(category => query.categoryNamespace!.includes(category.namespace))) return false; if (query.categoryValue?.length && !item.categories.some(category => query.categoryValue!.includes(category.categoryCode) || query.categoryValue!.includes(category.categoryLabel))) return false; if (asset && !normalizedText(item.affectedAsset ?? '').includes(asset)) return false; const observed = Date.parse(item.sourceObservedAt ?? item.evaluatedAt ?? item.importedAt); if (observed < from || observed > to) return false; if (search) { const text = normalizedText(JSON.stringify({ title: item.title, description: item.description, recommendation: item.recommendation, asset: item.affectedAsset, sourceRuleId: item.sourceRuleId, environmentProperty: item.environmentProperty, environmentSubjectType: item.environmentSubjectType, environmentSubjectIdentity: item.environmentSubjectIdentity, environmentState: item.environmentState, environmentOriginalValue: item.environmentOriginalValue, environmentNormalizedValue: item.environmentNormalizedValue, policyScope: item.policyScope, policyDeliveryMechanism: item.policyDeliveryMechanism, policyState: item.policyState, policyIdentityStatus: item.policyIdentityStatus, policyIdentityKey: item.policyIdentityKey, policyValues: item.policyValues, policyDiagnosticCodes: item.policyDiagnosticCodes, policyPreferenceCse: item.policyPreferenceCse, policyPreferenceAction: item.policyPreferenceAction, policyPreferenceTargeting: item.policyPreferenceTargeting, policyBaselineLabel: item.policyBaselineLabel, policyBaselineValue: item.policyBaselineValue, policyBaselineVariantAssessment: item.policyBaselineVariantAssessment, policyActualValue: item.policyActualValue, policyComparisonRelation: item.policyComparisonRelation, policyCustomerState: item.policyCustomerState, policyClassification: item.policyClassification, categories: item.categories.map(category => category.categoryLabel) })); if (!text.includes(search)) return false; } return true; }); } export function queryUnifiedResultItems(items: UnifiedResultProjection[], query: UnifiedResultQuery = {}): UnifiedResultQueryResult { // Rangfolge aus src/domain/severity-order.ts: werkzeugunabhaengig, weil // severityNormalized fuer alle Quellen auf dieselbe Skala abgebildet wird. // Zuvor stand hier eine zweite, wortgleiche Kopie derselben Tabelle. const filtered = filterUnifiedResults(items, query) .sort((left, right) => severityRank(left.severityNormalized) - severityRank(right.severityNormalized) || left.importedAt.localeCompare(right.importedAt) || left.resultProjectionId.localeCompare(right.resultProjectionId)); const offset = Math.max(0, query.offset ?? 0); const limit = Math.min(10000, Math.max(0, query.limit ?? 1000)); return { total: filtered.length, items: filtered.slice(offset, offset + limit) }; } /** * Facetten-Werte werden aus zwei getrennten Mengen gebaut: * - `universe`: legt innerhalb von Projekt/Assessment/Scan-Lauf die * grundsaetzlich bekannten Werte fest. * - `countScope`: wendet alle uebrigen aktiven Filter an, nimmt aber den * eigenen Facettenfilter aus. So bleiben erreichbare Alternativen waehlbar. * Werte mit Trefferzahl null werden nicht an die Darstellung weitergegeben. */ function scopeOnlyQuery(query: UnifiedResultQuery): UnifiedResultQuery { return { projectId: query.projectId, assessmentId: query.assessmentId, assessmentIds: query.assessmentIds, scanRunId: query.scanRunId, scanRunIds: query.scanRunIds, evaluationRunId: query.evaluationRunId }; } export function deriveUnifiedResultFacets(items: UnifiedResultProjection[], query: UnifiedResultQuery = {}): UnifiedResultFacetResult { const baseQuery: UnifiedResultQuery = { ...query, offset: undefined, limit: undefined }; const universe = filterUnifiedResults(items, scopeOnlyQuery(query)); const filtered = filterUnifiedResults(items, baseQuery); const withoutOwnFilter = (...keys: Array): UnifiedResultProjection[] => { if (!keys.length) return filtered; const next = { ...baseQuery }; keys.forEach(key => { next[key] = undefined; }); return filterUnifiedResults(items, next); }; function facet( id: UnifiedResultFacet['id'], label: string, getValue: (item: UnifiedResultProjection) => string | undefined, ...ownQueryKeys: Array ): UnifiedResultFacet { const countScope = withoutOwnFilter(...ownQueryKeys); const counts = new Map(); countScope.forEach(item => { const value = getValue(item); if (value === undefined) return; counts.set(value, (counts.get(value) ?? 0) + 1); }); const seen = new Set(); const values: UnifiedResultFacetValue[] = []; universe.forEach(item => { const value = getValue(item); if (value === undefined || seen.has(value)) return; seen.add(value); const count = counts.get(value) ?? 0; if (count > 0) values.push({ value, label: value, count }); }); // Der Schweregrad folgt seiner fachlichen Rangfolge, nicht dem Alphabet - // sonst stuende "critical" zwischen "high" und "info". values.sort(id === 'severity' ? (left, right) => severityRank(left.value as CanonicalSeverity) - severityRank(right.value as CanonicalSeverity) : (left, right) => left.label.localeCompare(right.label)); return { id, label, values }; } function categoryFacet(): UnifiedResultFacet { const key = (namespace: string, code: string) => namespace + '::' + code; const countScope = withoutOwnFilter('categoryValue'); const counts = new Map(); countScope.forEach(item => item.categories.forEach(category => { const mapKey = key(category.namespace, category.categoryCode); counts.set(mapKey, (counts.get(mapKey) ?? 0) + 1); })); const seen = new Map(); universe.forEach(item => item.categories.forEach(category => { const mapKey = key(category.namespace, category.categoryCode); if (!seen.has(mapKey)) seen.set(mapKey, { label: category.categoryLabel, namespace: category.namespace, code: category.categoryCode }); })); const values: UnifiedResultFacetValue[] = [...seen.entries()].map(([mapKey, meta]) => ({ value: meta.code, label: meta.label, namespace: meta.namespace, count: counts.get(mapKey) ?? 0 })).filter(value => value.count > 0) .sort((left, right) => (left.namespace ?? '').localeCompare(right.namespace ?? '') || left.label.localeCompare(right.label)); return { id: 'category', label: 'Categories', values }; } const findingAxis = (item: UnifiedResultProjection): boolean => item.sourceKind !== 'environment-observation' && item.sourceKind !== 'policy-observation'; return { total: filtered.length, facets: [ facet('sourceKind', 'Origin Type', item => item.sourceKind, 'sourceKind'), (() => { const result = facet('sourceTool', 'Source Tool', item => item.sourceTool, 'sourceTool'); result.values.forEach(value => { value.label = resultSourceToolLabel(value.value); }); result.values.sort((left, right) => left.label.localeCompare(right.label)); return result; })(), facet('environmentState', 'Environment State', item => item.environmentState, 'environmentState'), facet('environmentSubjectType', 'Environment Subject', item => item.environmentSubjectType, 'environmentSubjectType'), facet('environmentProperty', 'Environment Property', item => item.environmentProperty, 'environmentProperty'), facet('severity', 'Severity', item => findingAxis(item) ? item.severityNormalized : undefined, 'severity'), facet('outcome', 'Outcome', item => findingAxis(item) ? item.outcomeNormalized : undefined, 'outcome'), facet('technicalExecutionState', 'Execution', item => findingAxis(item) ? item.technicalExecutionState : undefined, 'technicalExecutionState'), facet('resultType', 'Result Type', item => findingAxis(item) ? item.resultType : undefined, 'resultType'), (() => { const result = facet( 'policyClassification', 'Result Status', item => item.policyClassification, 'policyClassification' ); const labels: Record = POLICY_CLASSIFICATION_LABELS; // A selected status must stay visible and removable even if another filter // reduces it to zero (for example Passed together with Deviation = Yes). for (const value of query.policyClassification ?? []) { if (!result.values.some(item => item.value === value)) result.values.push({ value, label: labels[value], count: 0 }); } result.values.forEach(value => { value.label = labels[value.value as PolicyResultClassification] ?? value.label; }); result.values.sort((left, right) => left.label.localeCompare(right.label)); return result; })(), facet('isIoe', 'IOE', item => findingAxis(item) ? String(item.isIoe) : undefined, 'isIoe'), facet('deviation', 'Deviation', item => findingAxis(item) ? String(item.isDeviation) : undefined, 'deviation'), facet('mappingRuleVersion', 'Mapping Rule Set', item => item.mappingRuleVersion, 'mappingRuleVersion'), facet('sourceProfileVersion', 'Profile Version', item => item.sourceProfileVersion, 'sourceProfileVersion'), facet('processingStatus', 'Processing Status', item => item.processingStatus, 'processingStatus'), facet('lifecycleState', 'Lifecycle', item => item.lifecycleState, 'lifecycleState'), facet('effectiveState', 'Effectiveness', item => item.effectiveState), categoryFacet() ] }; } export function listUnifiedResultCategories(items: UnifiedResultProjection[]): UnifiedResultCategory[] { const categories = new Map(); items.flatMap(item => item.categories).forEach(category => { const key = `${category.namespace}\u0000${category.categoryCode}\u0000${category.sourceProfileId ?? ''}\u0000${category.sourceProfileVersion ?? ''}`; if (!categories.has(key)) categories.set(key, category); }); return [...categories.values()].sort((left, right) => left.namespace.localeCompare(right.namespace) || left.categoryLabel.localeCompare(right.categoryLabel)); }