Files
Kordant/packages/correlation/src/normalizer.ts
Senior Engineer 03276dde2d Add cross-service alert correlation system FRE-4500
- Unified alert types (AlertSource, AlertCategory, CorrelationStatus, EntityType)
- NormalizedAlert and CorrelationGroup Prisma models
- AlertNormalizer for all 4 services (DarkWatch, SpamShield, VoicePrint, CallAnalysis)
- CorrelationEngine with temporal + entity-based correlation detection
- CorrelationService orchestrator with dashboard API
- Correlation API routes (/api/v1/correlation/*)
- Service emitters wired to DarkWatch, SpamShield, VoicePrint
- pnpm workspace config for monorepo
2026-05-02 01:10:44 -04:00

247 lines
6.8 KiB
TypeScript

import {
AlertSource,
AlertCategory,
Severity,
EntityTypes,
NormalizedAlertInput,
} from "@shieldai/types";
type EntityType = (typeof EntityTypes)[keyof typeof EntityTypes];
interface DarkWatchAlertPayload {
exposureId: string;
breachName: string;
severity: string;
channel: string;
dataType?: string[];
dataSource?: string;
}
interface SpamShieldAlertPayload {
phoneNumber: string;
decision: string;
confidence: number;
reasons?: string[];
channel?: "call" | "sms";
hiyaReputationScore?: number;
truecallerSpamScore?: number;
}
interface VoicePrintAlertPayload {
jobId: string;
verdict: string;
syntheticScore: number;
confidence: number;
matchedEnrollmentId?: string;
matchedSimilarity?: number;
analysisType?: string;
}
interface CallAnalysisAlertPayload {
callId: string;
eventType?: string;
mosScore?: number;
anomaly?: string;
sentiment?: { label: string; score: number };
}
const SEVERITY_MAP: Record<string, Severity> = {
LOW: "LOW",
INFO: "INFO",
MEDIUM: "MEDIUM",
WARNING: "WARNING",
HIGH: "HIGH",
CRITICAL: "CRITICAL",
};
function mapSeverity(raw: string | number): Severity {
if (typeof raw === "number") {
if (raw >= 0.9) return "CRITICAL";
if (raw >= 0.7) return "HIGH";
if (raw >= 0.5) return "WARNING";
if (raw >= 0.3) return "MEDIUM";
if (raw >= 0.1) return "INFO";
return "LOW";
}
const upper = raw.toUpperCase();
return SEVERITY_MAP[upper] ?? "INFO";
}
export class AlertNormalizer {
public normalizeDarkWatchAlert(
userId: string,
sourceAlertId: string,
payload: DarkWatchAlertPayload,
timestamp?: Date
): NormalizedAlertInput {
const severity = mapSeverity(payload.severity);
const entities: Array<{ type: EntityType; value: string }> = [];
if (payload.dataSource) {
entities.push({ type: EntityTypes.EMAIL, value: payload.breachName });
}
return {
source: AlertSource.DARKWATCH,
category: AlertCategory.BREACH_EXPOSURE,
severity,
userId,
title: `Breach Exposure: ${payload.breachName}`,
description: payload.dataType
? `Data types exposed: ${payload.dataType.join(", ")} in ${payload.breachName}`
: `Exposure detected in ${payload.breachName}`,
entities,
sourceAlertId,
payload: payload as unknown as Record<string, unknown>,
timestamp,
};
}
public normalizeSpamShieldAlert(
userId: string,
sourceAlertId: string,
payload: SpamShieldAlertPayload,
timestamp?: Date
): NormalizedAlertInput {
const decision = payload.decision.toUpperCase();
const severity =
decision === "BLOCK"
? "HIGH"
: decision === "FLAG"
? "WARNING"
: "INFO";
const channel = payload.channel === "sms" ? "sms" : "call";
const category =
channel === "sms"
? AlertCategory.SPAM_SMS
: AlertCategory.SPAM_CALL;
const entities: Array<{ type: EntityType; value: string }> = [
{ type: EntityTypes.PHONE_NUMBER, value: payload.phoneNumber },
];
return {
source: AlertSource.SPAMSHIELD,
category,
severity,
userId,
title: `${channel === "sms" ? "SMS" : "Call"} ${decision}: ${payload.phoneNumber}`,
description: payload.reasons
? `SpamShield ${decision} decision. Reasons: ${payload.reasons.join(", ")}`
: `SpamShield ${decision} decision with confidence ${Math.round(payload.confidence * 100)}%`,
entities,
sourceAlertId,
payload: payload as unknown as Record<string, unknown>,
timestamp,
};
}
public normalizeVoicePrintAlert(
userId: string,
sourceAlertId: string,
payload: VoicePrintAlertPayload,
timestamp?: Date
): NormalizedAlertInput {
const verdict = payload.verdict.toUpperCase();
let severity: Severity;
let category: AlertCategory;
if (payload.analysisType === "VOICE_MATCH" && payload.matchedEnrollmentId) {
category = AlertCategory.VOICE_MISMATCH;
severity =
payload.matchedSimilarity !== undefined && payload.matchedSimilarity > 0.85
? "MEDIUM"
: "LOW";
} else {
category = AlertCategory.SYNTHETIC_VOICE;
severity =
verdict === "SYNTHETIC"
? mapSeverity(payload.syntheticScore)
: verdict === "UNCERTAIN"
? "MEDIUM"
: "INFO";
}
const entities: Array<{ type: EntityType; value: string }> = [];
if (payload.matchedEnrollmentId) {
entities.push({ type: EntityTypes.USER_ID, value: payload.matchedEnrollmentId });
}
return {
source: AlertSource.VOICEPRINT,
category,
severity,
userId,
title: `Voice ${verdict}: Job ${payload.jobId}`,
description: payload.analysisType
? `Analysis type: ${payload.analysisType}. Verdict: ${verdict} (confidence: ${Math.round(payload.confidence * 100)}%)`
: `Synthetic voice detection: ${verdict} (score: ${payload.syntheticScore.toFixed(3)})`,
entities,
sourceAlertId,
payload: payload as unknown as Record<string, unknown>,
timestamp,
};
}
public normalizeCallAnalysisAlert(
userId: string,
sourceAlertId: string,
payload: CallAnalysisAlertPayload,
timestamp?: Date
): NormalizedAlertInput {
let category: AlertCategory;
let severity: Severity;
let title: string;
let description: string;
if (payload.anomaly) {
category = AlertCategory.CALL_ANOMALY;
severity = "WARNING";
title = `Call Anomaly: ${payload.anomaly}`;
description = `Anomaly "${payload.anomaly}" detected in call ${payload.callId}`;
} else if (payload.mosScore !== undefined) {
category = AlertCategory.CALL_QUALITY;
severity =
payload.mosScore < 2.5
? "CRITICAL"
: payload.mosScore < 3.5
? "HIGH"
: payload.mosScore < 4.0
? "MEDIUM"
: "INFO";
title = `Call Quality: MOS ${payload.mosScore.toFixed(1)}`;
description = `MOS score ${payload.mosScore.toFixed(1)} for call ${payload.callId}`;
} else if (payload.eventType) {
category = AlertCategory.CALL_EVENT;
severity = "INFO";
title = `Call Event: ${payload.eventType}`;
description = `Event "${payload.eventType}" during call ${payload.callId}`;
} else {
category = AlertCategory.CALL_EVENT;
severity = "INFO";
title = `Call Alert: ${payload.callId}`;
description = `Alert for call ${payload.callId}`;
}
const entities: Array<{ type: EntityType; value: string }> = [
{ type: EntityTypes.CALL_ID, value: payload.callId },
];
return {
source: AlertSource.CALL_ANALYSIS,
category,
severity,
userId,
title,
description,
entities,
sourceAlertId,
payload: payload as unknown as Record<string, unknown>,
timestamp,
};
}
}
export const alertNormalizer = new AlertNormalizer();