feat(audit): Add automation component logging

Add audit logging to:
- WebhookHandler: connect/disconnect, receive, not_found, denied, HMAC verified
- HeartbeatMonitor: start/stop, cycle, check, fail, recover
- GmailWatcher: connect/disconnect lifecycle events

All automation events now captured in audit log with proper context
This commit is contained in:
William Valentin
2026-02-11 16:04:33 -08:00
parent d62e836b5d
commit 2dddae8f9b
3 changed files with 47 additions and 2 deletions
+26
View File
@@ -2,6 +2,7 @@ import { statfsSync, accessSync, constants as fsConstants } from 'fs';
import { request } from 'http';
import type { HeartbeatConfig, HeartbeatCheck } from '../config/schema.js';
import type { ChannelAdapter, ChannelStatus, OutboundMessage } from '../channels/types.js';
import { auditLogger } from '../audit/index.js';
/** Result of a single health check. */
export interface CheckResult {
@@ -77,6 +78,7 @@ export class HeartbeatMonitor {
const intervalMs = parseInterval(this.deps.config.interval);
console.log(`HeartbeatMonitor: starting (interval=${this.deps.config.interval}, checks=[${this.deps.config.checks.join(', ')}])`);
auditLogger?.systemStart('HeartbeatMonitor', { interval: this.deps.config.interval, checks: this.deps.config.checks });
this.timer = setInterval(() => {
this.runChecks().catch((err) => {
@@ -96,6 +98,7 @@ export class HeartbeatMonitor {
clearInterval(this.timer);
this.timer = undefined;
}
auditLogger?.systemStop('HeartbeatMonitor');
}
/** Run all configured checks and return the result. */
@@ -136,6 +139,12 @@ export class HeartbeatMonitor {
}
checks.push(result);
auditLogger?.heartbeatCheck({
check_name: result.name,
healthy: result.healthy,
message: result.message,
duration_ms: result.durationMs,
});
}
const healthy = checks.every((c) => c.healthy);
@@ -154,16 +163,33 @@ export class HeartbeatMonitor {
this.notifiedFailure = true;
const failedChecks = checks.filter((c) => !c.healthy).map((c) => `${c.name}: ${c.message}`);
await this.notify(`Heartbeat FAILING (${this.consecutiveFailures} consecutive failures):\n${failedChecks.join('\n')}`);
auditLogger?.heartbeatFail({
checks_failed: failedChecks,
consecutive_failures: this.consecutiveFailures,
threshold: this.deps.config.failure_threshold,
});
}
} else {
if (this.notifiedFailure) {
// Recovery notification
await this.notify(`Heartbeat RECOVERED after ${this.consecutiveFailures} consecutive failure(s). All checks passing.`);
auditLogger?.heartbeatRecover({
consecutive_failures_before: this.consecutiveFailures,
});
}
this.consecutiveFailures = 0;
this.notifiedFailure = false;
}
auditLogger?.heartbeatCycle({
interval_ms: parseInterval(this.deps.config.interval),
checks: this.deps.config.checks,
healthy,
consecutive_failures: this.consecutiveFailures,
});
return heartbeatResult;
}