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.
This commit is contained in:
William Valentin
2026-02-27 13:08:37 -08:00
parent fd0ab6e6df
commit c68fd2498e
3 changed files with 93 additions and 5 deletions
+59
View File
@@ -163,6 +163,65 @@ describe('summarizePhase0Baseline', () => {
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', () => {