import type { JsonValue } from '../domain/canonical-model'; import type { PolicySourceEvidence, RecordField } from '../domain/finding'; import type { ReportProjection, ReportReviewInput } from '../domain/report-projection'; import type { UnifiedResultProjection } from '../domain/unified-result'; import { escapeHtml } from '../shared/html'; export const TECHNICAL_REPORT_RENDERER_VERSION = '1.0.0'; const plain = (value: unknown): string => value === undefined || value === null || value === '' ? '—' : escapeHtml(typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)); const number = (value: number): string => escapeHtml(value.toLocaleString('de-DE')); const when = (value: string): string => Number.isFinite(Date.parse(value)) ? escapeHtml(new Date(value).toLocaleString('de-DE')) : plain(value); function table(headers: string[], rows: string[][]): string { return `
${headers.map(label => ``).join('')}${rows.map(row => `${row.map(cell => ``).join('')}`).join('')}
${escapeHtml(label)}
${cell}
`; } function sourceValue(value: JsonValue, markedFields: readonly string[], markedItems: readonly string[]): string { if (Array.isArray(value)) { const members = ``; return markedFields.includes('value') && !markedItems.length ? `
${members}
` : members; } if (value && typeof value === 'object') { const instruction = `
${Object.entries(value).map(([key, item]) => { const marked = markedFields.includes(key) || (markedFields.includes('value') && key === 'value'); return `
${escapeHtml(key)}
${sourceValue(item, [], markedItems)}
`; }).join('')}
`; return markedFields.includes('value') && !Object.hasOwn(value, 'value') && !markedItems.length ? `
${instruction}
` : instruction; } return markedFields.includes('value') ? `${plain(value)}` : plain(value); } function sourceSections(sources: PolicySourceEvidence[]): string { if (!sources.length) return '

Keine einzelnen GPO-Quellanweisungen in diesem Snapshot verfügbar.

'; return sources.map(source => `
GPO Backup ${plain(source.gpoId)} · ${plain(source.nativeBackupId)}
Backup-Instanz
${plain(source.backupInstanceId)}
Evidence
${plain(source.sourceReference)}
Quellreihenfolge
${plain(source.sequence)}
${source.values.map((value, index) => `
Anweisung ${number(index + 1)} ${sourceValue(value, source.deviationFields?.[index] ?? [], source.deviationItems?.[index] ?? [])}
`).join('')}
`).join(''); } function resultDetail(result: UnifiedResultProjection, domainLabel: string, review?: ReportReviewInput, fields?: RecordField[]): string { const actual = result.policyActualValue; const expected = result.policyBaselineValue; const variants = result.policyBaselineVariantAssessment; return `

${plain(result.title)}

${plain(result.sourceTool)} · ${plain(result.severityNormalized)} · ${plain(result.outcomeNormalized)}
Security Domain
${escapeHtml(domainLabel)}
Source Rule ID
${plain(result.sourceRuleId)}
Technische Identität
${plain(result.policyIdentityKey ?? result.canonicalDefinitionId)}
Scope
${plain(result.policyScope ?? result.affectedAsset)}
Result Status
${plain(result.policyClassification ?? result.outcomeNormalized)}
Baseline
${plain(result.policyBaselineLabel)}
${result.description ? `

${plain(result.description)}

` : ''} ${result.recommendation ? `

Empfehlung: ${plain(result.recommendation)}

` : ''} ${actual !== undefined || expected !== undefined ? table(['Konfiguriert', 'Erwartet', 'Vergleich'], [[plain(actual), plain(expected), plain(result.policyComparisonRelation)]]) : '

Diese Quelle liefert hier keinen strukturierten Ist-/Sollwert. Es wird keiner abgeleitet.

'} ${variants ? `
Baseline-Varianten
${plain(variants)}
` : ''} ${result.policyDiagnosticCodes?.length ? `

Diagnostics: ${result.policyDiagnosticCodes.map(plain).join(', ')}

` : ''} ${result.frameworkReferences.length ? `
Framework-Referenzen (exakte Quellwerte)
${table(['Namespace', 'Referenz'], result.frameworkReferences.map(reference => [plain(reference.namespace), plain(reference.value)]))}` : ''} ${fields?.length ? `
Originale technische Felder
${table(['Feld', 'Wert'], fields.map(field => [plain(field.label), plain(field.value)]))}` : ''} ${review?.nextStep ? `

Geplanter Remediation-Schritt (unverifizierte Selbstauskunft): ${plain(review.nextStep)}

` : ''}

Result-ID ${plain(result.resultProjectionId)} · ScanRun ${plain(result.scanRunId)} · ImportArtifact ${plain(result.importArtifactId)} · Source Locator ${plain(result.sourceLocator)}

`; } export function buildTechnicalReportHtml(projection: ReportProjection): string { const results = projection.technicalResults ?? []; const labels = new Map(projection.domains.map(domain => [domain.domainId, domain.domainLabel])); const domainOf = (result: UnifiedResultProjection) => projection.technicalResultDomains?.[result.resultProjectionId] ?? 'unclassified'; const deviations = results.filter(result => result.isDeviation); const severityOrder = ['critical', 'high', 'medium', 'low', 'info', 'unknown']; const grouped = [...new Set(deviations.map(domainOf))].map(domainId => ({ domainId, label: labels.get(domainId) ?? domainId, items: deviations.filter(result => domainOf(result) === domainId) .sort((a, b) => severityOrder.indexOf(a.severityNormalized) - severityOrder.indexOf(b.severityNormalized) || a.title.localeCompare(b.title)) })); const policySources = new Map((projection.technicalPolicySources ?? []).map(item => [item.settingId, item.sources])); const reviews = new Map((projection.technicalReviews ?? []).map(item => [item.resultProjectionId, item])); const fields = new Map((projection.technicalFindingFields ?? []).map(item => [item.resultProjectionId, item.fields])); const neutral = new Set(['Not configured', 'No baseline rule', 'Not comparable']); const policyRows = projection.policyEntries.map(item => [ plain(item.title), plain(item.scope), plain(item.identity), `${plain(item.verdict)}${neutral.has(item.verdict) ? ' · score-neutral' : ''}`, item.conflict ? 'Ja' : 'Nein', item.duplicate ? 'Ja' : 'Nein' ]); const provenanceRows = projection.provenance.map(item => [plain(item.kind), plain(item.id), plain(item.version), plain(item.observedAt)]); const diagnosticRows = projection.diagnostics.map(item => [plain(item.code), plain(item.reason), plain(item.origin), number(item.count)]); return ` ASSESSOR · Technical Report · ${escapeHtml(projection.project.name)}

ASSESSOR · Technical Report

${escapeHtml(projection.project.name)}

Assessment ${plain(projection.assessmentId)} · Stand ${when(projection.generatedAt)}

Frozen Snapshot des gewählten Timeslots. GPO-Aussagen beruhen auf Backupvergleichen; der effektive Endpunktzustand ist nicht verifiziert.

Scope und Provenienz

${provenanceRows.length ? table(['Art', 'ID', 'Version', 'Beobachtet'], provenanceRows) : '

Keine Provenienz übermittelt.

'}

Result-Inventar

${number(projection.coverage.total)} Ergebnisse · ${number(projection.coverage.deviations)} Deviations · ${number(projection.coverage.passed)} Passed · ${number(projection.coverage.notRun)} Not run

${table(['Severity', 'Anzahl'], Object.entries(projection.inventory.bySeverity).map(([label, count]) => [plain(label), number(count)]))} ${table(['Outcome', 'Anzahl'], Object.entries(projection.inventory.byOutcome).map(([label, count]) => [plain(label), number(count)]))} ${table(['Security Domain', 'Anzahl'], Object.entries(projection.inventory.byDomain).map(([id, count]) => [plain(labels.get(id) ?? id), number(count)]))} ${table(['Source Tool', 'Anzahl'], Object.entries(projection.inventory.bySource).map(([tool, count]) => [plain(tool), number(count)]))}

Findings im Detail

${grouped.length ? grouped.map(group => `

${escapeHtml(group.label)}

${group.items.map(item => resultDetail(item, group.label, reviews.get(item.resultProjectionId), fields.get(item.resultProjectionId))).join('')}
`).join('') : '

Keine Deviations in diesem Snapshot.

'}

GPO Policy Register

Conflicts und Duplicates sind separate Diagnostics, keine Ersatz-Verdicts. Not configured, No baseline rule und Not comparable sind score-neutral.

${projection.gpo ? table(['Verdict / Diagnostic', 'Anzahl'], [ ['Deviation', projection.gpo.counts.Fail], ['Passed', projection.gpo.counts.Pass], ['Not configured · score-neutral', projection.gpo.counts['Not configured']], ['No baseline rule · score-neutral', projection.gpo.counts['No baseline rule']], ['Not comparable · score-neutral', projection.gpo.counts['Not comparable']], ['Review required · unklar', projection.gpo.counts['Review required']], ['Conflicts', projection.gpo.conflicts], ['Duplicates', projection.gpo.duplicates] ].map(([label, count]) => [plain(label), number(Number(count))])) : ''} ${policyRows.length ? table(['Setting', 'Scope', 'Technische Identität', 'Verdict', 'Conflicts', 'Duplicates'], policyRows) : '

Keine gerollten GPO Policy Settings im gewählten Assessment vorhanden.

'} ${projection.policyEntries.map(item => `

${plain(item.title)}

${sourceSections(policySources.get(item.id) ?? [])}
`).join('')}

Konvergenz-Nachweis

${table(['Security Domain', 'Quellen mit Deviations', 'Quellen', 'Evidence'], projection.convergence.map(item => [ plain(labels.get(item.domainId) ?? item.domainId), number(item.sourceCount), plain(item.sources.filter(source => source.deviations > 0).map(source => source.toolId).join(', ')), item.evidence === 'shared-framework-reference' ? 'Gemeinsame exakte Framework-Referenz' : 'Nur Domain-Nähe; Validierung erforderlich' ]))} ${projection.bundles.length ? table(['Framework-Referenz', 'Source Tools', 'Result-IDs'], projection.bundles.map(item => [ plain(item.key), plain(item.toolIds.join(', ')), plain(item.members.join(', ')) ])) : ''}

Scope-Grenzen

GPO-Vergleiche gelten nur für das importierte Backup-Set. Ohne RSoP bzw. Group Policy Results wird kein wirksamer Endpoint-Zustand behauptet.

not_run ist kein Pass. Nicht ausgeführte Checks und unbekannte Werte werden weder als bestanden noch als Deviation gezählt.

Diagnostics

${diagnosticRows.length ? table(['Code', 'Resolver-Grund', 'Herkunft', 'Anzahl'], diagnosticRows) : '

Keine aggregierten Diagnostics übermittelt.

'}

Anhang: vollständiger Result-Index

${table(['Source Tool', 'Source Rule ID', 'Titel', 'Outcome', 'Severity', 'Security Domain'], results.map(item => [plain(item.sourceTool), plain(item.sourceRuleId), plain(item.title), plain(item.outcomeNormalized), plain(item.severityNormalized), plain(labels.get(domainOf(item)) ?? domainOf(item))]))}
`; }