feat(audit): automate gateway phase0 live-window capture

This commit is contained in:
William Valentin
2026-02-26 23:56:30 -08:00
parent 5a34e986bf
commit 826df1d35b
12 changed files with 310 additions and 21 deletions
+74
View File
@@ -0,0 +1,74 @@
import { describe, expect, it } from 'vitest';
import type { AuditEvent } from './types.js';
import { findLatestGatewayCancelWindow } from './phase0GatewayWindow.js';
function event(
timestamp: number,
eventType: AuditEvent['event_type'],
payload: Record<string, unknown>,
): AuditEvent {
return {
timestamp,
level: 'info',
event_type: eventType,
event: payload,
};
}
describe('findLatestGatewayCancelWindow', () => {
it('returns the latest gateway session containing run.cancel and cancelled states', () => {
const events: AuditEvent[] = [
event(100, 'run.state', { session_id: 'old', source: 'gateway', state: 'start' }),
event(110, 'run.cancel', { session_id: 'old', source: 'gateway', acknowledged: true }),
event(115, 'run.state', { session_id: 'old', source: 'gateway', state: 'cancel_requested' }),
event(120, 'run.state', { session_id: 'old', source: 'gateway', state: 'cancelled' }),
event(200, 'run.state', { session_id: 'new', source: 'gateway', state: 'start' }),
event(210, 'run.cancel', { session_id: 'new', source: 'gateway', acknowledged: true }),
event(220, 'run.state', { session_id: 'new', source: 'gateway', state: 'cancelled' }),
event(300, 'run.state', { session_id: 'channel', source: 'channel', state: 'cancelled' }),
];
const window = findLatestGatewayCancelWindow(events);
expect(window).toEqual({
session_id: 'new',
start_time_ms: 200,
end_time_ms: 220,
event_count: 3,
run_cancel_count: 1,
cancel_requested_count: 0,
cancelled_count: 1,
});
});
it('applies padding and ignores malformed/missing payload fields', () => {
const events: AuditEvent[] = [
event(1000, 'run.state', { session_id: 's1', source: 'gateway', state: 'start' }),
event(1010, 'run.cancel', { session_id: 's1', source: 'gateway' }),
event(1020, 'run.state', { session_id: 's1', source: 'gateway', state: 'cancel_requested' }),
event(1030, 'run.state', { session_id: 's1', source: 'gateway', state: 'cancelled' }),
event(1040, 'run.cancel', { source: 'gateway' }),
event(1050, 'run.state', { session_id: 's2', state: 'cancelled' }),
];
const window = findLatestGatewayCancelWindow(events, { padding_ms: 25 });
expect(window).toEqual({
session_id: 's1',
start_time_ms: 975,
end_time_ms: 1055,
event_count: 4,
run_cancel_count: 1,
cancel_requested_count: 1,
cancelled_count: 1,
});
});
it('returns null when no gateway cancel+cancelled window exists', () => {
const events: AuditEvent[] = [
event(1, 'run.state', { session_id: 's1', source: 'gateway', state: 'start' }),
event(2, 'run.state', { session_id: 's1', source: 'gateway', state: 'complete' }),
event(3, 'run.cancel', { session_id: 's2', source: 'channel' }),
];
expect(findLatestGatewayCancelWindow(events)).toBeNull();
});
});
+121
View File
@@ -0,0 +1,121 @@
import type { AuditEvent } from './types.js';
export interface GatewayCancelWindowSummary {
session_id: string;
start_time_ms: number;
end_time_ms: number;
event_count: number;
run_cancel_count: number;
cancel_requested_count: number;
cancelled_count: number;
}
export interface FindGatewayCancelWindowOptions {
padding_ms?: number;
}
interface SessionWindowAccumulator {
session_id: string;
min_ts: number;
max_ts: number;
event_count: number;
run_cancel_count: number;
cancel_requested_count: number;
cancelled_count: number;
}
function toPayload(value: unknown): Record<string, unknown> {
return (value && typeof value === 'object') ? value as Record<string, unknown> : {};
}
function readString(value: unknown): string | undefined {
return typeof value === 'string' ? value : undefined;
}
function isGatewayEvent(payload: Record<string, unknown>): boolean {
return readString(payload.source) === 'gateway';
}
export function findLatestGatewayCancelWindow(
events: AuditEvent[],
options: FindGatewayCancelWindowOptions = {},
): GatewayCancelWindowSummary | null {
const bySession = new Map<string, SessionWindowAccumulator>();
for (const event of events) {
if (event.event_type !== 'run.state' && event.event_type !== 'run.cancel') {
continue;
}
const payload = toPayload(event.event);
if (!isGatewayEvent(payload)) {
continue;
}
const sessionId = readString(payload.session_id);
if (!sessionId) {
continue;
}
const acc = bySession.get(sessionId) ?? {
session_id: sessionId,
min_ts: event.timestamp,
max_ts: event.timestamp,
event_count: 0,
run_cancel_count: 0,
cancel_requested_count: 0,
cancelled_count: 0,
};
acc.event_count += 1;
acc.min_ts = Math.min(acc.min_ts, event.timestamp);
acc.max_ts = Math.max(acc.max_ts, event.timestamp);
if (event.event_type === 'run.cancel') {
acc.run_cancel_count += 1;
} else {
const state = readString(payload.state);
if (state === 'cancel_requested') {
acc.cancel_requested_count += 1;
} else if (state === 'cancelled') {
acc.cancelled_count += 1;
}
}
bySession.set(sessionId, acc);
}
const candidates = [...bySession.values()]
.filter((row) => row.run_cancel_count > 0 && row.cancelled_count > 0)
.sort((a, b) => {
const tsDelta = b.max_ts - a.max_ts;
if (tsDelta !== 0) {
return tsDelta;
}
const cancelDelta = b.run_cancel_count - a.run_cancel_count;
if (cancelDelta !== 0) {
return cancelDelta;
}
return a.session_id.localeCompare(b.session_id);
});
const latest = candidates[0];
if (!latest) {
return null;
}
const padRaw = options.padding_ms ?? 0;
const paddingMs = Number.isFinite(padRaw) && padRaw > 0
? Math.floor(padRaw)
: 0;
return {
session_id: latest.session_id,
start_time_ms: Math.max(0, latest.min_ts - paddingMs),
end_time_ms: latest.max_ts + paddingMs,
event_count: latest.event_count,
run_cancel_count: latest.run_cancel_count,
cancel_requested_count: latest.cancel_requested_count,
cancelled_count: latest.cancelled_count,
};
}