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
+54 -7
View File
@@ -5,6 +5,7 @@ import { dirname, resolve } from 'node:path';
import { parseArgs } from 'node:util';
import { queryAuditLogs } from '../src/audit/export.js';
import { capturePhase0LiveBaselineEvents } from '../src/audit/phase0LiveBaseline.js';
import { findLatestGatewayCancelWindow } from '../src/audit/phase0GatewayWindow.js';
import {
renderPhase0BaselineMarkdown,
summarizePhase0Baseline,
@@ -25,6 +26,8 @@ function usage(): string {
' --channel <name[,name...]> Restrict sample to channels',
' --source <gateway|channel[,..]> Restrict sample to sources',
' --exclude-session-substring <text[,..]> Exclude sessions containing any substring (default: probe)',
' --auto-gateway-cancel-window Auto-select latest gateway cancel/cancelled session window',
' --window-padding-ms <number> Milliseconds added before/after auto-selected window (default: 250)',
' --raw-identifiers Keep raw session/sender/request IDs (default: anonymized)',
' --tag <YYYY-MM-DD> Output file tag (default: current date UTC)',
' --sample-out <path> Output JSONL sample path override',
@@ -128,6 +131,8 @@ async function main(): Promise<void> {
channel: { type: 'string' },
source: { type: 'string' },
'exclude-session-substring': { type: 'string' },
'auto-gateway-cancel-window': { type: 'boolean' },
'window-padding-ms': { type: 'string' },
'raw-identifiers': { type: 'boolean' },
tag: { type: 'string' },
'sample-out': { type: 'string' },
@@ -149,14 +154,48 @@ async function main(): Promise<void> {
const auditPath = expandHomePath(values.audit ?? '~/.local/share/flynn/audit.log');
const tag = values.tag ?? isoDateTagNow();
const sampleOut = values['sample-out'] ?? `docs/plans/artifacts/phase0_baseline_live_${tag}.jsonl`;
const summaryJsonOut = values['summary-json-out'] ?? `docs/plans/artifacts/phase0_baseline_live_${tag}.json`;
const summaryMdOut = values['summary-md-out'] ?? `docs/plans/artifacts/phase0_baseline_live_${tag}.md`;
const channels = parseCsv(values.channel);
const sources = parseSources(values.source);
let sources = parseSources(values.source);
const excludeSessionSubstrings = parseCsv(values['exclude-session-substring']) ?? ['probe'];
const startTime = parseTime(values.since, '--since');
const endTime = parseTime(values.until, '--until');
const autoGatewayCancelWindow = Boolean(values['auto-gateway-cancel-window']);
const windowPaddingMs = parseOptionalNumber(values['window-padding-ms'], '--window-padding-ms');
if (windowPaddingMs !== undefined && windowPaddingMs < 0) {
throw new Error('--window-padding-ms must be greater than or equal to 0.');
}
if (autoGatewayCancelWindow && (values.since || values.until)) {
throw new Error('--auto-gateway-cancel-window cannot be combined with --since/--until.');
}
if (autoGatewayCancelWindow && sources && !sources.includes('gateway')) {
throw new Error('--auto-gateway-cancel-window requires --source to include "gateway" (or omit --source).');
}
let startTime = parseTime(values.since, '--since');
let endTime = parseTime(values.until, '--until');
let autoWindow: ReturnType<typeof findLatestGatewayCancelWindow> = null;
if (autoGatewayCancelWindow) {
sources = sources ?? ['gateway'];
const autoWindowSourceEvents = await queryAuditLogs(auditPath, {
event_types: ['run.state', 'run.cancel'],
});
autoWindow = findLatestGatewayCancelWindow(autoWindowSourceEvents, {
padding_ms: windowPaddingMs ?? 250,
});
if (!autoWindow) {
throw new Error('No gateway cancel/cancelled session window found in audit log.');
}
startTime = autoWindow.start_time_ms;
endTime = autoWindow.end_time_ms;
}
const isGatewayOnly = sources?.length === 1 && sources[0] === 'gateway';
const defaultBaseName = isGatewayOnly
? `docs/plans/artifacts/phase0_baseline_live_gateway_${tag}`
: `docs/plans/artifacts/phase0_baseline_live_${tag}`;
const sampleOut = values['sample-out'] ?? `${defaultBaseName}.jsonl`;
const summaryJsonOut = values['summary-json-out'] ?? `${defaultBaseName}.json`;
const summaryMdOut = values['summary-md-out'] ?? `${defaultBaseName}.md`;
const summaryOptions: Phase0BaselineSummaryOptions = {
channels,
@@ -194,6 +233,12 @@ async function main(): Promise<void> {
sources,
exclude_session_substrings: excludeSessionSubstrings,
anonymized_identifiers: !values['raw-identifiers'],
auto_gateway_cancel_window: autoWindow
? {
...autoWindow,
padding_ms: windowPaddingMs ?? 250,
}
: undefined,
},
options: summaryOptions,
summary,
@@ -204,6 +249,9 @@ async function main(): Promise<void> {
await writeTextFile(summaryMdOut, `${markdown}\n`);
process.stdout.write(`Captured ${sampledEvents.length} events from ${sourceEvents.length} source events.\n`);
if (autoWindow) {
process.stdout.write(`- auto gateway window: session=${autoWindow.session_id} start=${autoWindow.start_time_ms} end=${autoWindow.end_time_ms}\n`);
}
process.stdout.write(`- sample: ${sampleOut}\n`);
process.stdout.write(`- summary json: ${summaryJsonOut}\n`);
process.stdout.write(`- summary md: ${summaryMdOut}\n`);
@@ -214,4 +262,3 @@ main().catch((error) => {
process.stderr.write(`${message}\n\n${usage()}\n`);
process.exitCode = 1;
});