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 `
`;
}
function sourceValue(value: JsonValue, markedFields: readonly string[], markedItems: readonly string[]): string {
if (Array.isArray(value)) {
const members = `${value.map(item => `- ${sourceValue(item, [], [])}
`).join('')}
`;
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))]))}
`;
}