From 1df34b3a6ddb2f79d0f017c42970b0f508349192 Mon Sep 17 00:00:00 2001 From: Ryder Date: Wed, 21 Oct 2026 14:32:35 +0000 Subject: [PATCH] feat(risk): add typed RiskEvaluatedEvent for risk scoring audit persistence (#414) --- src/events/risk-evaluated.event.spec.ts | 34 +++++++++++++++++++++++++ src/events/risk-evaluated.event.ts | 26 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/events/risk-evaluated.event.spec.ts create mode 100644 src/events/risk-evaluated.event.ts diff --git a/src/events/risk-evaluated.event.spec.ts b/src/events/risk-evaluated.event.spec.ts new file mode 100644 index 0000000..0f3448c --- /dev/null +++ b/src/events/risk-evaluated.event.spec.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { RiskEvaluatedEvent } from './risk-evaluated.event'; +import { DomainEventName } from './event-names'; + +describe('RiskEvaluatedEvent (#414)', () => { + it('constructs an immutable risk evaluation event with score, band, and factors', () => { + const evt = new RiskEvaluatedEvent( + 'org_123', + { + transactionId: 'tx_999', + score: 82, + band: 'HIGH', + factors: [ + { + factor: 'amount', + weight: 30, + contribution: 25, + detail: 'Exceeds standard agent transfer limit', + }, + ], + canAutoExecute: false, + }, + 'agent_42', + ); + + expect(RiskEvaluatedEvent.eventName).toBe(DomainEventName.RiskEvaluated); + expect(evt.organizationId).toBe('org_123'); + expect(evt.payload.score).toBe(82); + expect(evt.payload.band).toBe('HIGH'); + expect(evt.payload.canAutoExecute).toBe(false); + expect(evt.actorId).toBe('agent_42'); + expect(evt.occurredAt).toBeInstanceOf(Date); + }); +}); diff --git a/src/events/risk-evaluated.event.ts b/src/events/risk-evaluated.event.ts new file mode 100644 index 0000000..1d73f64 --- /dev/null +++ b/src/events/risk-evaluated.event.ts @@ -0,0 +1,26 @@ +import { RiskBand } from '@prisma/client'; +import { DomainEventName } from './event-names'; +import { RiskFactorScore } from '../modules/risk/risk.types'; + +export interface RiskEvaluatedEventPayload { + transactionId?: string; + score: number; + band: RiskBand; + factors: RiskFactorScore[]; + canAutoExecute: boolean; +} + +/** + * Strongly typed domain event emitted whenever an agent transaction undergoes + * risk scoring so downstream audit listeners persist immutable risk metadata. + */ +export class RiskEvaluatedEvent { + static readonly eventName = DomainEventName.RiskEvaluated; + + constructor( + public readonly organizationId: string, + public readonly payload: RiskEvaluatedEventPayload, + public readonly actorId?: string, + public readonly occurredAt: Date = new Date(), + ) {} +}