import type { RegulatoryFindingQuery } from '../../domain/regulatory-rag'; import { REGULATORY_FINDING_QUERY_SCHEMA_VERSION } from '../../domain/regulatory-rag'; import type { UnifiedResultProjection } from '../../domain/unified-result'; import { privacySafeFinding } from '../../domain/privacy-safe-finding'; import catalog from './catalog.json'; import { publicSecurityTopics } from './public-security-topics'; const MAXIMUM_TITLE_CHARACTERS = 500; const MAXIMUM_DETAIL_CHARACTERS = 1_200; const MAXIMUM_RECOMMENDATION_CHARACTERS = 600; const publicTools: Record = { 'cis-cat': 'CIS-CAT / CIS Benchmarks', maester: 'Maester', pingcastle: 'PingCastle', 'purple-knight': 'Semperis Purple Knight' }; const publicRuleIds: Record = { maester: catalog.maesterIds, pingcastle: catalog.pingcastleIds }; /** * Builds an embedding query from the finding's rule-level check text — its title, * description and recommendation projected through the shared privacy boundary * ({@link privacySafeFinding}). That text describes the check, not the tenant: the * result-table / occurrence data (affectedAsset, per-object values) is never read, * and email addresses are redacted. Every scanner finding with a check title maps; * catalog rule IDs and fixed public topics only sharpen the query when present. */ export function buildRegulatoryFindingQuery(finding: UnifiedResultProjection): RegulatoryFindingQuery | undefined { if (finding.sourceKind !== 'scanner-finding') return undefined; const safe = privacySafeFinding(finding); const title = safe.title.trim().slice(0, MAXIMUM_TITLE_CHARACTERS); if (!title) return undefined; const toolId = finding.sourceTool?.trim().toLowerCase() ?? ''; const publicTool = publicTools[toolId]; const publicRuleId = recognizedRuleId(toolId, finding.sourceRuleId); const detail = safe.description?.slice(0, MAXIMUM_DETAIL_CHARACTERS); const recommendation = safe.recommendation?.slice(0, MAXIMUM_RECOMMENDATION_CHARACTERS); const topics = publicSecurityTopics([title, detail, recommendation].filter(Boolean).join('\n')); const query = [ 'Retrieve regulatory controls whose normative requirements directly address this security finding.', publicTool && `Public assessment tool: ${publicTool}.`, publicRuleId && `Public rule ID: ${publicRuleId}.`, `Security check: ${title}.`, detail && `Check detail: ${detail}.`, recommendation && `Recommended action: ${recommendation}.`, topics.length && `Public technical topics: ${topics.join('; ')}.`, 'Prefer controls with direct requirement text over merely related guidance. Do not infer customer scope, assets, configuration values, or compliance status.' ].filter(Boolean).join(' '); return { schemaVersion: REGULATORY_FINDING_QUERY_SCHEMA_VERSION, query, specificity: publicRuleId ? 'rule' : 'topic', ...(publicTool ? { publicTool } : {}), ...(publicRuleId ? { publicRuleId } : {}), publicTopics: topics }; } function recognizedRuleId(toolId: string, value: string | undefined): string | undefined { const candidate = value?.trim(); if (!candidate) return undefined; return publicRuleIds[toolId]?.find(id => id.toLowerCase() === candidate.toLowerCase()); }