feat(audit): automate gateway phase0 live-window capture
This commit is contained in:
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user