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
This commit is contained in:
Senior Engineer
2026-05-02 01:10:44 -04:00
committed by Michael Freno
parent 685fb57e53
commit 03276dde2d
35 changed files with 8072 additions and 31 deletions

View File

@@ -0,0 +1,143 @@
import {
AlertSource,
AlertCategory,
Severity,
EntityType,
NormalizedAlertInput,
CorrelationGroupOutput,
CorrelatedAlertOutput,
CorrelationQuery,
} from "@shieldai/types";
import { alertNormalizer, AlertNormalizer } from "./normalizer";
import { correlationEngine, CorrelationEngine } from "./engine";
export class CorrelationService {
private normalizer: AlertNormalizer;
private engine: CorrelationEngine;
constructor(
normalizer: AlertNormalizer = alertNormalizer,
engine: CorrelationEngine = correlationEngine
) {
this.normalizer = normalizer;
this.engine = engine;
}
public async ingestDarkWatchAlert(
userId: string,
sourceAlertId: string,
payload: {
exposureId: string;
breachName: string;
severity: string;
channel: string;
dataType?: string[];
dataSource?: string;
},
timestamp?: Date
): Promise<CorrelatedAlertOutput> {
const normalized = this.normalizer.normalizeDarkWatchAlert(
userId,
sourceAlertId,
payload,
timestamp
);
return this.engine.ingestAlert(normalized);
}
public async ingestSpamShieldAlert(
userId: string,
sourceAlertId: string,
payload: {
phoneNumber: string;
decision: string;
confidence: number;
reasons?: string[];
channel?: "call" | "sms";
hiyaReputationScore?: number;
truecallerSpamScore?: number;
},
timestamp?: Date
): Promise<CorrelatedAlertOutput> {
const normalized = this.normalizer.normalizeSpamShieldAlert(
userId,
sourceAlertId,
payload,
timestamp
);
return this.engine.ingestAlert(normalized);
}
public async ingestVoicePrintAlert(
userId: string,
sourceAlertId: string,
payload: {
jobId: string;
verdict: string;
syntheticScore: number;
confidence: number;
matchedEnrollmentId?: string;
matchedSimilarity?: number;
analysisType?: string;
},
timestamp?: Date
): Promise<CorrelatedAlertOutput> {
const normalized = this.normalizer.normalizeVoicePrintAlert(
userId,
sourceAlertId,
payload,
timestamp
);
return this.engine.ingestAlert(normalized);
}
public async ingestCallAnalysisAlert(
userId: string,
sourceAlertId: string,
payload: {
callId: string;
eventType?: string;
mosScore?: number;
anomaly?: string;
sentiment?: { label: string; score: number };
},
timestamp?: Date
): Promise<CorrelatedAlertOutput> {
const normalized = this.normalizer.normalizeCallAnalysisAlert(
userId,
sourceAlertId,
payload,
timestamp
);
return this.engine.ingestAlert(normalized);
}
public async ingestGenericAlert(
input: NormalizedAlertInput
): Promise<CorrelatedAlertOutput> {
return this.engine.ingestAlert(input);
}
public getCorrelatedAlerts(query: CorrelationQuery) {
return this.engine.getCorrelatedAlerts(query);
}
public getCorrelationGroups(query: CorrelationQuery) {
return this.engine.getCorrelationGroups(query);
}
public getGroupById(groupId: string) {
return this.engine.getGroupById(groupId);
}
public resolveGroup(groupId: string, status?: string) {
return this.engine.resolveGroup(groupId, status as any);
}
public getDashboardData(userId: string, timeWindowMinutes?: number) {
return this.engine.getDashboardData(userId, timeWindowMinutes);
}
}
export const correlationService = new CorrelationService();
export { alertNormalizer, correlationEngine };