import type { UnifiedResultProjection, UnifiedResultQuery, UnifiedResultQueryResult } from '../domain/unified-result'; import { discloseResultDbProjection, RESULT_DB_DISCLOSURE_POLICY_VERSION } from '../domain/result-db-disclosure'; export const RESULT_DB_JSONL_FORMAT = 'assessor-result-db-jsonl'; export const RESULT_DB_JSONL_SCHEMA_VERSION = '2.0.0'; export const RESULT_DB_EXPORT_PAGE_SIZE = 1000; type ResultPageReader = (query: UnifiedResultQuery) => Promise; function definedQueryEntries(query: UnifiedResultQuery): Array<[string, unknown]> { return Object.entries(query).filter(([, value]) => value !== undefined); } function exportScope(query: UnifiedResultQuery): Record { return { projectScoped: Boolean(query.projectId), assessmentScopeCount: query.assessmentIds?.length ?? (query.assessmentId ? 1 : 0), scanScopeCount: query.scanRunIds?.length ?? (query.scanRunId ? 1 : 0), evaluationScoped: Boolean(query.evaluationRunId) }; } function exportFilters(query: UnifiedResultQuery): Record { const excluded = new Set([ 'projectId', 'assessmentId', 'assessmentIds', 'scanRunId', 'scanRunIds', 'evaluationRunId', 'offset', 'limit', 'search', 'affectedAsset', 'observedFrom', 'observedTo' ]); return Object.fromEntries(definedQueryEntries(query) .filter(([key]) => !excluded.has(key)) .map(([key, value]) => [key, Array.isArray(value) ? { active: true, selectionCount: value.length } : value])); } function resultForExport(result: UnifiedResultProjection): Record { const disclosed = discloseResultDbProjection(result); if (disclosed.sourceKind === 'environment-observation' && (!disclosed.supplementalDisclosurePolicyVersion || !disclosed.supplementalDisclosureClass)) { throw new Error('An unclassified supplemental result cannot be exported.'); } const blocked = new Set([ 'resultProjectionId', 'sourceResultId', 'projectId', 'assessmentId', 'collectionRunId', 'scanRunId', 'gpoSnapshotId', 'evaluationRunId', 'importArtifactId', 'sourceViewId', 'sourceLocator', 'sourceObservedAt', 'importedAt', 'evaluatedAt', 'createdAt' ]); return Object.fromEntries(Object.entries(disclosed) .filter(([key, value]) => !blocked.has(key) && value !== undefined)); } export function buildUnifiedResultJsonl( query: UnifiedResultQuery, results: UnifiedResultProjection[], generatedAt: string ): string { const lines = [{ recordType: 'manifest', format: RESULT_DB_JSONL_FORMAT, schemaVersion: RESULT_DB_JSONL_SCHEMA_VERSION, disclosurePolicyVersion: RESULT_DB_DISCLOSURE_POLICY_VERSION, generatedAt, resultCount: results.length, scope: exportScope(query), filters: exportFilters(query) }, ...results.map((result, index) => ({ recordType: 'result', position: index + 1, result: resultForExport(result) }))]; return `${lines.map(line => JSON.stringify(line)).join('\n')}\n`; } export function unifiedResultJsonlFileName(generatedAt: string): string { const timestamp = generatedAt.replace(/\.\d{3}Z$/, 'Z').replaceAll(':', '-'); return `assessor-result-db-filtered-${timestamp}.jsonl`; } export async function queryAllUnifiedResults( readPage: ResultPageReader, query: UnifiedResultQuery, pageSize = RESULT_DB_EXPORT_PAGE_SIZE ): Promise { const baseQuery = { ...query, offset: undefined, limit: undefined }; const results: UnifiedResultProjection[] = []; let expectedTotal: number | undefined; while (expectedTotal === undefined || results.length < expectedTotal) { const page = await readPage({ ...baseQuery, offset: results.length, limit: pageSize }); if (expectedTotal === undefined) expectedTotal = page.total; if (page.total !== expectedTotal) throw new Error('The filtered result set changed during export. Please retry.'); if (!page.items.length && results.length < expectedTotal) throw new Error('The filtered result export ended before all rows were returned.'); results.push(...page.items); } if (results.length !== expectedTotal) throw new Error('The filtered result export returned an inconsistent row count.'); return results; }