- Single parameterized append-only INSERT into audit_events - Supports optional injected transaction client for atomic writes - Documents the four canonical event_type strings for this phase
55 lines
2.3 KiB
TypeScript
55 lines
2.3 KiB
TypeScript
/**
|
|
* Phishing Audit Trail (Phase 20)
|
|
*
|
|
* Single, append-only audit-event writer used by every state-changing
|
|
* phishing-triage service call (remediation-service.ts). Every write is a
|
|
* parameterized INSERT into `audit_events` — no ON CONFLICT, no updates,
|
|
* no deletes. This is the ONLY place that inserts into `audit_events`.
|
|
*
|
|
* Canonical `event_type` strings emitted across this phase (Phase 19 + 20):
|
|
* - 'campaign_classified' (Phase 19 — not emitted by this file)
|
|
* - 'remediation_approved' (remediation-service.ts, Task 2)
|
|
* - 'remediation_completed' (remediation-service.ts, Task 2)
|
|
* - 'campaign_marked_false_positive' (remediation-service.ts, Task 3)
|
|
*
|
|
* `writeAuditEvent` accepts an optional transaction `client` so a caller can
|
|
* write its state change and its audit row inside the SAME
|
|
* `postgresClient.transaction(...)` block — a rollback discards both
|
|
* together, guaranteeing no state change can commit without its audit row
|
|
* (REMED-06 / T-20-03).
|
|
*/
|
|
|
|
import { postgresClient } from './postgres-client';
|
|
|
|
export interface AuditEventInput {
|
|
campaignId: string;
|
|
actor: string | null;
|
|
eventType: string;
|
|
payload: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Minimal shape shared by both `postgresClient` and a transaction `PoolClient`
|
|
* — only the `query` method this module needs, so a fake test client or a
|
|
* real `pg.PoolClient` both satisfy it without importing `pg` here.
|
|
*/
|
|
export interface AuditQueryClient {
|
|
query: (text: string, params?: unknown[]) => Promise<{ rows: Array<{ id: string }> }>;
|
|
}
|
|
|
|
/**
|
|
* Inserts one row into `audit_events` and returns its id. Pass `client`
|
|
* (a transaction client) to make the write commit/rollback atomically with
|
|
* the caller's own state change; omit it to write standalone via the
|
|
* `postgresClient` singleton.
|
|
*/
|
|
export async function writeAuditEvent(input: AuditEventInput, client?: AuditQueryClient): Promise<string> {
|
|
const target = client ?? postgresClient;
|
|
const result = await target.query(
|
|
`INSERT INTO audit_events (campaign_id, actor, event_type, payload)
|
|
VALUES ($1, $2, $3, $4::jsonb)
|
|
RETURNING id::text AS id`,
|
|
[input.campaignId, input.actor, input.eventType, JSON.stringify(input.payload)]
|
|
);
|
|
return result.rows[0].id;
|
|
}
|