Files
flynn/src/audit/phase0BaselineSummary.test.ts
T
William Valentin c68fd2498e fix(audit): enforce phase0 summary max-limit semantics
Validate maxSessions/maxChannels/maxSkipReasons as non-negative finite values, make 0 produce zero rows, and add regression coverage. No architecture/protocol flow changes; diagram files reviewed and no updates were needed.
2026-02-27 13:08:37 -08:00

255 lines
7.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { AuditEvent } from './types.js';
import { renderPhase0BaselineMarkdown, summarizePhase0Baseline } from './phase0BaselineSummary.js';
function makeEvent(
timestamp: number,
event_type: AuditEvent['event_type'],
event: Record<string, unknown>,
): AuditEvent {
return {
timestamp,
level: 'info',
event_type,
event,
};
}
describe('summarizePhase0Baseline', () => {
it('summarizes run outcomes, cancel latency, and reaction decisions', () => {
const events: AuditEvent[] = [
makeEvent(1000, 'run.state', {
session_id: 'telegram:s1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
state: 'start',
}),
makeEvent(1200, 'run.state', {
session_id: 'telegram:s1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
state: 'complete',
}),
makeEvent(2000, 'run.state', {
session_id: 'discord:s2',
channel: 'discord',
sender: 'u2',
source: 'gateway',
state: 'start',
}),
makeEvent(2400, 'run.state', {
session_id: 'discord:s2',
channel: 'discord',
sender: 'u2',
source: 'gateway',
state: 'error',
}),
makeEvent(3000, 'run.state', {
session_id: 'telegram:s3',
channel: 'telegram',
sender: 'u3',
source: 'channel',
state: 'start',
}),
makeEvent(3200, 'run.state', {
session_id: 'telegram:s3',
channel: 'telegram',
sender: 'u3',
source: 'channel',
state: 'cancelled',
}),
makeEvent(3300, 'run.state', {
session_id: 'telegram:s3',
channel: 'telegram',
sender: 'u3',
source: 'channel',
state: 'cancel_requested',
}),
makeEvent(3500, 'run.cancel', {
session_id: 'telegram:s3',
channel: 'telegram',
sender: 'u3',
source: 'channel',
requested: true,
acknowledged: true,
latency_ms: 120,
}),
makeEvent(3600, 'run.cancel', {
session_id: 'discord:s2',
channel: 'discord',
sender: 'u2',
source: 'gateway',
requested: true,
acknowledged: false,
latency_ms: 300,
}),
makeEvent(3700, 'reaction.match', {
session_id: 'telegram:s1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
rule_name: 'boss-email',
}),
makeEvent(3800, 'reaction.skip', {
session_id: 'telegram:s1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
reason: 'no_match',
candidate_count: 1,
}),
makeEvent(3900, 'reaction.skip', {
session_id: 'discord:s2',
channel: 'discord',
sender: 'u2',
source: 'gateway',
reason: 'no_rules',
candidate_count: 0,
}),
];
const summary = summarizePhase0Baseline(events);
expect(summary.event_counts.run_state).toBe(7);
expect(summary.run_outcomes.overall.total_outcomes).toBe(3);
expect(summary.run_outcomes.overall.complete).toBe(1);
expect(summary.run_outcomes.overall.cancelled).toBe(1);
expect(summary.run_outcomes.overall.error).toBe(1);
expect(summary.run_outcomes.overall.cancel_requested).toBe(1);
expect(summary.run_outcomes.overall.start).toBe(3);
const telegram = summary.run_outcomes.by_channel.find((row) => row.key === 'telegram');
expect(telegram?.stats.total_outcomes).toBe(2);
const discord = summary.run_outcomes.by_channel.find((row) => row.key === 'discord');
expect(discord?.stats.total_outcomes).toBe(1);
const cancelStats = summary.cancel_latency_ms;
expect(cancelStats?.count).toBe(2);
expect(cancelStats?.p50_ms).toBe(210);
expect(cancelStats?.p95_ms).toBe(291);
expect(summary.reactions.matched).toBe(1);
expect(summary.reactions.skipped).toBe(2);
expect(summary.reactions.match_rate_pct).toBe(33.33);
expect(summary.reactions.skip_reasons).toEqual([
{ reason: 'no_match', count: 1, pct: 50 },
{ reason: 'no_rules', count: 1, pct: 50 },
]);
});
it('filters by channel', () => {
const events: AuditEvent[] = [
makeEvent(1000, 'run.state', {
session_id: 'telegram:s1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
state: 'complete',
}),
makeEvent(1100, 'run.state', {
session_id: 'discord:s2',
channel: 'discord',
sender: 'u2',
source: 'gateway',
state: 'error',
}),
];
const summary = summarizePhase0Baseline(events, { channels: ['telegram'] });
expect(summary.run_outcomes.overall.total_outcomes).toBe(1);
expect(summary.run_outcomes.by_channel).toHaveLength(1);
expect(summary.run_outcomes.by_channel[0]?.key).toBe('telegram');
});
it('applies max row limits including zero', () => {
const events: AuditEvent[] = [
makeEvent(1000, 'run.state', {
session_id: 's1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
state: 'complete',
}),
makeEvent(1100, 'run.state', {
session_id: 's2',
channel: 'discord',
sender: 'u2',
source: 'gateway',
state: 'error',
}),
makeEvent(1200, 'reaction.skip', {
session_id: 's1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
reason: 'no_match',
}),
makeEvent(1300, 'reaction.skip', {
session_id: 's2',
channel: 'discord',
sender: 'u2',
source: 'gateway',
reason: 'no_rules',
}),
];
const none = summarizePhase0Baseline(events, {
maxChannels: 0,
maxSessions: 0,
maxSkipReasons: 0,
});
expect(none.run_outcomes.by_channel).toHaveLength(0);
expect(none.run_outcomes.by_session).toHaveLength(0);
expect(none.reactions.skip_reasons).toHaveLength(0);
const oneEach = summarizePhase0Baseline(events, {
maxChannels: 1,
maxSessions: 1,
maxSkipReasons: 1,
});
expect(oneEach.run_outcomes.by_channel).toHaveLength(1);
expect(oneEach.run_outcomes.by_session).toHaveLength(1);
expect(oneEach.reactions.skip_reasons).toHaveLength(1);
});
it('rejects negative max limits', () => {
const events: AuditEvent[] = [];
expect(() => summarizePhase0Baseline(events, { maxSessions: -1 })).toThrow('maxSessions');
expect(() => summarizePhase0Baseline(events, { maxChannels: -1 })).toThrow('maxChannels');
expect(() => summarizePhase0Baseline(events, { maxSkipReasons: -1 })).toThrow('maxSkipReasons');
});
});
describe('renderPhase0BaselineMarkdown', () => {
it('renders key sections', () => {
const events: AuditEvent[] = [
makeEvent(1000, 'run.state', {
session_id: 'telegram:s1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
state: 'complete',
}),
makeEvent(1200, 'reaction.skip', {
session_id: 'telegram:s1',
channel: 'telegram',
sender: 'u1',
source: 'channel',
reason: 'no_match',
candidate_count: 1,
}),
];
const summary = summarizePhase0Baseline(events);
const markdown = renderPhase0BaselineMarkdown(summary);
expect(markdown).toContain('Phase 0 Baseline Telemetry Summary');
expect(markdown).toContain('Run Outcomes (Overall)');
expect(markdown).toContain('Reaction Decisions');
expect(markdown).toContain('no_match');
});
});